当前位置:   article > 正文

Spring Security Oauth2认证源码解析_spring security oauth2源码解析

spring security oauth2源码解析

        本文开始从源码的层面,讲解一些Spring Security Oauth2的认证流程。本文较长,适合在空余时间段观看。且涉及了较多的源码,非关键性代码以…代替。

准备工作

        首先开启debug信息:

  1. logging:
  2.   level:
  3.     org.springframework: DEBUG

        可以完整的看到内部的运转流程。

        client模式稍微简单一些,使用client模式获取token 
http://localhost:8080/oauth/token?client_id=client_1&client_secret=123456&scope=select&grant_type=client_credentials

        由于debug信息太多了,我简单按照顺序列了一下关键的几个类:

  1. ClientCredentialsTokenEndpointFilter
  2. DaoAuthenticationProvider
  3. TokenEndpoint
  4. TokenGranter


@EnableAuthorizationServer

        上一篇博客中我们尝试使用了password模式和client模式,有一个比较关键的endpoint:/oauth/token。从这个入口开始分析,spring security oauth2内部是如何生成token的。获取token,与第一篇文章中的两个重要概念之一有关,也就是AuthorizationServer与ResourceServer中的AuthorizationServer。

        在之前的配置中

  1. @Configuration
  2. @EnableAuthorizationServer
  3. protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {}


        出现了AuthorizationServerConfigurerAdapter 关键类,他关联了三个重要的配置类,分别是

  1. public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
  2.     @Override
  3.     public void configure(AuthorizationServerSecurityConfigurer security <1>) throws Exception {
  4.     }
  5.     @Override
  6.     public void configure(ClientDetailsServiceConfigurer clients <2>) throws Exception {
  7.     }
  8.     @Override
  9.     public void configure(AuthorizationServerEndpointsConfigurer endpoints <3>) throws Exception {
  10.     }
  11. }

  1. 配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
  2. 配置OAuth2的客户端相关信息
  3.  配置AuthorizationServerEndpointsConfigurer众多相关类,包括配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory

        我们逐步分析其中关键的类

客户端身份认证核心过滤器ClientCredentialsTokenEndpointFilter(掌握)

        截取关键的代码,可以分析出大概的流程 
在请求到达 /oauth/token 之前经过了 ClientCredentialsTokenEndpointFilter 这个过滤器,关键方法如下

  1. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
  2.         throws AuthenticationException, IOException, ServletException {
  3.     ...
  4.     String clientId = request.getParameter("client_id");
  5.     String clientSecret = request.getParameter("client_secret");
  6.     ...
  7.     clientId = clientId.trim();
  8.     UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
  9.             clientSecret);
  10.     return this.getAuthenticationManager().authenticate(authRequest);
  11. }


顶级身份管理者AuthenticationManager(掌握)


        用来从请求中获取client_id,client_secret,组装成一个UsernamePasswordAuthenticationToken作为身份标识,使用容器中的顶级身份管理器AuthenticationManager去进行身份认证(AuthenticationManager的实现类一般是ProviderManager。而ProviderManager内部维护了一个List,真正的身份认证是由一系列AuthenticationProvider去完成。而AuthenticationProvider的常用实现类则是DaoAuthenticationProvider,DaoAuthenticationProvider内部又聚合了一个UserDetailsService接口,UserDetailsService才是获取用户详细信息的最终接口,而我们上一篇文章中在内存中配置用户,就是使用了UserDetailsService的一个实现类InMemoryUserDetailsManager)。UML类图可以大概理解下这些类的关系,省略了授权部分。

  图1 认证相关UML类图

        可能机智的读者会发现一个问题,我前面一篇文章已经提到了client模式是不存在“用户”的概念的,那么这里的身份认证是在认证什么呢?debug可以发现UserDetailsService的实现被适配成了ClientDetailsUserDetailsService,这个设计是将client客户端的信息(client_id,client_secret)适配成用户的信息(username,password),这样我们的认证流程就不需要修改了。
        经过ClientCredentialsTokenEndpointFilter之后,身份信息已经得到了AuthenticationManager的验证。接着便到达了:TokenEndpoint

Token处理端点TokenEndpoint(掌握)


        前面的两个 ClientCredentialsTokenEndpointFilter AuthenticationManager 可以理解为一些前置校验,和身份封装,而这个类一看名字就知道和我们的token是密切相关的。

  1. @FrameworkEndpoint
  2. public class TokenEndpoint extends AbstractEndpoint {
  3.     @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
  4.     public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
  5.     Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
  6.          ...
  7.         String clientId = getClientId(principal);
  8.         ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);//<1>
  9.         ...
  10.         TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);//<2>
  11.         ...
  12.         OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);//<3>
  13.         ...
  14.         return getResponse(token);
  15.     }
  16.     private TokenGranter tokenGranter;
  17. }

  1. 加载客户端信息
  2.  结合请求信息,创建TokenRequest
  3. 将TokenRequest传递给TokenGranter颁发token

        省略了一些校验代码之后,真正的 /oauth/token 端点暴露在了我们眼前,其中方法参数中的Principal经过之前的过滤器,已经被填充了相关的信息,而方法的内部则是依赖了一个TokenGranter 来颁发token。其中 OAuth2AccessToken 的实现类 DefaultOAuth2AccessToken 就是最终在控制台得到的token序列化之前的原始类:

  1. public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken {
  2.     private static final long serialVersionUID = 914967629530462926L;
  3.     private String value;
  4.     private Date expiration;
  5.     private String tokenType = BEARER_TYPE.toLowerCase();
  6.     private OAuth2RefreshToken refreshToken;
  7.     private Set<String> scope;
  8.     private Map<String, Object> additionalInformation = Collections.emptyMap();
  9.     //getter,setter
  10. }
  11. @org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class)
  12. @org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class)
  13. @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)
  14. @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)
  15. public interface OAuth2AccessToken {
  16.     public static String BEARER_TYPE = "Bearer";
  17.     public static String OAUTH2_TYPE = "OAuth2";
  18.     /**
  19.      * The access token issued by the authorization server. This value is REQUIRED.
  20.      */
  21.     public static String ACCESS_TOKEN = "access_token";
  22.     /**
  23.      * The type of the token issued as described in <a
  24.      * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-7.1">Section 7.1</a>. Value is case insensitive.
  25.      * This value is REQUIRED.
  26.      */
  27.     public static String TOKEN_TYPE = "token_type";
  28.     /**
  29.      * The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will
  30.      * expire in one hour from the time the response was generated. This value is OPTIONAL.
  31.      */
  32.     public static String EXPIRES_IN = "expires_in";
  33.     /**
  34.      * The refresh token which can be used to obtain new access tokens using the same authorization grant as described
  35.      * in <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-6">Section 6</a>. This value is OPTIONAL.
  36.      */
  37.     public static String REFRESH_TOKEN = "refresh_token";
  38.     /**
  39.      * The scope of the access token as described by <a
  40.      * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3">Section 3.3</a>
  41.      */
  42.     public static String SCOPE = "scope";
  43.     ...
  44. }


        一个典型的样例token响应,如下所示,就是上述类序列化后的结果:

  1.     "access_token":"950a7cc9-5a8a-42c9-a693-40e817b1a4b0"
  2.     "token_type":"bearer"
  3.     "refresh_token":"773a0fcd-6023-45f8-8848-e141296cb3cb"
  4.     "expires_in":27036
  5.     "scope":"select" 
  6. }


TokenGranter(掌握)


        先从UML类图对TokenGranter接口的设计有一个宏观的认识


图2 TokenGranter相关UML类图

        TokenGranter的设计思路是使用CompositeTokenGranter管理一个List列表,每一种grantType对应一个具体的真正授权者,在debug过程中可以发现 CompositeTokenGranter 内部就是在循环调用五种 TokenGranter 实现类的grant方法,而granter内部则是通过grantType来区分是否是各自的授权类型。

  1. public class CompositeTokenGranter implements TokenGranter {
  2.     private final List<TokenGranter> tokenGranters;
  3.     public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
  4.         this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
  5.     }
  6.     public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
  7.         for (TokenGranter granter : tokenGranters) {
  8.             OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
  9.             if (grant!=null) {
  10.                 return grant;
  11.             }
  12.         }
  13.         return null;
  14.     }
  15. }


        五种类型分别是:

ResourceOwnerPasswordTokenGranter ==> password密码模式
AuthorizationCodeTokenGranter ==> authorization_code授权码模式
ClientCredentialsTokenGranter ==> client_credentials客户端模式
ImplicitTokenGranter ==> implicit简化模式
RefreshTokenGranter ==>refresh_token 刷新token专用
以客户端模式为例,思考如何产生token的,则需要继续研究5种授权者的抽象类:AbstractTokenGranter

  1. public abstract class AbstractTokenGranter implements TokenGranter {
  2.     protected final Log logger = LogFactory.getLog(getClass());
  3.     //与token相关的service,重点
  4.     private final AuthorizationServerTokenServices tokenServices;
  5.     //与clientDetails相关的service,重点
  6.     private final ClientDetailsService clientDetailsService;
  7.     //创建oauth2Request的工厂,重点
  8.     private final OAuth2RequestFactory requestFactory;
  9.     private final String grantType;
  10.     ...
  11.     public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
  12.         ...
  13.         String clientId = tokenRequest.getClientId();
  14.         ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
  15.         validateGrantType(grantType, client);
  16.         logger.debug("Getting access token for: " + clientId);
  17.         return getAccessToken(client, tokenRequest);
  18.     }
  19.     protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
  20.         return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
  21.     }
  22.     protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
  23.         OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
  24.         return new OAuth2Authentication(storedOAuth2Request, null);
  25.     }
  26.     ...
  27. }

        回过头去看TokenEndpoint中,正是调用了这里的三个重要的类变量的相关方法。由于篇幅限制,不能延展太多,不然没完没了,所以重点分析下 AuthorizationServerTokenServices 是何方神圣。

  1. AuthorizationServerTokenServices(了解)
  2. AuthorizationServer端的token操作service,接口设计如下:
  3. public interface AuthorizationServerTokenServices {
  4.     //创建token
  5.     OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
  6.     //刷新token
  7.     OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
  8.             throws AuthenticationException;
  9.     //获取token
  10.     OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
  11. }


        在默认的实现类DefaultTokenServices中,可以看到token是如何产生的,并且了解了框架对token进行哪些信息的关联。

  1. @Transactional
  2. public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
  3.     OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
  4.     OAuth2RefreshToken refreshToken = null;
  5.     if (existingAccessToken != null) {
  6.         if (existingAccessToken.isExpired()) {
  7.             if (existingAccessToken.getRefreshToken() != null) {
  8.                 refreshToken = existingAccessToken.getRefreshToken();
  9.                 // The token store could remove the refresh token when the
  10.                 // access token is removed, but we want to
  11.                 // be sure...
  12.                 tokenStore.removeRefreshToken(refreshToken);
  13.             }
  14.             tokenStore.removeAccessToken(existingAccessToken);
  15.         }
  16.         else {
  17.             // Re-store the access token in case the authentication has changed
  18.             tokenStore.storeAccessToken(existingAccessToken, authentication);
  19.             return existingAccessToken;
  20.         }
  21.     }
  22.     // Only create a new refresh token if there wasn't an existing one
  23.     // associated with an expired access token.
  24.     // Clients might be holding existing refresh tokens, so we re-use it in
  25.     // the case that the old access token
  26.     // expired.
  27.     if (refreshToken == null) {
  28.         refreshToken = createRefreshToken(authentication);
  29.     }
  30.     // But the refresh token itself might need to be re-issued if it has
  31.     // expired.
  32.     else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
  33.         ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
  34.         if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
  35.             refreshToken = createRefreshToken(authentication);
  36.         }
  37.     }
  38.     OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
  39.     tokenStore.storeAccessToken(accessToken, authentication);
  40.     // In case it was modified
  41.     refreshToken = accessToken.getRefreshToken();
  42.     if (refreshToken != null) {
  43.         tokenStore.storeRefreshToken(refreshToken, authentication);
  44.     }
  45.     return accessToken;
  46. }


        简单总结一下 AuthorizationServerTokenServices 的作用,他提供了创建token,刷新token,获取token的实现。在创建token时,他会调用tokenStore对产生的token和相关信息存储到对应的实现类中,可以是redis,数据库,内存,jwt

总结


        本篇总结了使用客户端模式获取Token时,spring security oauth2内部的运作流程,重点是在分析 AuthenticationServer 相关的类。其他模式有一定的不同,但抽象功能是固定的,只是具体的实现类会被相应地替换。

        阅读spring的源码,会发现它的设计中出现了非常多的抽象接口,这对我们理清楚内部工作流程产生了不小的困扰,我的方式是可以借助UML类图,先从宏观理清楚作者的设计思路,这会让我们的分析事半功倍。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/83994
推荐阅读
相关标签
  

闽ICP备14008679号