Spring Cloud 集成 Spring Security和Oauth2.0实现认证授权(二)认证中心集成自定义授权模式

740 阅读2分钟

依赖搭建

认证中心原则上也是一个独立的服务,需要引入Web依赖。

<dependencies>
    <dependency>
        <groupId>com.book</groupId>
        <artifactId>cbook_common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
</dependencies>

Spring Security 配置

这里和普通的Spring Security配置一样

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // 这个配置类是创建我们自定义授权模式的认证处理器
    private SmsCodeSecurityConfig smsCodeSecurityConfig;

    @Autowired
    public void setSmsCodeSecurityConfig(SmsCodeSecurityConfig smsCodeSecurityConfig) {
        this.smsCodeSecurityConfig = smsCodeSecurityConfig;
    }

    @Resource
    private GlobalAuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private GlobalAccessDeniedHandler accessDeniedHandler;


    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring()
                .antMatchers("/code/*")
                .and();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .apply(smsCodeSecurityConfig)
                .and()
                .exceptionHandling() // 自定义的认证和授权异常处理器
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
                .and()
                .authorizeRequests()
                .antMatchers("/code/*").permitAll() // 放行获取验证码的请求
                .anyRequest().authenticated();
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

资源配置中心配置

@EnableAuthorizationServer
@Configuration
@Slf4j
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    public DataSource dataSource;
    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    @Resource
    private ClientDetailsService clientDetailsService;

    @Resource
    private TokenStore tokenStore;

    @Resource
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private GlobalAuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private GlobalAccessDeniedHandler accessDeniedHandler;

    // 客户端配置
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端认证模块,oauth2.0授权模式默认在对用户进行认证之前会对客户端进行认证,并且是客户端认证在前面,这里需要实现ClientDetailsService中的loadClientByClientId方法,从数据库中加载客户端信息。
        clients.jdbc(dataSource).clients(clientDetailsService);
    }

    /**
     * 配置令牌访问的安全约束
     */
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .allowFormAuthenticationForClients()
                .passwordEncoder(new BCryptPasswordEncoder())
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
                .tokenKeyAccess("permitAll()");
    }

    /**
     * 配置令牌访问的端点
     */
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    // 获取oauth的授权模式,将我们自定义的授权模式加进去
        List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
        granters.add(new MobilePassWordGranter(tokenServices(), clientDetailsService, endpoints.getOAuth2RequestFactory(), authenticationManager));
        endpoints
                //令牌管理服务,无论哪种模式都需要
                .tokenServices(tokenServices())
                .tokenGranter(new CompositeTokenGranter(granters))
                .exceptionTranslator(new GlobalOauth2ExceptionHandler())
                //只允许POST提交访问令牌,uri:/oauth/token
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    /**
     * 配置令牌的相关信息
     */
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        //客户端端配置策略
        tokenServices.setClientDetailsService(clientDetailsService);
        //支持令牌的刷新
        tokenServices.setSupportRefreshToken(true);
        //令牌服务
        tokenServices.setTokenStore(tokenStore);
        //access_token的过期时间
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 3);
        //refresh_token的过期时间
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
        //一定要配置这一步,否则不会使用JWT方式生成token,默认生成的access_token过短
        tokenServices.setTokenEnhancer(jwtAccessTokenConverter);
        return tokenServices;
    }
}

令牌增强配置

@Configuration
@Slf4j
public class JwtConfig {

    @Resource(name = "keyProp")
    private KeyProperties keyProperties;

    @Bean("keyProp")
    public KeyProperties keyProperties()
    {
        // 这是使用java生成的证书来生成公钥私钥的配置类
        return new KeyProperties();
    }

    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter)
    {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    /**
     * JWT令牌转换器
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter()
    {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenEnhancer();
        // 获取公钥私钥信息
        KeyPair keyPair = new KeyStoreKeyFactory(
                keyProperties.getKeyStore().getLocation(),
                keyProperties.getKeyStore().getSecret().toCharArray()
        ).getKeyPair(keyProperties.getKeyStore().getAlias(),
                keyProperties.getKeyStore().getPassword().toCharArray());
        // RSA 私钥加密
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        tokenConverter.setSigner(new RsaSigner(privateKey));
        return tokenConverter;
    }

    /**
     * JWT令牌增强,继承JwtAccessTokenConverter
     * 将业务所需的额外信息放入令牌中,这样下游微服务就能解析令牌获取
     */
    public static class JwtAccessTokenEnhancer extends JwtAccessTokenConverter {
        /**
         * 重写enhance方法,在其中扩展
         */
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
            log.info("accessToken=={}", accessToken);
            Object principal = authentication.getUserAuthentication().getPrincipal();
            if (principal instanceof LoginUser)
            {
                LoginUser loginUser = (LoginUser) principal;
                System.out.println(loginUser.toString());
                // 设置用户信息
                LinkedHashMap<String, Object> info = new LinkedHashMap<>();
//                info.put(SysConstant.USER_ID, loginUser.getUserId());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
            }
            return super.enhance(accessToken, authentication);
        }
    }
}

授权处理器配置SmsCodeSecurityConfig

@Component
public class SmsCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private RedisCache redisCache;

    @Autowired
    public void setRedisCache(RedisCache redisCache) {
        this.redisCache = redisCache;
    }

    private RSAUtils rsaUtils;

    @Autowired
    public void setRsaUtils(RSAUtils rsaUtils) {
        this.rsaUtils = rsaUtils;
    }

    private PasswordEncoder passwordEncoder;

    @Autowired
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    private SmsCodeUserDetailService smsCodeUserDetailService;

    @Autowired
    public void setSmsCodeUserDetailService(SmsCodeUserDetailService smsCodeUserDetailService) {
        this.smsCodeUserDetailService = smsCodeUserDetailService;
    }

    @Override
    public void configure(HttpSecurity builder) {

        MobilePasswordAuthenticationProvider mobilePasswordAuthenticationProvider = new MobilePasswordAuthenticationProvider(smsCodeUserDetailService, passwordEncoder, redisCache, rsaUtils);
        // 构造授权处理器并配置到Spring Security中
 builder.authenticationProvider(mobilePasswordAuthenticationProvider);
    }
}