(七)springcloud Oauth2授权-Spring Cloud Oauth2

spring-security-oauth2

客户端四种授权模式:

  • 授权码模式(authorization code):第三方应用先申请一个授权码,然后再用该码获取令牌。
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

授权码模式:

1、第三方应用向授权端点URI

GET https://b.com/oauth/authorize?
  response_type=code&           // 表示授权码模式
  client_id=CLIENT_ID&              // 表示客户端id
  redirect_uri=CALLBACK_URL&            // 授权后跳转的url
  scope=read&                           // 要求的授权范围
  state=state                           // 推荐的。一个不透明的值用于维护请求和回调之间的状态。授权服务器在将用户代理重定向会客户端的时候会带上该参数。

2、返回授权码

https://a.com/callback?code=AUTHORIZATION_CODE

3、POST 请求令牌,向令牌端点发出请求

POST https://b.com/oauth/token?
 client_id=CLIENT_ID&
 client_secret=CLIENT_SECRET&
 grant_type=authorization_code&
 code=AUTHORIZATION_CODE&
 redirect_uri=CALLBACK_URL

4、验证通过后返回令牌

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101,
  "info":{...}
}

简化模式:

RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)

https://b.com/oauth/authorize?
  response_type=token&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

用户认证后,直接返回令牌

https://a.com/callback#token=ACCESS_TOKEN

密码模式:

如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。

1、客户端直接获取用户在资源服务器的账号密码,然后向认证服务器获取令牌

https://oauth.b.com/token?
  grant_type=password&
  username=USERNAME&
  password=PASSWORD&
  client_id=CLIENT_ID
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

客户端模式:

用于没有前端的命令行应用,即在命令行下请求令牌。

https://oauth.b.com/token?
  grant_type=client_credentials&
  client_id=CLIENT_ID&
  client_secret=CLIENT_SECRET

一搬 client_id 和 client_secret 加密作为请求头传输

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

授权服务器发放令牌:

  • access_token:必须的。
  • token_type:必须的。比如:"bearer","mac"等等
  • expires_in:推荐的。
  • refresh_token:可选的。
  • scope:可选的。

media type是application/json,参数被序列化成JSON对象。

授权服务器必须包含"Cache-Control"HTTP头,并且值必须是"no-store"。

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

刷新token:

  • grant_type:必须的。值必须是"refresh_token"。
  • refresh_token:必须的。
  • scope:可选的。
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

Spring-Cloud-Oauth2流程

获取token:

1、if (!(principal instanceof Authentication)):校验用户的身份信息(身份校验由spring-security (filter,authenticationManager, provider...完成)

2、getClientDetailsService().loadClientByClientId(clientId):通过clientId获取客户端信息

3、创建TokenRequest

4、校验用户scope this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient)

5、判断用户授权类型

6、TokenGranter:根据用户授权类型,创建access_token和refresh_token,默认实现为CompositeTokenGranter

  • TokenEndpoint
@RequestMapping(
    value = {"/oauth/token"},
    method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
    if (!(principal instanceof Authentication)) {
        throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
    } else {
        String clientId = this.getClientId(principal);
        ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
        TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
        if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
            throw new InvalidClientException("Given client ID does not match authenticated client");
        } else {
            if (authenticatedClient != null) {
                this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
            }

            if (!StringUtils.hasText(tokenRequest.getGrantType())) {
                throw new InvalidRequestException("Missing grant type");
            } else if (tokenRequest.getGrantType().equals("implicit")) {
                throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
            } else {
                if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
                    this.logger.debug("Clearing scope of incoming token request");
                    tokenRequest.setScope(Collections.emptySet());
                }

                if (this.isRefreshTokenRequest(parameters)) {
                    tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
                }

                OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
                if (token == null) {
                    throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
                } else {
                    return this.getResponse(token);
                }
            }
        }
    }
}

private boolean isRefreshTokenRequest(Map<String, String> parameters) {
    return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
}

private boolean isAuthCodeRequest(Map<String, String> parameters) {
    return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
}
  • AbstractTokenGranter
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);

}

// 委托给 tokenServices 创建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);
}
  • TokenServices:默认为DefaultTokenServices

1、从store中获取token(key为client_id, username, scope的map进行md5拼上前缀),未查找则创建token(uuid), 存入store

@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;

}
  • TokenStore:对Token的CRUD,解析

1、InMemoryTokenStore

2、JwtTokenStore

3、JdbcTokenStore

4、JwkTokenStore

5、RedisTokenStore

转载于:https://www.cnblogs.com/zuier/p/10794917.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值