• SpringSecurity Oauth2实战 - 04 自定义AuthProvider实现认证登录



    这是一个系列文章,具体项目结构可以参考这篇文章: SpringSecurity Oauth2实战 - 01 搭建授权服务器(密码模式)

    1. 搭建资源服务器

    1. Token存储配置类 TokenStoreAutoConfiguration
    @Configuration
    public class TokenStoreAutoConfiguration {
    
        @Autowired
        private RedisConnectionFactory connectionFactory;
    
        /**
         * 初始化 RedisTokenStore 用于将 token 存储至 Redis
         */
        @Bean
        public RedisTokenStore redisTokenStore() {
            RedisTokenStore redisTokenStore = new RedisTokenStore(connectionFactory);
            // 设置key的层级前缀,方便查询
            redisTokenStore.setPrefix("TOKEN:");
            return redisTokenStore;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    2. 资源服务器配置类 ResourceServerAutoConfiguration
    @Slf4j
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private TokenStore tokenStore;
    
        @Value("${spring.application.name}")
        private String appName;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(appName);
            resources.tokenStore(tokenStore);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    3. 在META-INF/spring.factories文件下添加配置类
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.hh.config.ResourceServerAutoConfiguration,\
    com.hh.config.TokenStoreAutoConfiguration
    
    • 1
    • 2
    • 3
    • 4

    2. 搭建授权服务器

    1. 密码加密配置类 PasswordEncodeConfig
    @Configuration
    public class PasswordEncodeConfig {
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2. RestTemplateConfig
    @Configuration
    public class RestTemplateConfig {
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    3. 授权服务器配置类 AuthorizationServerConfiguration
    /**
     * 配置授权服务器
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        @Qualifier("refreshTokenUserDetailService")
        private UserDetailsService refreshTokenUserDetailService;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private TokenStore tokenStore;
    
        /**
         * 用来配置授权服务器可以为哪些客户端授权,使用哪种授权模式
         *
         * 密码模式:在密码模式中,用户向客户端提供用户名和密码,客户端通过用户名和密码到认证服务器获取令牌
         * (A)用户访问客户端,提供URI连接包含用户名和密码信息给授权服务器
         * (B)授权服务器对客户端进行身份验证
         * (C)授权通过,返回access_token给客户端
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    // 客户端id
                    .withClient("client_id")
                    // 客户端秘钥
                    .secret(passwordEncoder.encode("client_secret"))
                    // 授权模式
                    .authorizedGrantTypes("password", "refresh_token", "client_credentials")
                    // access_token的过期时长
                    .accessTokenValiditySeconds(43200)
                    // refresh_token的过期时长
                    .refreshTokenValiditySeconds(86400)
                    // 允许授权的范围
                    .scopes("all");
        }
    
        /**
         * 刷新令牌必须配置userDetailsService,用来刷新令牌时的认证
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            // 配置token的存储方式
            endpoints.tokenStore(tokenStore);
            // 配置认证管理器
            endpoints.authenticationManager(authenticationManager);
            // 配置认证数据源,刷新令牌时的认证
            endpoints.userDetailsService(refreshTokenUserDetailService);
        }
    
        /**
         * 开启token检查接口
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security){
            security.allowFormAuthenticationForClients().checkTokenAccess("permitAll()");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    注意: 刷新令牌必须配置 UserDetailsService,用来刷新令牌时的认证,这里配置了一个RefreshTokenUserDetailServiceImpl 实现 UserDetailsService 接口,默认使用的账号密码验证方式会自动加载DaoAuthenticationProvider类,该类会自动扫描一个UserDetailService实现类并注入,如果存在多个UserDetailService实现类会注入失败。

    @Slf4j
    @Service("refreshTokenUserDetailService")
    public class RefreshTokenUserDetailServiceImpl implements UserDetailsService {
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    4. 数据库表的数据准备

    因为项目使用的flyway组件,因此项目启动时就会执行sql脚本创建表并添加表中数据:

    -- 用户表
    CREATE TABLE `user`(
        `id`         varchar(64)  NOT NULL COMMENT '用户id',
        `username`   varchar(50)  NOT NULL COMMENT '账户名称',
        `password`   varchar(200) NOT NULL COMMENT '用户密码密文',
        `phone`      varchar(20) DEFAULT NULL COMMENT '手机号码',
        `createTime` datetime    DEFAULT NULL COMMENT '创建时间',
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
    
    -- 角色表
    CREATE TABLE `role`(
        `id`          varchar(64) NOT NULL COMMENT '主键',
        `name`        varchar(255) DEFAULT NULL COMMENT '角色名称',
        `description` varchar(300) DEFAULT NULL,
        `createTime`  datetime     DEFAULT NULL COMMENT '创建时间',
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
    
    -- 权限表
    CREATE TABLE `policy`(
        `id`         varchar(32) NOT NULL,
        `name`       varchar(64) NOT NULL COMMENT '权限策略名',
        `createTime` timestamp   NOT NULL DEFAULT current_timestamp() COMMENT '创建时间',
        PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMMENT ='权限策略表';
    
    -- 用户角色表
    CREATE TABLE `user_role`(
        `id`         varchar(64) NOT NULL COMMENT '主键',
        `userId`     varchar(64) DEFAULT NULL COMMENT '用户id',
        `roleId`     varchar(64) DEFAULT NULL COMMENT '角色id',
        `createTime` datetime    DEFAULT NULL COMMENT '创建时间',
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
    
    -- 角色权限表
    CREATE TABLE `role_policy`(
        `id`         varchar(64) NOT NULL COMMENT '主键',
        `roleId`     varchar(64) DEFAULT NULL COMMENT '角色id',
        `policyId`   varchar(64) DEFAULT NULL COMMENT '菜单权限id',
        `createTime` datetime    DEFAULT NULL COMMENT '创建时间',
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    -- 添加两个用户
    INSERT INTO `authority`.`user` (`id`, `username`, `password`, `phone`, `createTime`) VALUES ('c24e51170fc64702ab38778e6562d8ac', 'zhangsan', '$2a$12$ECL54x35.kTMjNuefp28nOJu3bnTY7TPyVLGEMfapem10m1u0hMze', '18751779090', '2022-11-04 06:11:35');
    INSERT INTO `authority`.`user` (`id`, `username`, `password`, `phone`, `createTime`) VALUES ('d20ff7f13c61474687b7cb6148e2823b', 'root', '$2a$12$f2csfxV4IE6/lLD.4ZWJCOGOLNtjYhYfPMn20s1bMOkKN/RtaBOTe', '18751779090', '2022-11-04 15:49:05');
    
    -- 添加两个角色
    INSERT INTO `authority`.`role` (`id`, `name`, `description`, `createTime`) VALUES ('3e65cd3532d94feb9bdaade9c09b8f08', '超级管理员', '超级管理员', '2022-11-04 06:18:41');
    INSERT INTO `authority`.`role` (`id`, `name`, `description`, `createTime`) VALUES ('bca2b44e287642fb9eac63d2f0f08b4b', '平台管理员', '平台管理员', '2022-11-04 06:12:24');
    
    -- 添加5个权限
    INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('5eca3c4d9c224580855edb876beaa0ce', 'userEdit', '2022-11-04 06:16:29');
    INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('618717f96b9a4f0380571ab203a5ac75', 'superAdmin', '2022-11-04 06:16:57');
    INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('cab872e977824f65849b4ae1fc763d84', 'knowledgeQuery', '2022-11-04 06:16:09');
    INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('fb693bf8ec13424e8ee370c08a597fcf', 'userQuery', '2022-11-04 06:16:40');
    INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('fd60b1eaf222482496bb9ec5ec3f86dc', 'knowledgeEdit', '2022-11-04 06:16:00');
    
    -- 给zhangsan用户关联2个角色
    INSERT INTO `authority`.`user_role` (`id`, `userId`, `roleId`, `createTime`) VALUES ('cdcc6217725b49efa733f5e50b09ab7a', 'c24e51170fc64702ab38778e6562d8ac', '3e65cd3532d94feb9bdaade9c09b8f08', '2022-11-04 06:20:37');
    
    -- 给超级管理员角色添加5个权限
    INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('adb5aead86f444ec8aef9f8e60fdc649', '3e65cd3532d94feb9bdaade9c09b8f08', '618717f96b9a4f0380571ab203a5ac75', '2022-11-04 06:24:27');
    INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('c6028a63797e44e99e83d9d1cb87e11b', '3e65cd3532d94feb9bdaade9c09b8f08', '5eca3c4d9c224580855edb876beaa0ce', '2022-11-04 06:24:12');
    INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('c7c8ac1db9b04cf387bb886a7d71834a', '3e65cd3532d94feb9bdaade9c09b8f08', 'fb693bf8ec13424e8ee370c08a597fcf', '2022-11-04 06:24:48');
    INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('dda442d423de4386bfb43a7126a03c12', '3e65cd3532d94feb9bdaade9c09b8f08', 'fd60b1eaf222482496bb9ec5ec3f86dc', '2022-11-04 06:25:02');
    INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('ff95697aa0c54d83af00e8e36816b5fa', '3e65cd3532d94feb9bdaade9c09b8f08', 'cab872e977824f65849b4ae1fc763d84', '2022-11-04 06:24:40');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    5. 密码加密工具类 BcryptUtil
    /**
     * bcrypt算法工具
     * bcrypt加密算法属于单向hash加密, salt与密文按照约定拼接, 不用单独管理salt, 每次生成不同密文, 即使明文相同
     */
    @Slf4j
    public class BcryptUtil {
        /**
         * bcrypt加密算法的long_round参数配置, 安全基线明确要求>=12
         */
        private static BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(12);
    
        /**
         * 对密码进行加密
         *
         * @param rawPwd 原始密码
         * @return 加密后的密码
         */
        public static String bEncryptEncode(String rawPwd) {
            return bCryptPasswordEncoder.encode(rawPwd);
        }
    
        /**
         * 进行加盐的比对
         * 
         * @param rawPwd 原有的密码
         * @param dbPwd  加密后的密码
         * @return boolean 是否相同
         */
        public static boolean bEncryptMatch(String rawPwd, String dbPwd) {
            return bCryptPasswordEncoder.matches(rawPwd, dbPwd);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    3. 自定义AuthProvider实现认证登录

    1. 登录请求入口 AuthController
    @Slf4j
    @RestController
    @RequestMapping("/api/v1")
    public class AuthController {
    
        @Autowired
        private LoginService loginService;
    
        @PostMapping("/login")
        public ApiResponse<AuthenticationInfo> authority( @Validated @RequestBody LoginQo loginQo){
            AuthenticationInfo authenticationInfo = loginService.checkAndAuth( loginQo);
            return new ApiResponse<>(0,"success",authenticationInfo);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意:/api/v1/login 请求资源时受保护的资源,因此需要在资源服务器中放行该请求

    @Slf4j
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private TokenStore tokenStore;
    
        @Value("${spring.application.name}")
        private String appName;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(appName);
            resources.tokenStore(tokenStore);
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                // 放行的请求
                .antMatchers("/api/v1/login").permitAll()
                // 其他请求必须认证才能访问
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    2. 登录成功的响应类 AuthenticationInfo
    @Data
    public class AuthenticationInfo {
    
        /*令牌对象*/
        private AuthToken authToken;
    
        /*用户详情*/
        private User user;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ① 登录成功返回的用户信息 User

    @Data
    public class User implements Serializable {
        private static final long serialVersionUID = 160958617672077663L;
        /**
        * 用户id
        */
        private String id;
        /**
        * 账户名称
        */
        private String username;
        /**
        * 用户密码密文
        */
        private String password;
        /**
        * 手机号码
        */
        private String phone;
        /**
        * 创建时间
        */
        private Date createTime;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    ② 认证成功返回的 AuthToken

    /**
     * 认证返回的AccessToken
     */
    @Data
    public class AuthToken {
    
        /*令牌*/
        private String accessToken;
    
        /*令牌类型*/
        private String tokenType;
    
        /*刷新使用的令牌*/
        private String refreshToken;
    
        /*过期时间长,单位:秒*/
        private Integer expiresIn;
    
       /*令牌的作用范围*/
        private String scope;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    ③ 认证的主体

    /**
     * 用于jwt认证的主体
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class AuthJwtPrincipal implements Serializable {
        /*用户ID*/
        private String userId;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    3. 登录认证 LoginServiceImpl
    @Service
    @Slf4j
    public class LoginServiceImpl implements LoginService {
    
        @Autowired
        private UserService userService;
    
        @Autowired
        private RestTemplate restTemplate;
    
        /**
         * 登录认证
         * @param loginQo 登录请求体
         * @return AuthenticationInfo
         */
        @Override
        public AuthenticationInfo checkAndAuth(LoginQo loginQo) {
            // 密码认证:数据库中的密码是经过bcrypt加密算法存储的
            User user = userService.queryByName(loginQo.getName());
            String dbEncryptPwd = user.getPassword();
            boolean isPassed = BcryptUtil.bEncryptMatch(loginQo.getPassword(), dbEncryptPwd);
            // 登陆失败
            if (!isPassed) {
                log.info("the user: {} login failed, account or password is wrong", user.getId());
                throw new RuntimeException("用户账号或者密码错误");
            }
            // 登录成功,获取用户认证信息
            AuthJwtPrincipal authJwtPrincipal = AuthJwtPrincipal.builder().userId(user.getId()).build();
            AuthenticationInfo authenticationInfo =  obtainAuthenticationInfo(authJwtPrincipal,user);
            String accessToken = authenticationInfo.getAuthToken().getAccessToken();
            if(StringUtils.isBlank(accessToken)){
                throw new RuntimeException("用户账号或者密码错误");
            }
            return authenticationInfo;
        }
    
        /**
         * 获取认证信息
         * @param authJwtPrincipal 认证主体
         * @param user 用户信息
         * @return AuthenticationInfo
         */
        private AuthenticationInfo obtainAuthenticationInfo(AuthJwtPrincipal authJwtPrincipal,User user) {
            AuthenticationInfo authenticationInfo = new AuthenticationInfo();
            // SpringSecurity Oauth2获取access_token
            AuthToken authToken = getAccessToken(authJwtPrincipal);
            authenticationInfo.setAuthToken(authToken);
            authenticationInfo.setUser(user);
            return authenticationInfo;
        }
    
        /**
         *  通过登陆信息获取相应的令牌对象
         * @param authJwtPrincipal 认证主体
         * @return AuthToken
         */
        public AuthToken getAccessToken(AuthJwtPrincipal authJwtPrincipal) {
            String loginJsonString = JSON.toJSONString(authJwtPrincipal);
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("username", loginJsonString);
            // 因为使用的是自定义认证方式 CustomerAuthProvider,密码用不到,所以设为一个空值即可
            map.add("password", StringUtils.EMPTY);
            map.add("client_id", "client_id");
            map.add("client_secret","client_secret");
            map.add("grant_type", "password");
            map.add("scope", "all");
            // 这里会进行Oauth2.0的请求 在CustomerAuthProvider进行认证处理
            Map response = restTemplate.postForObject("http://127.0.0.1:8081/oauth/token", map, Map.class);
            if (MapUtil.isEmpty(response)) {
                return null;
            }
            // 封装返回
            AuthToken authToken = new AuthToken();
            authToken.setAccessToken((String) response.get("access_token"));
            authToken.setExpiresIn((Integer) response.get("expires_in"));
            authToken.setRefreshToken((String) response.get("refresh_token"));
            authToken.setTokenType((String) response.get("token_type"));
            authToken.setScope((String) response.get("scope"));
            return authToken;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    4. 自定义认证管理器 CustomerAuthProvider
    @Slf4j
    @Component
    public class CustomAuthProvider implements AuthenticationProvider {
    
        @Autowired
        private CustomUserDetailService customUserDetailService;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            // LoginServiceImpl#getAccessToken方法中的loginJsonString
            String contentJsonStr
                    = Objects.isNull(authentication.getPrincipal()) ? StringUtils.EMPTY : authentication.getName();
            // 通过authentication获取登录用户信息AuthUser
            AuthUser user = customUserDetailService.loadUserByContent(contentJsonStr);
            UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                    user, authentication.getCredentials(), user.getAuthorities()
            );
            result.setDetails(authentication.getDetails());
            return result;
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    5. 自定义UserDetailService实现类 CustomUserDetailService

    作为UserDetailService实现类,原本应该继承UserDetailService接口,但是由于默认使用的账号密码验证方式会自动加载DaoAuthenticationProvider类,该类会自动扫描一个UserDetailService实现类并注入,如果存在多个UserDetailService实现类会注入失败,所以这里不继承该接口,直接实现功能,前面我们已经配置了一个RefreshTokenUserDetailServiceImpl

    /**
     * 自定义的UserDetailService调用实现
     */
    @Slf4j
    @Service("customUserDetailService")
    public class CustomUserDetailService{
    
        @Autowired
        private UserService userService;
    
        @Autowired
        private PolicyService policyService;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private RolePolicyService rolePolicyService;
    
        public AuthUser loadUserByContent(String content) throws UsernameNotFoundException {
            if (StringUtils.isBlank(content)) {
                throw new UsernameNotFoundException("exceptions.authentication.principal.error");
            }
            // content是loginJsonString,可以转为AuthJwtPrincipal
            AuthJwtPrincipal authJwtPrincipal = JSON.parseObject(content, AuthJwtPrincipal.class);
            String userId = authJwtPrincipal.getUserId();
    
            // 获取登录用户信息
            User user = userService.queryById(userId);
            if (Objects.isNull(user)) {
                throw new UsernameNotFoundException("exceptions.authentication.principal.error");
            }
    
            // 获取登录用户的角色信息
            List<UserRole> userRoleList  = userService.getUserRoleByUserId(userId);
            List<String> roleIds = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());
    
            // 获取登录用户的权限信息
            List<Policy> policyList = policyService.getPolicyByRoleIds(roleIds);
            if (CollectionUtils.isEmpty(policyList)) {
                throw new UsernameNotFoundException("contact.administrator.to.assign.policy");
            }
            List<String> policyNames = policyList.stream().map(Policy::getName).collect(Collectors.toList());
            Set<GrantedAuthority> grantedAuthoritySet = policyList.stream()
                    .map(policyEntity -> new SimpleGrantedAuthority(policyEntity.getName()))
                    .collect(Collectors.toSet());
    
            // 存储本地用户信息
            UserInfo userInfo = new UserInfo();
            userInfo.setId(user.getId());
            userInfo.setUsername(user.getUsername());
            userInfo.setRoleIds(roleIds);
            userInfo.setPolicyName(policyNames);
    
            String encodePasswordStr = passwordEncoder.encode("password");
            return new AuthUser(userId, encodePasswordStr, userInfo, grantedAuthoritySet);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    6. 认证成功后的用户对象 AuthUser
    /**
     * 认证成功后的用户对象
     */
    @AllArgsConstructor
    @NoArgsConstructor
    public class AuthUser implements UserDetails {
        @Setter
        private String username;
    
        @Setter
        private String password;
    
        @Setter
        @Getter
        private UserInfo userInfo;
    
        /*登录用户具备的权限*/
        @Setter
        private Set<GrantedAuthority> authorities;
    
        public AuthUser(String username, String password){
            this.username = username;
            this.password = password;
        }
    
        public AuthUser(String username, String password, Set<GrantedAuthority> authorities){
            this(username,password);
            this.authorities = authorities;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public String getUsername() {
            return 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    用户信息 UserInfo :

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UserInfo implements Serializable {
        private static final long serialVersionUID = 671647501342140183L;
        // 用户信息
        private String id;
        private String username;
        // 角色信息
        private List<String> roleIds;
        // 权限信息
        private List<String> policyName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    7. 安全配置类中添加自定义认证管理器 WebSecurityConfig
    /**
     * - @EnableGlobalMethodSecurity:该注解是用来开启权限注解
     * - prePostEnabled = true:开启Spring Security提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize 以及@PreFilter
     */
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        /**
         * 登陆认证相关
         */
        @Autowired
        private CustomAuthProvider customAuthProvider;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
        }
    
        // 自定义AuthenticationManager:并没有在工厂中暴露出来
        // 使用AuthenticationManagerBuilder来自定义AuthenticationManager,覆盖默认的AuthenticationManager
        @Override
        protected void configure(AuthenticationManagerBuilder auth) {
            auth.authenticationProvider(customAuthProvider);
        }
    
        // 如需使用AuthenticationManager,则可以通过覆盖此方法,将configure(AuthenticationManagerBuilder)方法构造的AuthenticationManager暴露为Bean。
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    8. 启动项目测试登录功能

    在这里插入图片描述

    redis中存储的token信息:

    在这里插入图片描述

    核心点debug过程:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    AWS认证SAA-C03每日一题
    HBase 的安装与部署
    uni-app:解决异步请求返回值问题
    下列对“一带一路”倡议的认识中,正确的有()。 A 顺应时代潮流 B 适应发展规律 C 符合各国人民利益 D 具有广阔前景
    科锐国际(计算机类),快手,CVTE,得物,蓝月亮,蓝禾,奇安信,顺丰,康冠科技,金证科技24春招内推
    IB考试45分是如何做到的?
    CUDA和cudnn详细安装过程【通用win】
    C++面向对象三大特性之一------继承
    SpringBoot项目创建
    面试官:我看你简历上写了MySQL,对MySQL InnoDB引擎的索引了解吗?
  • 原文地址:https://blog.csdn.net/qq_42764468/article/details/127701060