Spring Security OAuth2 专题

1.概述

首先了解一下Oauth2,什么是Oauth2 ?

Oauth2是一种协议规范,他的实现有很多种,Spring Security Oauth2 是对他的一种实现。

Oauth2的认证方式有授权码模式,简单模式,密码模式,客户端模式等等,还可以自己去实现其他模式。

可以通过这个链接去了解更多关于Oauth的概念:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

在这里主要讲授权服务,授权服务主要有四大核心功能:

  • 客户端授权关系管理
  • 接收用户或客户端登录请求,生成accessToken
  • token存储
  • token校验

2.授权服务配置

授权服务的几个关键类

  • @EnableAuthorizationServer

    启动授权服务,并引入AuthorizationServerEndpointsConfigurationAuthorizationServerSecurityConfiguration两个配置类

  • AuthorizationServerEndpointsConfiguration/AuthorizationServerEndpointsConfigurer

    授权服务Ebdpoints配置类

  • AuthorizationServerSecurityConfiguration/AuthorizationServerSecurityConfigurer

    授权服务访问授权配置类

  • AuthorizationServerConfigurerAdapter(AuthorizationServerConfigurer)

    授权服务配置适配器,开发人员可以定义实现类来完成对授权服务的配置

    开发定义的配置列表:

    1. ClientDetailsService:ClientDetails信息加载实现类。

    2. TokenStore:token管理服务。

    3. TokenEnhancer:token信息的额外信息处理。

    4. PasswordEncoder: clientDetail 信息里的client_secret字段加解密器。

  • ClientDetailsServiceConfigurer

    客户端配置类

2.1.AuthorizationServerConfigurerAdapter (授权服务配置适配器)

  1. AuthorizationServerConfigurerAdapter是 AuthorizationServer配置的适配类。

  2. AuthorizationServerConfigurerAdapter分别有三个方法:

    //定义客户端详细信息服务的配置器。可以初始化客户端详细信息
    public void configure(ClientDetailsServiceConfigurer clients) 
    
    //定义令牌端点上的安全约束。
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
    
    //定义授权和令牌端点以及令牌服务。
    public void configure(AuthorizationServerSecurityConfigurer security)
    
  3. OAuth2AuthorizationServerConfigurationAuthorizationServerConfigurerAdapter的系统默认实现,开发人员可以参考这个类的实现。

  4. 开发如何自定义实现如下:

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
       /**
         * 客户端信息访问配置
         * 配置clientDetails授权方式,
         *
         * @param clients
         * @throws Exception
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        }
    
        /**
         * 访问端点配置
         * @param endpoints
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {  
        }
    
        /**
         * 授权服务安全配置
         * @param security
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) {
        }
    }
    

    下面再详细讲解三个配置。

  5. 类关系图

image20211014102846237.png

2.2.ClientDetailsServiceConfigurer (配置客户端详细信息)

​ 这个配置主要是注入ClientDetailsService实例,其它地方通过ClientDetailsServiceConfigurer调用开发配置的ClientDetailsService。系统提供两个ClientDetailsService实现类:JdbcClientDetailsService、InMemoryClientDetailsService。

    /**
     * 客户端信息访问配置
     * 配置clientDetails授权方式,
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 1.这里用于测试,客户端信息从内存中获取
        clients.inMemory()
                .withClient("client") // client_id
                .secret("secret")// client_secret,客户端密码
                .authorizedGrantTypes("password")// 该client允许的授权类型
                .scopes("app")      // 允许的授权范围
                .autoApprove(true);//登录后绕过批准询问

        // 2.客户端信息从数据库获取
        // clients.jdbc(dataSource);

        // 3.这是自己实现ClientDetailsService
        // clients.withClientDetails(clientDetailsService);

    }

2.3.AuthorizationServerEndpointsConfigurer (访问端点配置)

AuthorizationServerEndpointsConfigurer是一个转载器,将Endpoints所有相关的配置类:(AuthorizationServer、TokenServices、TokenStore、ClientDetailsService、UserDetailsService)

配置实例:

    /**
     * 访问端点配置
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager) // 认证管理器
                .tokenStore(new InMemoryTokenStore())          // token存储、系统实现了 内存存储、Redis存储、数据库存储、JWTtoken
                .userDetailsService(userDetailsService)       //用户信息获取方式,密码模式必须要
				.tokenEnhancer(new JwtAccessTokenConverter()) //令牌增强,可以设置生成不同类型的token,如jwt
            	.tokenGranter(tokenGranter(endpoints));         //配置grant_type模式,如果不配置则默认使用密码模式、简化模式、验证码模式以及刷新token模式,如果配置了只使用配置中,默认配置失效
                
    }

2.4.AuthorizationServerSecurityConfigurer (授权服务安全配置)

AuthorizationServerSecurityConfigurer继承SecurityConfigurerAdapter.也就是一个 Spring Security安全配置提供给AuthorizationServer去配置AuthorizationServer的端点(/oauth/**)的安全访问规则、过滤器Filter。

/**
 * 授权服务安全配置
 * 配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
 *  默认过滤器:BasicAuthenticationFilter
 *  1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
 *  2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
 * 对以下的几个端点进行权限配置:
 * /oauth/authorize:授权端点
 * /oauth/token:令牌端点
 * /oauth/confirm_access:用户确认授权提交端点
 * /oauth/error:授权服务错误信息端点
 * /oauth/check_token:用于资源服务访问的令牌解析端点
 * /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
 *
 * @param security
 */
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
    security.allowFormAuthenticationForClients()
            .passwordEncoder(new BCryptPasswordEncoder())
            .tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()");
}

2.5.完整授权配置示例

/**
 * @author lizhichao
 * @description 授权服务配置
 * @date 2021/10/13 17:03
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Autowired
    @Qualifier("UserDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    /**
     * 客户端信息访问配置
     * 配置clientDetails授权方式,
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 1.这里用于测试,客户端信息从内存中获取
        clients.inMemory()
                .withClient("client") // client_id
                .secret("$2a$10$xox4JztwJISO9eWlj85.YO5ugx3RjVQYXXQ.TyACcjseiyyPeb8Q6")// client_secret
                .authorizedGrantTypes("password","authorization_code","implicit", "refresh_token")// 该client允许的授权类型
                .scopes("app")      // 允许的授权范围
                .autoApprove(true) //登录后绕过批准询问
                .redirectUris("https://www.baidu.com");


        // 2.客户端信息从数据库获取
        // clients.jdbc(dataSource);

        // 3.这是自己实现ClientDetailsService
        // clients.withClientDetails(clientDetailsService);

    }


    /**
     * 访问端点配置
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

        endpoints//.authenticationManager(authenticationManager)  // 认证管理器
                .tokenStore(new InMemoryTokenStore())           // token存储
                .userDetailsService(userDetailsService)       //用户信息获取方式,密码模式必须要
                .tokenEnhancer(new JwtAccessTokenConverter())   //令牌增强,可以设置生成不同类型的token,如jwt
                .tokenGranter(tokenGranter(endpoints));         //配置grant_type模式,用户可以进行扩展,如果不配置则默认使用密码模式、简化模式、验证码模式以及刷新token模式,如果配置了只使用配置中,默认配置失效

    }

    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
        List<TokenGranter> list = new ArrayList<>();
        //这里配置密码模式、刷新token模式、授权码模式、简化模式
        list.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        list.add(new RefreshTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        list.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(),endpoints.getAuthorizationCodeServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        list.add(new ImplicitTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory()));
        return new CompositeTokenGranter(list);
    }

    /**
     * 授权服务安全配置
     * 配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
     *  默认过滤器:BasicAuthenticationFilter
     *  1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
     *  2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
     * 对以下的几个端点进行权限配置:
     * /oauth/authorize:授权端点
     * /oauth/token:令牌端点
     * /oauth/confirm_access:用户确认授权提交端点
     * /oauth/error:授权服务错误信息端点
     * /oauth/check_token:用于资源服务访问的令牌解析端点
     * /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
     *
     * @param security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients()  //允许表单提交
                .passwordEncoder(new BCryptPasswordEncoder()) //密码加密方式
                .tokenKeyAccess("permitAll()")                // 令牌访问权限
                .checkTokenAccess("isAuthenticated()");      //token校验访问权限

    }

}

2.6.安全配置(WebSecurityConfig)

安全配置主要是配置请求访问权限、定义认证管理器、密码加密配置。

@Order(2)
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("UserDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .csrf().disable();


    }

    /**
     * 身份验证管理器配置
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }
	
   /**
    * 密码编码器
    *
    */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


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

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }
}

3.资源服务配置

这里的工作是将鉴权管理器AuthorizationManager配置到资源服务器、请求白名单放行、无权访问和无效token的自定义异常响应。配置类基本上都是约定俗成那一套,核心功能和注意的细节点通过注释说明。

3.1.AuthorizationManager (鉴权管理器)

/**
 *
 *鉴权管理器
 */
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {


    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono , AuthorizationContext authorizationContext) {
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();


        // 1.跨域的预请求直接放行
        if (request.getMethod() == HttpMethod.OPTIONS){
            return Mono.just(new AuthorizationDecision(true));
        }

        // 2.token 为空直接拒绝
        String token = request.getHeaders().getFirst(AuthConstants.JWT_TOKEN_HEADER);
        if (token == null || token.length() == 0){
            return Mono.just(new AuthorizationDecision(false));
        }
        
        // 3.这里可以做权限拦截,比如根据不同的权限,来拦截路由,我这里就没做啦!
        return  mono.filter(Authentication::isAuthenticated)
                .map(e->new AuthorizationDecision(true))
                .defaultIfEmpty(new AuthorizationDecision(false));
    }
}

3.2.资源配置

这里配置用什么oauth2资源服务

@Component
@EnableWebFluxSecurity
public class ResourceServerConfig {

    @Autowired
    private AuthorizationManager authorizationManager;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        //资源服务配置服务  可以选择 jwt  和 opaqueToken
        http.oauth2ResourceServer().opaqueToken();

        //授权配置
        http.authorizeExchange()
                .anyExchange().access(authorizationManager)
                .and()
                .csrf().disable();

        return http.build();
    }

}

4.完整代码示例

这里做下完整的代码示例、其模块包含 授权服务 、网关。

代码见GitHub :https://github.com/769306079/gateway-oauth-demo

Q.E.D.