OAuth2的resource_id配置与验证(含源码分析)

版权声明:本文为博主ExcelMann的原创文章,未经博主允许不得转载。
作者:ExcelMann,转载需注明。

OAuth2的resource_id配置与验证

一、resource_id的作用

  • 资源服务器端:

每一个服务请求到资源服务器,都可以在资源服务器为当前的ResourceServer资源服务(即一个微服务实例)设置一个resource_id。

  • 认证服务器端:

OAuth2的认证服务器,在给client第三方客户端授权的时候,可以设置这个client可以访问哪一些ResourceServer资源服务(即微服务),如果没设置,就是对所有的Resource Server都有访问权限。

二、资源服务器设置ResourceId

在每个资源服务上设置resourceId,该resourceId将作为该服务资源的唯一标识。(若同一个微服务资源部署多份,那么多个实例将共享一个resourceId)

本文的资源服务器在common服务模块,其他的资源服务(如user服务模块等)都依赖common模块进行资源的保护。因此,当任意一个请求到某一个资源服务时,就会读取当前资源服务的spring.application.name作为当前服务的唯一标识(即resourceId)。

public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Value("${spring.application.name}")
    private String applicationName;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore())
                .resourceId(applicationName)	// 配置resource_id
            	...;
    }
}

三、认证服务器配置ResourceIds

在认证服务器为客户端client配置ResourceIds的目的是:限制某个client可以访问的资源服务

配置如下:

@EnableAuthorizationServer // 开启授权服务器的功能
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端存储到db,替代默认的内存模式
        clients.withClientDetails(myJdbcClientDetailsService());
    }

    @Bean
    public MyJdbcClientDetailsService myJdbcClientDetailsService(){
        // 这个是我自定义的继承JdbcClientDetailsService的一个service
        MyJdbcClientDetailsService myJdbcClientDetailsService = new MyJdbcClientDetailsService(dataSource);
        myJdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return myJdbcClientDetailsService;
    }
}

另外,还需要创建一个数据库表oauth_client_details进行配置的持久化存储,以及动态的配置。

在这里插入图片描述

四、ResourceId的验证流程

首先应该知道一个大致的流程:

  • 用户进入认证服务器,认证服务器最后会将当前用户的客户端ID以及客户端的其他信息(包括ResourceIds),封装到最终返回的access_token中;
  • 用户携带access_token,访问某个资源服务的时候,会经过资源服务器,并且当前访问的资源服务会绑定资源服务器获取唯一标识resourceId;
  • 此时,资源服务器解析用户携带的access_token中的client_id和其他信息(包括ResourceIds,其标识当前客户端能访问什么资源),并判断resourceIds是否包含有当前的资源服务resourceId。
    • 若有的话,则说明有访问权限;
    • 若不包含的话,说明当前客户端不能访问当前的资源服务;

细节源码分析:

  1. 资源服务器的@EnableResourceServer注解,引入ResourceServerConfiguration

  2. ResourceServerConfiguration类的configure(HttpSercurity http)方法中,创建引入了ResourceServerSecurityConfigurer资源服务安全配置器对象,并进行一些配置;

  3. ResourceServerSecurityConfigurer类中,在configure(HttpSecurity http)方法中,创建了一个OAuth2AuthenticationProcessingFilter过滤器,并且通过addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)方法,将该过滤器添加到FilterChain中,过滤所有的资源请求;

    public final class ResourceServerSecurityConfigurer extends
    		SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);
            // 创建OAuth2AuthenticationProcessingFilter过滤器
            resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
            resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
            resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
            if (eventPublisher != null) {
                resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);
            }
            if (tokenExtractor != null) {
                resourcesServerFilter.setTokenExtractor(tokenExtractor);
            }
            if (authenticationDetailsSource != null) {
                resourcesServerFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
            }
            resourcesServerFilter = postProcess(resourcesServerFilter);
            resourcesServerFilter.setStateless(stateless);
    
            // @formatter:off
            http
                .authorizeRequests().expressionHandler(expressionHandler)
                .and()
                // 将创建的过滤器添加到FilterChain中
                .addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler)
                .authenticationEntryPoint(authenticationEntryPoint);
            // @formatter:on
    	}
    }
    
  4. OAuth2AuthenticationProcessingFilterdoFilter()方法中,调用OAuth2AuthenticationManagerauthenticate()方法来验证token;

  5. authenticate()方法中,首先通过tokenServices.loadAuthentication(token)方法,调用DefaultTokenServicesloadAuthentication(token)方法,从tokenStore中读取token串对应的token;接着,从得到的token中读取出resourceIds,然后进行resourceIds是否包含当前服务资源的resourceId的判断;

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (authentication == null) {
            throw new InvalidTokenException("Invalid token (token not found)");
        }
        String token = (String) authentication.getPrincipal();
        OAuth2Authentication auth = tokenServices.loadAuthentication(token);
        if (auth == null) {
            throw new InvalidTokenException("Invalid token: " + token);
        }
    	
        // 从token中取出resourceIds,再判断是否包含当前的resourceId
        Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
        if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
            throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
        }
    
        checkClientDetails(auth);
    
        if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
            OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
            // Guard against a cached copy of the same details
            if (!details.equals(auth.getDetails())) {
                // Preserve the authentication details from the one loaded by token services
                details.setDecodedDetails(auth.getDetails());
            }
        }
        auth.setDetails(authentication.getDetails());
        auth.setAuthenticated(true);
        return auth;
    }
    

    其中当前服务的resourceId是在上述的资源服务器中配置的:

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore())
            .resourceId(applicationName)
            ...;
    }
    

    在这里配置之后,ResourceServerSecurityConfigurer的resourceId成员变量就是我们设置的值。接着,在ResourceServerSecurityConfigureroauthAuthenticationManager(HttpSecurity http)方法中,将resourceId设置为OauthAuthenticationManager的成员变量,从而将resourceId存储到OAuth的认证管理器中,因此上述的authenticate()方法处才能获取到。

借鉴于以下文章(若有侵权,请私信联系,谢谢~):

1.字母哥博客: https://www.cnblogs.com/zimug/p/13389348.html

你可能感兴趣的:(oauth2,Spring,Security,resource_id,java)