spring-security-oauth2实现OAuth2.0服务

关于OAuth的介绍查看我的另一篇文章OAuth的4种授权方式,spring-security-oauth2是实现OAuth2.0的框架,配置稍微有些繁琐,因此本文记录下大概的思路,加深印象。

OAuth 2.0中主要有Authorization Service授权服务和Resource Service资源服务,他们可以在同一个应用程序中,也可以在两个应用程序中,甚至多个资源服务共享一个授权服务。

spring-security提供了相应的endpoints来管理token的请求,/oauth/authorize端点负责授权服务,/oauth/token端点负责token的请求服务;资源服务中的过滤器OAuth2AuthenticationProcessingFilter 负责校验Token。

下面从总体上介绍授权服务和资源服务的主要配置,详细的配置在githubspring-security-oauth demo上。

授权服务配置


@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    private static final String DEMO_RESOURCE_ID = "openapi";

    @Autowired
    private OrderAuthProperties orderAuthProperties;

    /**
     * 注意这里AuthenticationManager和UserAccountService
     * 是在SecurityConfiguration配置的,把俩个配置类关联了起来
     */
    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    private UserAccountService userAccountService;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private OAuthClientDetailsService oAuthClientDetailsService;

    @Autowired
    private DataSource dataSource;

    /**
     * 配置第三方客户端的信息,可以从内存中加载,也可以从数据库加载(更常用)
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
        clients.withClientDetails(oAuthClientDetailsService);

        // 这里将第三方客户端放到内存里,配置两个客户端,一个用于client认证
//        clients.inMemory()
//                .withClient("client_1")
//                .resourceIds(DEMO_RESOURCE_ID)
//                .authorizedGrantTypes("client_credentials", "refresh_token")
//                .scopes("select")
//                .authorities("client")
//                .secret("123456")
//                .and().withClient("client_2")
//                .secret("123456")
//                .resourceIds(DEMO_RESOURCE_ID)
//                .authorizedGrantTypes("authorization_code", "code", "password", "refresh_token")
//                .scopes("select")
//                .redirectUris("http://www.baidu.com");
    }


    /**
     * 定义token endpoint的安全配置
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer
                .realm("oauth2-resources")
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .passwordEncoder(clientPasswordEncoder())
                .allowFormAuthenticationForClients();
    }

    /**
     * 第三方客户端使用加密方式
     */
    @Bean
    public PasswordEncoder clientPasswordEncoder() {
        // 不做加密处理,明文存储
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 定义授权、token endpoint和token服务,即如何生成token和token存储在哪里(内存/数据库/JWT)
     *
     * <p>
     * 这里最重要的就是DefaultTokenServices,默认是生成随机值作为token
     * <p>
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // 这里设置了两个TokenEnhancer,可改变token值,它会在token生成后/保存前调用
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));

        endpoints.authenticationManager(authenticationManager) // 注入authenticationManager开启密码授权模式
                // 必须要配置userDetailsService,才支持refresh token grant,to ensure that the account is still active
                // 这里和SecurityConfiguration的userAccountService也可以不一样,为什么?
                .userDetailsService(userAccountService)
                .authorizationCodeServices(authorizationCodeServices) // 定义authorizationCodeServices支持auth code grant.
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain);
    }


    /**
     * TokenEnhancer
     *
     * JWT提供的,帮助把OAuth认证信息转为JWT,即access_token,它返回的很多默认字段(jti,ati)都是在这里定义的
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 这里使用的密钥也要定义在资源服务里,以便资源服务也可以校验token,否则认证服务就要提供校验token的接口给资源服务
        converter.setSigningKey(orderAuthProperties.getOauthJwtSecret());
        return converter;
    }

    /**
     * TokenEnhancer
     *
     * 向token里添加自定义信息
     */
    @Bean
    public JwtTokenEnhancer tokenEnhancer() {
        return new JwtTokenEnhancer();
    }


    /**
     * 定义token的存储方式:可以放在redis/数据库(oauth_access_token表)/内存,或者jwt中
     * 这里放在JWT里,根本就不必后端存储token了,这是JWT很大的优势
     *
     * 但是JWT也有缺点,1.不容易撤销授权,所以一般令牌时效性很短,撤销授权可以在刷新时实现,怎么实现?
     * 2.如果要存储的信息很多,令牌会变得很大
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
}

版权声明:本文为java_collect原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。