后台基础权限框架搭建实现[木字楠博客]

2023-10-17 10:47:07 浏览数 (1)

后台权限框架搭建:本项目权限主要依赖SpringSecurity实现,主要涉及的表有角色表菜单表以及角色菜单关联表等数据库表。权限部分功能的实现需要使用到自定义配制文件、自定义注解、自定义服务类等等…

1、项目整合SpringSecurity

1.1、引入SpringSecurity依赖
代码语言:javascript复制
        <!--===================== SpringBoot相关依赖版本  =========================-->
        <springboot.version>2.5.5</springboot.version>
代码语言:javascript复制
            <!-- security -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
                <version>${springboot.version}</version>
            </dependency>
1.2、启动测试

启动项目之后访问接口发现已经被SpringSecurity拦截,出现如下界面说明SpringSecurity已经成功引入

1.3、自定义实体类继承UserDetails

由于SpringSecurity默认提供的登陆接口会执行loadUserByUsername()方法,此方法的返回值为UserDetails,而SpringSecurity会根据返回值中的加密密码进行密码校验,所以我们需要自定义一个实体类来继承UserDetails

代码语言:javascript复制
/**
 * @author 木字楠
 * @version 1.0
 * @description 用户信息
 * @date 2022/8/12
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {

    /**
     * 用户主键
     */
    private String id;

    /**
     * 用户名/OpenId
     */
    private String username;

    /**
     * 用户密码
     */
    private String password;

    /**
     * 用户登录方式(1 用户名  2 邮箱  3 QQ  4 微信)
     */
    private Integer loginType;

    /**
     * 是否开启邮箱登录(0 否  1是)
     */
    private Boolean emailLogin;

    /**
     * 是否禁用(0 正常  1禁用)
     */
    private Boolean disabled;

    /**
     * 用户名
     */
    private String nickname;

    /**
     * 用户头像
     */
    private String avatar;

    /**
     * 用户性别(-1 未知  0 仙女  1帅哥)
     */
    private String gender;

    /**
     * 用户邮箱
     */
    private String email;

    /**
     * 用户个人简介
     */
    private String personIntro;

    /**
     * ip地址
     */
    private String ipAddress;

    /**
     * ip来源
     */
    private String ipSource;

    /**
     * 用户创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime gmtCreate;

    /**
     * 用户信息更新时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime gmtUpdate;

    /**
     * 用户最近一次登录时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime lastLoginTime;

    /**
     * 浏览器
     */
    private String brower;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 角色
     */
    private String role;

    /**
     * 权限列表
     */
    private Set<String> permissionList;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        SimpleGrantedAuthority list = new SimpleGrantedAuthority(role);
        return Collections.singleton(list);
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
1.4、自定义配制文件

引入依赖

代码语言:javascript复制
        <!-- 自定义配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

自定义配制信息

代码语言:javascript复制
# custom Configuration
application:
  #Swagger Configuration
  swagger:
    isEnable: true
  #Super_admin Configuration
  highest-authority:
    highest-role-label: super_admin
    highest-role-secret: MuZiNan#$%^&*
    highest-permission: <*><*><*>

我们可以使用@Value的注解进行自定义配制信息的提取,但是那样做法太Low,而且不方便。这里我们采用更加优雅的方式来提起自定义注解信息!使用Properties配制文件来提取自定义配制信息!

代码语言:javascript复制
/**
 * @author 木字楠
 * @version 1.0
 * @description 最高权限配制文件
 * @date 2022/8/12
 */
@Data
@ConfigurationProperties(HIGHEST_AUTHORITY)
public class HigestAuthorityProperties {

    /**
     * 最高权限角色标识
     */
    private String highestRoleLabel;

    /**
     * 最高权限角色秘钥
     */
    private String highestRoleSecret;

    /**
     * 最高权限标识
     */
    private String highestPermission;
}

我们是用常量的形式来简化字符串的拼接

代码语言:javascript复制
# ConfigPropertiesConstant

/**
 * @author: 木字楠
 * @date: 2022/8/12
 * @version: 1.0
 */
public class ConfigPropertiesConstant {

    /**
     * 应用相关前缀,位于“Application”配置中
     */
    public static class Application {

        /**
         * 默认前缀
         */
        private static final String DEFAULT_PREFIX = "application";

        /**
         * JWT相关配置信息
         */
        public static final String JWT = DEFAULT_PREFIX   ".jwt";

        /**
         * 最高权限配制信息
         */
        public static final String HIGHEST_AUTHORITY = DEFAULT_PREFIX   ".highest-authority";

        /**
         * 阿里云Oss对象存储
         */
        public static final String ALIYUN_OSS = DEFAULT_PREFIX   ".aliyun.oss";

        /**
         * 七牛云Kodo对象存储
         */
        public static final String QINIU_KODO = DEFAULT_PREFIX   ".qiniu.kodo";

        /**
         * 服务器Ftp上传
         */
        public static final String ECS_FTP = DEFAULT_PREFIX   ".ecs.ftp";

    }

}

开启配制文件的使用

代码语言:javascript复制
/**
 * @author 木字楠
 * @version 1.0
 * @description 配制文件是否开始使用
 * @date 2022/8/12
 */
@Configuration
@EnableConfigurationProperties({
        HigestAuthorityProperties.class,
})
public class PropertiesConfig {
}
1.5、重写loadUserByUsername方法
代码语言:javascript复制
/**
 * @author 木字楠
 * @version 1.0
 * @description 自定义服务实现
 * @date 2022/8/12
 */
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private HttpServletRequest request;
    private final UserAuthService userAuthService;
    private final UserInfoService userInfoService;
    private final RoleService roleService;
    private final MenuService menuService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserAuth userAuth = usernamePasswordCheck(username);
        return convertToUser(request, userAuth);
    }

    /**
     * 用户数据转护
     *
     * @param request  request
     * @param userAuth 用户权限信息
     * @return {@link User} 用户信息
     */
    private User convertToUser(HttpServletRequest request, UserAuth userAuth) {
        User user = BeanCopyUtil.copyObject(userAuth, User.class);
        // 查询用户基础信息
        UserInfo userInfo = userInfoService.getOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getId, userAuth.getUserInfoId()));

        //region 获取用户Ip相关信息
        String ipAddress = IpUtil.getIpAddress(request);
        String ipSource = IpUtil.getIpSource(ipAddress);
        UserAgent userAgent = IpUtil.getUserAgent(request);
        //endregion

        //region 获取用户权限信息
        Set<String> permisstionSet = new HashSet<>(99);
        Role role = roleService.getOne(new LambdaQueryWrapper<Role>().eq(Role::getId, userAuth.getUserRoleId()));
        if (null != role) {
            permisstionSet = menuService.getUserPermissionList(role);
        }
        //endregion

        //数据封装
        String visitor = "游客";
        user.setNickname(userInfo.getNickname());
        user.setAvatar(userInfo.getAvatar());
        user.setGender(GenderEnum.getGenderValueByCode(userInfo.getGender()));
        user.setEmail(userInfo.getEmail());
        user.setPersonIntro(user.getPersonIntro());
        user.setIpAddress(ipAddress);
        user.setIpSource(ipSource);
        user.setGmtCreate(userInfo.getGmtCreate());
        user.setGmtUpdate(userInfo.getGmtUpdate());
        user.setLastLoginTime(userInfo.getLastLoginTime());
        user.setRole(null == role ? visitor : role.getRoleName());
        user.setPermissionList(permisstionSet);
        user.setOs(userAgent.getOperatingSystem().getName());
        user.setBrower(userAgent.getBrowser().getName());

        return user;
    }

    /**
     * 用户名密码校验
     */
    private UserAuth usernamePasswordCheck(String username) {
        // 用户名非空校验
        if (StringUtils.isEmpty(username)) {
            throw new BaseException(HttpCodeEnum.USERNAME_CAN_NOT_BE_EMPTY.getMessage());
        }

        // 查询用户信息
        UserAuth userAuth = userAuthService.getOne(new LambdaQueryWrapper<UserAuth>().eq(UserAuth::getUsername, username)
                .eq(UserAuth::getIsDisabled, SystemConstant.NOT_DISABLED)
                .eq(UserAuth::getIsDeleted, SystemConstant.NOT_DELETED));

        if (null == userAuth) {
            throw new BaseException(HttpCodeEnum.USERNAME_OR_PASSWORD_ERROR.getMessage());
        }

        return userAuth;
    }

}
1.6、自定义匿名访问注解

自定义匿名访问注解,届时我们会使用SpringSecurity拦截所有请求,未登录用户访问接口时将会统一返回 请登陆后再进行访问!,而带有匿名注解的接口将会被放行,无论用户是否登录,都可以正常访问。

代码语言:javascript复制
/**
 * @author 木字楠
 * @version 1.0
 * @description 匿名访问注解
 * @date 2022/8/12
 */
public @interface Anonymous {
}
代码语言:javascript复制
    private final ApplicationContext applicationContext;
    /**
     * 查找可以匿名访问的接口
     *
     * @return 匿名访问接口集合
     */
    private Set<String> listAnonymous() {
        Map<RequestMappingInfo, HandlerMethod> handlerMethods =
                applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        Set<String> anonymousUrls = new HashSet<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
            if (anonymousAccess != null) {
                assert infoEntry.getKey().getPatternsCondition() != null;
                anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
            }
        }
       System.out.println("可以匿名访问的url:{}", anonymousUrls);
        return anonymousUrls;
    }
1.8、编写SpringSecurity配制类

自定义配制类继承WebSecurityConfigurerAdapter,并且重写其中的三个config方法

public void configure(WebSecurity web) 内部重新实现,主要放行静态资源

代码语言:javascript复制
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers(
                "/js/**", "/css/**", "/img/**", "/fonts/**",
                "/", "/index.html", "/favicon.ico", "/doc.html",
                "/swagger-ui.html", "/webjars/**", "/swagger-resources/**", "/v3/**");
    }

protected void configure(AuthenticationManagerBuilder auth) 内部重新实现,指定UserDetailsService执行方式以及密码加密方式

代码语言:javascript复制
    private final UserDetailsServiceImpl userDetailsService;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

0 人点赞