初识Spring Security OAuth2(二)

2. Oauth2认证流程源码分析

2.1客户端认证

从浏览器中输入网址之后,首先会通过Spring的相关过滤器进行拦截,filterchains中的过滤器一层一层执行doFilter,

到ClientCredentialsTokenEndPointFilter,首先会调用其父类AbstractAuthenticationProcessingFilterdoFilter方法。

然后就会进入子类ClientCredentialsTokenEndPointFilter的attemptAuthentication方法。

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)

           throws AuthenticationException, IOException, ServletException {

 

       if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {

           throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" });

       }

 

       String clientId = request.getParameter("client_id");

       String clientSecret = request.getParameter("client_secret");

 

       // If the request is already authenticated we can assume that this

       // filter is not needed

       Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

       if (authentication != null && authentication.isAuthenticated()) {

           return authentication;

       }

 

       if (clientId == null) {

           throw new BadCredentialsException("No client credentials presented");

       }

 

       if (clientSecret == null) {

           clientSecret = "";

       }

 

       clientId = clientId.trim();

       UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,

              clientSecret);

 

       return this.getAuthenticationManager().authenticate(authRequest);

 

    }

 

解析:从request中获取clientId和clientsecret,Authentication authentication = SecurityContextHolder.getContext().getAuthentication();检查当前的request,实质应该为client,是否已经被认证过,如果认证过,直接返回authentication,否则进行下面的认证。将clientId和clientsecret封装成UsernamePasswordAuthenticationToken;使用AuthenticationManagerauthenticate方法去对UsernamePasswordAuthenticationToken进行客户端的认证。

 

AuthenticationManager

使用容器中的顶级身份管理器AuthenticationManager去进行身份认证(AuthenticationManager的实现类一般是ProviderManager。而ProviderManager内部维护了一个List,真正的身份认证是由一系列AuthenticationProvider去完成。而AuthenticationProvider的常用实现类则是DaoAuthenticationProvider

DaoAuthenticationProvider内部又聚合了一个UserDetailsService接口,UserDetailsService才是获取用户详细信息的最终接口,而我们上面的文章中在内存中配置用户,就是使用了UserDetailsService的一个实现类InMemoryUserDetailsManager)。

client模式是没有用户的概念的,运行中会将UserDetailsService适配成ClientDetailsUserDetailsService

UserDetailsService的继承关系如下

AuthenticationManager的认证流程

AuthenticationManager是接口类,因此分析它的实现类 ProviderManager,ProviderManager 的 authenticate() 方法代码如下:

public Authentication authenticate(Authentication authentication)

           throws AuthenticationException {

。。。

。。。

 

       for (AuthenticationProvider provider : getProviders()) {

           。。。

 

           try {

              result = provider.authenticate(authentication);

 

              if (result != null) {

                  copyDetails(authentication, result);

                  break;

              }

           }

       。。。

 

       if (result == null && parent != null) {

           // Allow the parent to try.

           try {

    result = parentResult = parent.authenticate(authentication);

           }

           。。。

    }

 

主要是result = provider.authenticate(authentication);

AuthenticationProvider是个接口,主要分析他的实现类AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider,它调用父类AbstractUserDetailsAuthenticationProvider中的authenticate方法。

public Authentication authenticate(Authentication authentication)

           throws AuthenticationException {

       。。。

 

       // Determine username

       String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"

              : authentication.getName();

 

       boolean cacheWasUsed = true;

       UserDetails user = this.userCache.getUserFromCache(username);

 

       if (user == null) {

           cacheWasUsed = false;

 

           try {

              user = retrieveUser(username,

                     (UsernamePasswordAuthenticationToken) authentication);

           }

           。。。

 

       try {

           preAuthenticationChecks.check(user);

           additionalAuthenticationChecks(user,

                  (UsernamePasswordAuthenticationToken) authentication);

       }

       catch (AuthenticationException exception) {

           if (cacheWasUsed) {

              // There was a problem, so try again after checking

              // we're using latest data (i.e. not from the cache)

              cacheWasUsed = false;

              user = retrieveUser(username,

                     (UsernamePasswordAuthenticationToken) authentication);

              preAuthenticationChecks.check(user);

              additionalAuthenticationChecks(user,

                     (UsernamePasswordAuthenticationToken) authentication);

           }

           else {

              throw exception;

           }

       }

 

       postAuthenticationChecks.check(user);

 

       if (!cacheWasUsed) {

           this.userCache.putUserInCache(user);

       }

 

       Object principalToReturn = user;

 

       if (forcePrincipalAsString) {

           principalToReturn = user.getUsername();

       }

 

       return createSuccessAuthentication(principalToReturn, authentication, user);

    }

 

user = retrieveUser(username,                    (UsernamePasswordAuthenticationToken) authentication);

调用的就是DaoAuthenticationProvider的方法,具体如下

protected final UserDetails retrieveUser(String username,

           UsernamePasswordAuthenticationToken authentication)

           throws AuthenticationException {

       prepareTimingAttackProtection();

       try {

           UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

       。。。

           return loadedUser;

    。。。

 

从数据库中获取user,像mybatis的调用格式

preAuthenticationChecks.check(user);

              additionalAuthenticationChecks(user,

                     (UsernamePasswordAuthenticationToken) authentication);

这两个方法是对获得的user进行验证

其中addtionalAuthenticationChecks代码如下:

protected void additionalAuthenticationChecks(UserDetails userDetails,

           UsernamePasswordAuthenticationToken authentication)

           throws AuthenticationException {

       。。。

 

       String presentedPassword = authentication.getCredentials().toString();

 

       if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {

           。。。

    }

 

使用passwordEncoder的校验方法。这样authenticationManager的认证就结束了。

2.2 token的生成

通过过滤器和认证管理器之后,客户端的前置认证基本完成了,现在分析token的产生

涉及到TokenEndpoint

public class TokenEndpoint extends AbstractEndpoint {

 

    。。。

 

    @RequestMapping(value = "/oauth/token", method=RequestMethod.GET)

    public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam

    Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

       。。。

       return postAccessToken(principal, parameters);

    }

   

    @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)

    public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam

    Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

 

    。。。

 

       String clientId = getClientId(principal);

       ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);---

 

       TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);----

 

       。。。

 

       OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);----

       。。。

 

       return getResponse(token);

 

    }

。。。

}

 

①加载客户端信息

②结合请求信息clientId,scope,grantType,创建TokenRequest

③将TokenRequest传递给TokenGranter颁发token

getTokenGranter().grant(tokenRequest.getGrantType())

TokenGranter来生成AccessToken,具体继承关系如下

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

public class CompositeTokenGranter implements TokenGranter {

 

    。。。

   

    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {

       for (TokenGranter granter : tokenGranters) {

           OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);

           if (grant!=null) {

              return grant;

           }

       }

       return null;

    }

   

    。。。

 

}

 

 

 

 

五种类型分别是

以客户端模式为例,分析如何产生token,从他们的父类AbstractTokenGranter入手。

public abstract class AbstractTokenGranter implements TokenGranter {

。。

 

    private final AuthorizationServerTokenServices tokenServices;

 

    private final ClientDetailsService clientDetailsService;

   

    private final OAuth2RequestFactory requestFactory;

   

    private final String grantType;

 

    。。。

 

    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {

 

       if (!this.grantType.equals(grantType)) {

           return null;

       }

      

       String clientId = tokenRequest.getClientId();

       ClientDetails client = clientDetailsService.loadClientByClientId(clientId);

       validateGrantType(grantType, client);

      

       logger.debug("Getting access token for: " + clientId);

 

       return getAccessToken(client, tokenRequest);

 

    }

grant方法通过tokenRequest获取client和用户的相关信息,通过getAcessToken方法获取token,其中getOAuth2Authentication根据client和tokenRequest获得OAuth2Request,其中包含用户的相关信息。生成token

 

    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {

       return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));

    }

 

    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

       OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);

       return new OAuth2Authentication(storedOAuth2Request, null);

    }

 

    protected void validateGrantType(String grantType, ClientDetails clientDetails) {

       。。。

    }

 

    。。。

 

}

 

重点是tokenServices的createAccessToken方法。

public interface AuthorizationServerTokenServices {

    //创建token

    OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;

    //刷新token

    OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)

            throws AuthenticationException;

    //获取token

    OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

}

它的实现类为

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,

                   ConsumerTokenServices, InitializingBean {

 

。。。

。。。

 

         @Transactional

         public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

 

                   OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);

                   OAuth2RefreshToken refreshToken = null;

                   if (existingAccessToken != null) {

                            if (existingAccessToken.isExpired()) {

                                     if (existingAccessToken.getRefreshToken() != null) {

                                               refreshToken = existingAccessToken.getRefreshToken();

                                               // The token store could remove the refresh token when the

                                               // access token is removed, but we want to

                                               // be sure...

                                               tokenStore.removeRefreshToken(refreshToken);

                                     }

                                     tokenStore.removeAccessToken(existingAccessToken);

                            }

                            else {

                                     // Re-store the access token in case the authentication has changed

                                     tokenStore.storeAccessToken(existingAccessToken, authentication);

                                     return existingAccessToken;

                            }

                   }

 

                   。。。

                   if (refreshToken == null) {

                            refreshToken = createRefreshToken(authentication);

                   }

                   。。。

                   else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {

                            ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;

                            if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {

                                     refreshToken = createRefreshToken(authentication);

                            }

                   }

 

                   OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);

                   tokenStore.storeAccessToken(accessToken, authentication);

                   // In case it was modified

                   refreshToken = accessToken.getRefreshToken();

                   if (refreshToken != null) {

                            tokenStore.storeRefreshToken(refreshToken, authentication);

                   }

                   return accessToken;

 

         }

 

         。。。

 

}

获取当前授权的token作为existingAccessToken,如果当前token不为空过期了,就将该token对应的refrestToken一起删除;

如果没有过期,就继续使用当前的token;如果当前的existingAccessToken是null说明没有token,如果过期token没有一个关联的refreshToken,就只能创建一个refreshToken。

使用新的refreshToken和authentication创建新的accessToken

使用tokenStore将token和authentication一起存储起来。

private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {

       DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());

       int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());

       if (validitySeconds > 0) {

           token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));

       }

       token.setRefreshToken(refreshToken);

       token.setScope(authentication.getOAuth2Request().getScope());

 

       return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;

    }

新的token的生成,使用uuId生成,然后设置refreshTokenscope

上面就是token 生成的大致流程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值