源码分析

没有文档的情况下,只能看代码,来知道系统是怎么设置的。

①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖

1. 核心类

官方文档上给出了一些核心的类,可以按照下面的思路来研究源代码。

  1. Configuration Model
    • OAuth2AuthorizationServerConfigurer
    • OAuth2AuthorizationServerConfiguration
    • ProviderSettings / ProviderContext
  2. Core Components / Domain Model
    • RegisteredClientRepository / RegisteredClient
    • OAuth2AuthorizationService / OAuth2Authorization
    • OAuth2AuthorizationConsentService / OAuth2AuthorizationConsent
    • JwtEncoder
    • OAuth2TokenCustomizer / OAuth2TokenContext
  3. Protocol Endpoints
    • OAuth 2.0 Authorization Endpoint
    • OAuth 2.0 Token Endpoint
    • OAuth 2.0 Token Introspection Endpoint
    • OAuth 2.0 Token Revocation Endpoint
    • OAuth 2.0 Authorization Server Metadata Endpoint
    • JWK Set Endpoint
    • OpenID Connect 1.0 Provider Configuration Endpoint
    • OpenID Connect 1.0 UserInfo Endpoint
    • OpenID Connect 1.0 Client Registration Endpoint
EndpointClassDescription
GET "/oauth2/authorize"OAuth2AuthorizationEndpointFilterOAuth 2.0 Authorization Endpoint
POST "/oauth2/token"OAuth2TokenEndpointFilterobtain Access Token
"/oauth2/introspect"OAuth2TokenIntrospectionEndpointFilter内省地址
"/oauth2/revoke"OAuth2TokenRevocationEndpointFilter撤销 Refresh Token
GET "/.well-known/oauth-authorization-server"OAuth2AuthorizationServerMetadataEndpointFilter获取服务器的 Metadata,可以获取服务器支持的参数列表
GET "/oauth2/jwks"NimbusJwkSetEndpointFilter显示公钥
"/.well-known/openid-configuration"OidcProviderConfigurationEndpointFilter
"/userinfo"OidcUserInfoEndpointFilter
"/connect/register"OidcClientRegistrationEndpointFilter

GET "/oauth2/jwks" 的结果

{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "82b83721-051a-460a-901b-4c209d2c3ab6",
"n": "oZZen-gDdCb5ZlF26ZOTqWfyLG0wKfsQ8UuUyyu9BlE1WlAWIuR8EM46Unonr-m4TZx7CpwPqe0CYwmHCADiFdS_ac7JKuNtLLaVH5friqKPueHq-ZFjnLdxkg2HJBXQSIj8qwopbQU7TSC9c1i4-GNmNjgfQ60jL8mU2b85zZQId3Ir7UT5qnYZCffbfa1ZFXqmnKcin1hLSwPTaXwKDAggBulTzgkzYsZxG0_QhXRbcrOt3FIrxxbqBKtOUxgaLVfeY_EPYJriHeezGbNKHDXr_WNJfNFp4hFXNYEe6tyulYnh9KBcC23dPhQst2FSsJ8gr-X3h46yNeGEaMTimw"
}
]
}

JWS, JWE, JWK, JWA, JWT

JWS:JSON Web Signature,Digital signature/HMAC specification

JWE:JSON Web Encryption,Encryption specification

JWK:JSON Web Key,Public key specification:公钥规范

JWA:JSON Web Algorithms,Algorithms and identifiers specification

JWT:JSON Web Token

1.1 Token 类结构

2. Authorization Server

当出现问题时? 如何跟踪这个程序呢? 建议从 Endpoint 入口开始:

  • 获取 Token,就在 IDEA 中找这个类名OAuth2*Token*Endpoint
    • 最终到到了OAuth2TokenEndpointFilter
  • 获取 code,就在 IDEA 中找这个类名OAuth2*Token*Endpoint
    • 最终到到了OAuth2AuthorizationEndpointFilter

2.1 申请授权 code

① 核心流程

  • GET /oauth2/authorize

实际上根据参数的不同,分成普通 code 与 PKCE 两个分支。这里有两个流程的详细说明

定义了要拦截的 URL,这里情况比较复杂,出了要拦截 Get 请求,还要区分是否是确认页。

doFilterInternal 这是一个主函数,里面包含了完整的逻辑链条

  1. 转换requestAuthentcation
  2. 执行授权操作,并返回结果
  3. 根据结果判断是否登陆过?要是没有跳转到登陆页面
  4. 判断是否要显示确认页?如果是,就跳转到确认页
  5. 调用:sendAuthorizationResponse,返回信息,包含CODE STATE RedirectUri

这里描述一下上面的 2.1 中转换request到Authentcation的逻辑

  1. 进行了参数校验
  2. 将非常规的参数存在additionalParameters,这样就可以自定义参数了
  3. 得到当前登陆用户

生成OAuth2AuthorizationCodeRequestAuthenticationToken对象,这个逻辑比较简单,就不详细描述了。

调用AuthenticationManagerauthenticateAuthenticationManager包含了好多处理的类,其中会找到OAuth2AuthorizationCodeRequestAuthenticationProvider

OAuth2AuthorizationCodeRequestAuthenticationProviderauthenticate。这是一个重点的类,根据ClientID得到registeredClient。然后与用户传入的参数进行核对,核对的逻辑如下;

  1. 检验redirect_uri
  2. 校验AuthorizationGrantType与设置的是否一致
  3. 校验scope
  4. 校验codeChallenge
    1. 如果不为空,就校验codeChallengeMethod是否存在
    2. 如果为空,并且require-proof-key=true,也就是必须有,那么就抛出错误
  5. 判断当前用户是否经过认证?如果没有认证,return isAuthenticated() is false,然后就跳转到登陆页面
  6. 生成OAuth2AuthorizationRequest
  7. 调用authorizationConsentService,找到当前的OAuth2AuthorizationConsent
  8. 判断是否需要权限确认页面 ? 如果需要,就return,同时标记需要确认页面
  9. 调用authorizationCodeGenerator生成code
  10. 生成OAuth2Authorization,并调用authorizationService保存起来,这是需要实例化
  11. 如果redirectUri为空,就找设置中默认的那个
  12. return一个包含CodeOAuth2AuthorizationCodeRequestAuthenticationToken

生成OAuth2AuthenticationContext对象: 是一个储存容器,包含了 Authentication与可选的额外参数

OAuth2AuthorizationCodeGenerator 负责具体生成 Code

OAuth2AuthorizationService,将最终生成OAuth2Authorization保存起来。在获取 Token 时候用。

② 定制点

通过了解源代码,那么有那些地方可以进行定制呢?

例如定制 Login

http.oauth2Login(a->a.)

未完,待续。

2.2 获取 token

① 核心流程

OAuth2TokenEndpointFilter 是整个流程的入口。

  • 主流程很简单:
    1. URl是否匹配
    2. Request转换
    3. 进行authenticate
    4. Response返回

调用RequestMatcher.matchesURl是否匹配

调用DelegatingAuthenticationConverter.convert ,将Request传递过来的内容进行校验与转换。有三种转换情况

OAuth2AuthorizationCodeAuthenticationConverter转换器

  • 1、判断类型是否匹配,不匹配就返回null2、从SecurityContextHolder得到clientPrincipal3、得到code4、得到redirect_uri5、得到其他additionalParameters6、返回OAuth2AuthorizationCodeAuthenticationToken

OAuth2RefreshTokenAuthenticationConverter转换器

OAuth2ClientCredentialsAuthenticationConverter转换器

AuthenticationManager进行授权,这个类要调用具体的子类

OAuth2AuthorizationCodeAuthenticationProvider,这个类负责得到Token

  1. 得到前台传递过来的:authorizationCodeAuthentication
  2. 通过authorizationCodeAuthentication.getPrincipal()得到clientPrincipal。 如果clientPrincipal.isAuthenticated() 就表示成功。
  3. 通过clientPrincipal.getRegisteredClient()得到注册的registeredClient
  4. 根据Code,通过authorizationService.findByToken 方法得到OAuth2Authorization:authorization
  5. 通过authorization.getToken,来得到在请求Code时,系统保存的CodeauthorizationCode
  6. authorization.getAttribute得到authorizationRequest
  7. 做了一个防护处理,如果ClientId不同,或者Code.isInvalidated(),就抛出错误
  8. 做了一个防护处理,如果RedirectUri不同,就抛出错误
  9. 做了一个防护处理,!authorizationCode.isActive()Code失效了,就抛出错误
  10. 构建一个tokenContextBuilder
  11. 构建一个authorizationBuilder
  12. 使用tokenContextBuilder.tokenType,生成tokenContext
  13. 使用tokenGenerator.generate(tokenContext),生成generatedAccessToken
  14. 使用generatedAccessToken,生成accessToken
  15. 如果generatedAccessToken类型是ClaimAccessor,就设置claims属性
  16. ---------------------refreshToken-----------------------
  17. 如果是public client或者registeredClient没有包含refresh_token,就不执行
  18. 如果refreshTokenGenerator不为空,就用refreshTokenGenerator生成refreshToken
  19. 如果不是上面两个,就用生成tokenContext,然后使用tokenGenerator.generate(tokenContext)产生新的generatedRefreshToken
  20. 使用authorizationBuilder.refreshToken(refreshToken) 做一下处理。
  21. ----- ID token -----
  22. 如果Scope包含了openid,就创建一个id_token类型的tokenContext
  23. 然后使用tokenGenerator生成generatedIdToken,这个generatedIdToken类型是Jwt
  24. 然后组装成OidcIdToken形成idToken
  25. 使用authorizationBuilder.token来对idToken进行处理
  26. authorization = authorizationBuilder.build();
  27. OAuth2AuthenticationProviderUtils.invalidate code做废掉,因为只能用一次
  28. authorizationService保存authorization
  29. 如果idToken不等空,就得到additionalParameters
  30. 返回一个OAuth2AccessTokenAuthenticationToken,里面包含;registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters

sendAccessTokenResponse,将得到的Token返回给前台。

3. OAuth2 Client

3.1 第三方登陆

CommonOAuth2Provider类中定义了例如 google github 等常用的第三方登陆。

4. Resource Server

{"alg":"RS256"}

{"sub":"user","scope":"message:read","iss":"https://www.example.com","exp":1651805873}

{"typ":"JWT","alg":"RS256"}

{"sub":"subject","iat":1516239022}