前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring security oauth2的认证流程

Spring security oauth2的认证流程

作者头像
用户1499526
发布2019-08-20 16:42:57
2.8K0
发布2019-08-20 16:42:57
举报
文章被收录于专栏:简单的日记简单的日记

撸了今年阿里、头条和美团的面试,我有一个重要发现.......>>>

Spring Security OAuth2 整理

  1. 隐式授权模式(Implicit Grant)
代码语言:javascript
复制
需要提供的参数
    地址:  
        oauth/token
    请求头参数:
        Authorization=Basic base64.encode(client_id:client_secret) 
    ==如果secret为空,只对client_id进行编码==
     
    POST参数 
     grant_type:password 
     username: 用户名
     password:密码
  1. client_credentials 认证
代码语言:javascript
复制
需要传递的参数
   地址:  
       oauth/token
  Header参数
      client_id:
      client_secret:
      
  Header头添加
     Authorization=Basic base64.encode(client_id:client_secret) 
    ==如果secret为空,只对client_id进行编码==
  
  Post参数
      grant_type: password
      scope: 作用域
  1. password 模式
代码语言:javascript
复制
需要传递的参数
   地址:  
       oauth/token
  Header参数
      client_id:
      client_secret:
      
  Header头添加
     Authorization=Basic base64.encode(client_id:client_secret) 
    ==如果secret为空,只对client_id进行编码==
  
  Post参数
      grant_type: password
      username: 用户名
      password: 密码
      scope: 作用域
  1. 授权码模式
代码语言:javascript
复制
   1.首先请求oauth/authorize 获取code
   
    需要提交的参数
     Header参数
      client_id:
      client_secret:
      
     Header头添加
      Authorization=Basic base64.encode(client_id:client_secret) 
         如果secret为空,只对client_id进行编码
      
    post参数  
      grant_type: password
      username: 用户名
      password: 密码
      scope: 作用域
   
   
   2. 通过code 访问 oauth/token接口,换取回应的accessToken   

Spring Security OAuth2 认证流程

  1. 首先开启@EnableAuthorizationServer 注解

AuthorizationServerConfigurerAdapterm默认方法配置

代码语言:javascript
复制
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
 
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security ) throws Exception {
    // 配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
    }
 
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    //配置OAuth2的客户端相关信息
    }
 
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    //配置AuthorizationServerEndpointsConfigurer众多相关类,包括配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory
    }
  1. 客户端身份认证核心过滤器ClientCredentialsTokenEndpointFilter

核心代码如下

代码语言:javascript
复制
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException {
    ...
    String clientId = request.getParameter("client_id");
    String clientSecret = request.getParameter("client_secret");
 
    ...
    clientId = clientId.trim();
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
            clientSecret);
 
    return this.getAuthenticationManager().authenticate(authRequest);
  1. 顶级身份管理者AuthenticationManager
从请求中获取client_id,client_secret,组装成一个UsernamePasswordAuthenticationToken作为身份标识,使用容器中的顶级身份管理器AuthenticationManager去进行身份认证(AuthenticationManager的实现类一般是ProviderManager。而ProviderManager内部维护了一个List,真正的身份认证是由一系列AuthenticationProvider去完成。而AuthenticationProvider的常用实现类则是DaoAuthenticationProvider,DaoAuthenticationProvider内部又聚合了一个UserDetailsService接口,UserDetailsService才是获取用户详细信息的最终接口
client模式是不存在“用户”的概念的,那么这里的身份认证是在认证什么呢?debug可以发现UserDetailsService的实现被适配成了ClientDetailsUserDetailsService,这个设计是将client客户端的信息(client_id,client_secret)适配成用户的信息(username,password)
经过ClientCredentialsTokenEndpointFilter之后,身份信息已经得到了AuthenticationManager的验证。接着便到达了

TokenEndpoint。

  1. Token处理端点TokenEndpoint

前面的两个ClientCredentialsTokenEndpointFilter和AuthenticationManager可以理解为一些前置校验,和身份封装

代码语言:javascript
复制
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
 
    @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);
//结合请求信息,创建TokenRequest
        ...
        OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
//将TokenRequest传递给TokenGranter颁发token
        ...
        return getResponse(token);
    }
    private TokenGranter tokenGranter;

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

代码语言:javascript
复制
public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken {
 
    private static final long serialVersionUID = 914967629530462926L;
 
    private String value;
 
    private Date expiration;
 
    private String tokenType = BEARER_TYPE.toLowerCase();
 
    private OAuth2RefreshToken refreshToken;
 
    private Set<String> scope;
 
    private Map<String, Object> additionalInformation = Collections.emptyMap();
 
    //getter,setter
}
 
@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class)
@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)
 
public interface OAuth2AccessToken {
 
    public static String BEARER_TYPE = "Bearer";
 
    public static String OAUTH2_TYPE = "OAuth2";
 
    /**
     * The access token issued by the authorization server. This value is REQUIRED.
     */
    public static String ACCESS_TOKEN = "access_token";
 
    /**
     * The type of the token issued as described in <a
     * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-7.1">Section 7.1</a>. Value is case insensitive.
     * This value is REQUIRED.
     */
    public static String TOKEN_TYPE = "token_type";
 
    /**
     * The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will
     * expire in one hour from the time the response was generated. This value is OPTIONAL.
     */
    public static String EXPIRES_IN = "expires_in";
 
    /**
     * The refresh token which can be used to obtain new access tokens using the same authorization grant as described
     * in <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-6">Section 6</a>. This value is OPTIONAL.
     */
    public static String REFRESH_TOKEN = "refresh_token";

    /**
     * The scope of the access token as described by <a
     * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3">Section 3.3</a>
     */
    public static String SCOPE = "scope";

    ...
  1. TokenGranter

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

代码语言:javascript
复制
public class CompositeTokenGranter implements TokenGranter {
 
    private final List<TokenGranter> tokenGranters;
 
    public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
        this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
    }
 
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        for (TokenGranter granter : tokenGranters) {
            OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
            if (grant!=null) {
                return grant;
            }
        }
        return null;
    }
}

五种类型分别是:

  • ResourceOwnerPasswordTokenGranter ==> password密码模式
  • AuthorizationCodeTokenGranter ==> authorization_code授权码模式
  • ClientCredentialsTokenGranter ==> client_credentials客户端模式
  • ImplicitTokenGranter ==> implicit简化模式
  • RefreshTokenGranter ==>refresh_token 刷新token专用

以客户端模式为例,思考如何产生token的,则需要继续研究5种授权者的抽象类:AbstractTokenGranter

代码语言:javascript
复制
public abstract class AbstractTokenGranter implements TokenGranter {
 
    protected final Log logger = LogFactory.getLog(getClass());
 
    //与token相关的service,重点
    private final AuthorizationServerTokenServices tokenServices;
    //与clientDetails相关的service,重点
    private final ClientDetailsService clientDetailsService;
    //创建oauth2Request的工厂,重点
    private final OAuth2RequestFactory requestFactory;
 
    private final String grantType;
    ...
 
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
 
        ...
        String clientId = tokenRequest.getClientId();
        ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        validateGrantType(grantType, client);
 
        logger.debug("Getting access token for: " + clientId);
 
        return getAccessToken(client, tokenRequest);
 
    }
 
    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);
    }
 
    ...
}
  1. AuthorizationServerTokenServices分析重点

AuthorizationServer端的token操作service,接口设计如下:

代码语言:javascript
复制
public interface AuthorizationServerTokenServices {
    //创建token
    OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
    //刷新token
    OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
            throws AuthenticationException;
    //获取token
    OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
 
}

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

代码语言:javascript
复制
@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;
        }
    }
 
    // Only create a new refresh token if there wasn't an existing one
    // associated with an expired access token.
    // Clients might be holding existing refresh tokens, so we re-use it in
    // the case that the old access token
    // expired.
    if (refreshToken == null) {
        refreshToken = createRefreshToken(authentication);
    }
    // But the refresh token itself might need to be re-issued if it has
    // expired.
    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;
 
}

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Security OAuth2 整理
  • Spring Security OAuth2 认证流程
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档