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


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

    1、项目整合SpringSecurity

    1.1、引入SpringSecurity依赖

            
            <springboot.version>2.5.5springboot.version>
    
                
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-securityartifactId>
                    <version>${springboot.version}version>
                dependency>
    

    1.2、启动测试

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

    在这里插入图片描述

    1.3、自定义实体类继承UserDetails

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

    /**
     * @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、自定义配制文件

    引入依赖

            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-configuration-processorartifactId>
            dependency>
    

    自定义配制信息

    # 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配制文件来提取自定义配制信息!

    /**
     * @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;
    }
    

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

    # 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";
    
        }
    
    }
    
    

    开启配制文件的使用

    /**
     * @author 木字楠
     * @version 1.0
     * @description 配制文件是否开始使用
     * @date 2022/8/12
     */
    @Configuration
    @EnableConfigurationProperties({
            HigestAuthorityProperties.class,
    })
    public class PropertiesConfig {
    }
    
    

    1.5、重写loadUserByUsername方法

    /**
     * @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拦截所有请求,未登录用户访问接口时将会统一返回 请登陆后再进行访问!,而带有匿名注解的接口将会被放行,无论用户是否登录,都可以正常访问。

    /**
     * @author 木字楠
     * @version 1.0
     * @description 匿名访问注解
     * @date 2022/8/12
     */
    public @interface Anonymous {
    }
    
    
        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) 内部重新实现,主要放行静态资源

        @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执行方式以及密码加密方式

        private final UserDetailsServiceImpl userDetailsService;
        
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
    
  • 相关阅读:
    Kafka为什么性能这么快?4大核心原因详解
    端子排中间继电器WZY-400W
    爽,我终于实现了selenium图片滑块验证码!【附代码】
    HBase 记录
    clickhouse分布式之弹性扩缩容的故事
    一、数据库的基础简介
    【小游戏】Unity游戏愤怒的足球(小鸟)
    AI与人类的较量席卷艺术界:DALL·E将战胜人类?
    Day726.Java平台模块系统 -Java8后最重要新特性
    JVM调优之StringTable调优
  • 原文地址:https://blog.csdn.net/nanshen__/article/details/127068234