客户端
什么是客户端?
常见的浏览器,App 等这些都是客户端。
在 Spring Authorization Server 中 什么是客户端呢?
demo中的demo-client就是客户端;从代码层面理解引入了
spring-boot-starter-oauth2-client
依赖的就是客户端,demo-authorizationserver 中也引入了spring-boot-starter-oauth2-client
,说明 demo-authorizationserver 也是客户端。
demo-client中、demo-authorizationserver中都引入spring-boot-starter-oauth2-client
依赖,猜猜看他们的作用应该也是一致的。
授权服务
什么是授权服务?
通俗点讲也是就授权客户端的服务。
例如:掘金
使用微信登录
时 微信扫码授权登录
,这时微信的服务也就是授权服务
掘金相对时客户端
。再例如:Gitee
使用 QQ登录
时 ,QQ授权登录
,此时QQ服务就是授权服务
,Gitee相对就是客户端
;所以客户端和授权服务是相对的,不同场景下所对应的角色就不一样了。
在 Spring Authorization Server 中什么是授权服务呢?
demo中的demo-authorizationserver就是授权服务;从代码层面理解引入了
spring-security-oauth2-authorization-server
依赖的就是授权服务。
资源服务
在 Spring Authorization Server 中什么是资源服务?
demo中的messages-resource就是资源服务;从代码层面理解引入了
spring-boot-starter-oauth2-resource-server
依赖的就是资源服务。
我们从 demo-client
入手看
日志很重要,别忽略,仔细看👇
访问
http://127.0.0.1:8080/index
,看看demo-client
的输出的日志。![]()
demo-client
日志输出
o.s.security.web.FilterChainProxy : Securing GET /index o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous >SecurityContext o.s.s.w.s.HttpSessionRequestCache : Saved request http://127.0.0.1:8080/index?continue >to session o.s.s.web.DefaultRedirectStrategy : Redirecting to >http://127.0.0.1:8080/oauth2/authorization/messaging-client-oidc o.s.security.web.FilterChainProxy : Securing GET /oauth2/authorization/messaging->client-oidc o.s.s.web.DefaultRedirectStrategy : Redirecting to >http://192.168.56.1:9000/oauth2/authorize?response_type=code&client_id=messaging->client&scope=openid%20profile&state=_c0sC->fsLjR6ZTi37U65_J5Z5JCnpoSyfoFa5A2llo4%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/>messaging-client-oidc&nonce=t02L8qUOiibfrKdZuukArlhoymJBFRFDual0kSTKI7w
Redirecting to http://127.0.0.1:8080/oauth2/authorization/messaging-client-oidc
这个/oauth2/authorization
一看应该是 spring security 内置的api,对应的OAuth2AuthorizationRequestRedirectFilter
。
public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter { > /oauth2/authorization 处理的 //The default base {@code URI} used for authorization requests. public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = >"/oauth2/authorization"; private OAuth2AuthorizationRequestResolver authorizationRequestResolver; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse >response, FilterChain filterChain) throws ServletException, IOException { try { //核心处理就是这了 OAuth2AuthorizationRequest authorizationRequest = >this.authorizationRequestResolver.resolve(request); // if (authorizationRequest != null) { this.sendRedirectForAuthorization(request, response, authorizationRequest); return; } } catch (Exception ex) { this.unsuccessfulRedirectForAuthorization(request, response, ex); return; } } ... }
this.authorizationRequestResolver.resolve(request)
中的authorizationRequestResolver
对应的默认实现DefaultOAuth2AuthorizationRequestResolver
Class
public final class DefaultOAuth2AuthorizationRequestResolver implements >OAuth2AuthorizationRequestResolver { @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { //这个就是从 /oauth2/authorization/messaging-client-oidc 获取 registrationId 也就是 messaging-client-oidc String registrationId = resolveRegistrationId(request); if (registrationId == null) { return null; } String redirectUriAction = getAction(request, "login"); return resolve(request, registrationId, redirectUriAction); } private String resolveRegistrationId(HttpServletRequest request) { if (this.authorizationRequestMatcher.matches(request)) { return this.authorizationRequestMatcher.matcher(request).getVariables() .get(REGISTRATION_ID_URI_VARIABLE_NAME); } return null; } private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { if (registrationId == null) { return null; } ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); if (clientRegistration == null) { throw new InvalidClientRegistrationIdException("Invalid Client Registration with Id: " + registrationId); } OAuth2AuthorizationRequest.Builder builder = getBuilder(clientRegistration); String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction); // @formatter:off builder.clientId(clientRegistration.getClientId()) .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) .redirectUri(redirectUriStr) .scopes(clientRegistration.getScopes()) .state(DEFAULT_STATE_GENERATOR.generateKey()); // @formatter:on this.authorizationRequestCustomizer.accept(builder); return builder.build(); } }
String registrationId = resolveRegistrationId(request); 这个是从 URL 上获取到 registrationId ,然后通过 registrationId 找到 yml中 registrationId 对应的所有配置,
然后build重定向URL 👉
Redirecting tohttp://192.168.56.1:9000/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid%20profile&state=dFUSOiG3BXavKSPFCHfK-ER7LbcAyOnHkzWcsNG9w_4%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=XMptCr0NC1yRO1yNJ6v6VdtuNTC09Di_wPm_LWb7Ph0
这个重定向URL 看到有点眼熟 是吧,就是之前演示授权码模式 获取授权码的请求URL
以上日志最后一行就是这样得来的。
重定向到了 demo-authorizationserver(授权服务器)接着看
客户端的重定向到授权服务的URL
http://192.168.56.1:9000/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid%20profile&state=dFUSOiG3BXavKSPFCHfK-ER7LbcAyOnHkzWcsNG9w_4%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=XMptCr0NC1yRO1yNJ6v6VdtuNTC09Di_wPm_LWb7Ph0
看看demo-authorizationserver
的日志输出。
o.s.security.web.FilterChainProxy : Securing GET /oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid%20profile&state=u2Gws4H0Sk34mm8gQGRvx3Ulx6ukazugneZEbaCeBj8%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=XG92BigeimIMXkDZnQwKmJY1E2_pGGzeAeeqwwq87mU o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext o.s.s.w.s.HttpSessionEventPublisher : Publishing event: org.springframework.security.web.session.HttpSessionCreatedEvent[source=io.undertow.servlet.spec.HttpSessionImpl@6670b8f6] o.s.s.w.s.HttpSessionRequestCache : Saved request http://192.168.56.1:9000/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid%20profile&state=u2Gws4H0Sk34mm8gQGRvx3Ulx6ukazugneZEbaCeBj8%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=XG92BigeimIMXkDZnQwKmJY1E2_pGGzeAeeqwwq87mU&continue to session s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.HeaderContentNegotiationStrategy@2c929396, matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[]] s.w.a.DelegatingAuthenticationEntryPoint : Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@1441274e o.s.s.web.DefaultRedirectStrategy : Redirecting to http://192.168.56.1:9000/login
Securing GET /oauth2/authorize? 对应是
OAuth2AuthorizationEndpointFilter
Filter
public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter { //The default endpoint {@code URI} for authorization requests. private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; ...省略 @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, >FilterChain filterChain) throws ServletException, IOException { if (!this.authorizationEndpointMatcher.matches(request)) { filterChain.doFilter(request, response); return; } try { Authentication authentication = this.authenticationConverter.convert(request); if (authentication instanceof AbstractAuthenticationToken) { ((AbstractAuthenticationToken) authentication) .setDetails(this.authenticationDetailsSource.buildDetails(request)); } Authentication authenticationResult = >this.authenticationManager.authenticate(authentication); if (!authenticationResult.isAuthenticated()) { // If the Principal (Resource Owner) is not authenticated then // pass through the chain with the expectation that the authentication process // will commence via AuthenticationEntryPoint filterChain.doFilter(request, response); return; } if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken) { if (this.logger.isTraceEnabled()) { this.logger.trace("Authorization consent is required"); } sendAuthorizationConsent(request, response, (OAuth2AuthorizationCodeRequestAuthenticationToken) authentication, (OAuth2AuthorizationConsentAuthenticationToken) authenticationResult); return; } this.sessionAuthenticationStrategy.onAuthentication( authenticationResult, request, response); this.authenticationSuccessHandler.onAuthenticationSuccess( request, response, authenticationResult); } catch (OAuth2AuthenticationException ex) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorization request failed: %s", >ex.getError()), ex); } this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); } } ...省略 }
OAuth2AuthorizationEndpointFilter 中关键的2行代码
1.Authentication authentication = this.authenticationConverter.convert(request)
authenticationConverter 此时对应的OAuth2AuthorizationCodeRequestAuthenticationConverter
public final class OAuth2AuthorizationCodeRequestAuthenticationConverter implements >AuthenticationConverter { private static final Authentication ANONYMOUS_AUTHENTICATION = new >AnonymousAuthenticationToken( "anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); private static final RequestMatcher OIDC_REQUEST_MATCHER = createOidcRequestMatcher(); @Override public Authentication convert(HttpServletRequest request) { ... return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationUri, clientId, principal, redirectUri, state, scopes, additionalParameters); }
authentication 返回的
OAuth2AuthorizationCodeRequestAuthenticationToken
对应是OAuth2AuthorizationCodeRequestAuthenticationProvider
处理。
2.Authentication authenticationResult = this.authenticationManager.authenticate(authentication)
public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = (OAuth2AuthorizationCodeRequestAuthenticationToken) authentication; //...省略了部分代码 //生成授权码 OAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext); OAuth2Authorization authorization = authorizationBuilder(registeredClient, principal, authorizationRequest) .authorizedScopes(authorizationRequest.getScopes()) .token(authorizationCode) .build(); //保存了授权请求 this.authorizationService.save(authorization); //返回新的 OAuth2AuthorizationCodeRequestAuthenticationToken return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationRequest.getAuthorizationUri(), registeredClient.getClientId(), principal, authorizationCode, redirectUri, authorizationRequest.getState(), authorizationRequest.getScopes()); } }
demo-authorizationserver 授权服务器登录、授权成功后重定向 客户端的日志输出
o.s.s.web.DefaultRedirectStrategy : Redirecting to http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc?code=sV9eoLu4NYu7vjSzcstwC8lSrZXts_1LFUPeBVtfbg6KuIUlBBah6MOU5Z9z8DAopR10p2ocJ1eVf1za3F0-DqJLfz0OZ3jhRqgWbp0q_lyDX16LGv8wL23pNGRc-XBP&state=cfWVM_UkJ8209MOuS5tiGaPlCucbOjP8XaAiDiAZ338%3D
重定向到了 demo-client(客户端)接着看
回调到客户端的URL/login/oauth2/code/messaging-client-oidc
对应的是OAuth2LoginAuthenticationFilter
Filter 继承了AbstractAuthenticationProcessingFilter
,这个URL 也是一个模板 对应/login/oauth2/code/*
AbstractAuthenticationProcessingFilter
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } try { Authentication authenticationResult = attemptAuthentication(request, response); if (authenticationResult == null) { // return immediately as subclass has indicated that it hasn't completed return; } this.sessionStrategy.onAuthentication(authenticationResult, request, response); // Authentication success if (this.continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authenticationResult); } catch (InternalAuthenticationServiceException failed) { this.logger.error("An internal error occurred while trying to authenticate the user.", failed); unsuccessfulAuthentication(request, response, failed); } catch (AuthenticationException ex) { // Authentication failed unsuccessfulAuthentication(request, response, ex); } } }
OAuth2LoginAuthenticationFilter
public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/oauth2/code/*"; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //...省略 OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest); OAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter.convert(authenticationResult); this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response); return oauth2Authentication; } } OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest); 对应的就是 `OidcAuthorizationCodeAuthenticationProvider` ```java public class OidcAuthorizationCodeAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { OAuth2LoginAuthenticationToken authorizationCodeAuthentication = (OAuth2LoginAuthenticationToken) authentication; //...省略 OAuth2AccessTokenResponse accessTokenResponse = getResponse(authorizationCodeAuthentication); //...省略 OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration, accessTokenResponse.getAccessToken(), idToken, additionalParameters)); //...省略 } //继续跟 OAuth2AccessTokenResponse accessTokenResponse = getResponse(authorizationCodeAuthentication); private OAuth2AccessTokenResponse getResponse(OAuth2LoginAuthenticationToken authorizationCodeAuthentication) { try { return this.accessTokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(), authorizationCodeAuthentication.getAuthorizationExchange())); } catch (OAuth2AuthorizationException ex) { OAuth2Error oauth2Error = ex.getError(); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex); } } }
this.accessTokenResponseClient.getTokenResponse() 对应是
DefaultAuthorizationCodeTokenResponseClient
,这个里面封装了request请求去请求授权服务器
public final class DefaultAuthorizationCodeTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> { @Override public OAuth2AccessTokenResponse getTokenResponse( OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) { Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null"); RequestEntity<?> request = this.requestEntityConverter.convert(authorizationCodeGrantRequest); ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request); return response.getBody(); } private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) { try { return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class); } catch (RestClientException ex) { OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, "An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null); throw new OAuth2AuthorizationException(oauth2Error, ex); } } }
/oauth2/token 就是获取token请求,看以上的请求参数 就是授权码模式的请求参数 👍
又回到 demo-authorizationserver(授权服务)
接着看 /oauth2/token
/oauth2/token 对应的
OAuth2TokenEndpointFilter
public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter { //The default endpoint {@code URI} for access token requests private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //核心代码差不多就这2行 Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request); OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication); } }
Authentication authentication = this.authenticationConverter.convert(request);
授权码获取token时 authenticationConverter 对应OAuth2AuthorizationCodeAuthenticationConverter
验证参数 将对应参数封装再OAuth2AuthorizationCodeAuthenticationToken
返回Authentication authenticationResult = this.authenticationManager.authenticate(authentication); 对应的
OAuth2AuthorizationCodeAuthenticationProvider
里面对参数认证通过后 生成 access_token、refresh_token 等信息返回 。
又回到 demo-client(客户端)
授权服务器响应回来的数据,也就是/oauth2/token 接口响应数据,客户端有了 授权服务的token 携带这个token去访问资源服务就ok了
访问
这个请求实际上会去访问
http://127.0.0.1:8090/messages
也就是资源服务的 messages 接口
messages-resource(资源服务器)
messages-resource的日志输出
o.s.security.web.FilterChainProxy : Securing GET /messages o.s.web.client.RestTemplate : HTTP GET http://192.168.56.1:9000/.well-known/openid-configuration o.s.web.client.RestTemplate : Accept=[application/json, application/*+json] o.s.web.client.RestTemplate : Response 200 OK o.s.web.client.RestTemplate : Reading to [java.util.Map<java.lang.String, java.lang.Object>] o.s.web.client.RestTemplate : HTTP GET http://192.168.56.1:9000/oauth2/jwks o.s.web.client.RestTemplate : Accept=[text/plain, application/json, application/*+json, */*] o.s.web.client.RestTemplate : Response 200 OK o.s.web.client.RestTemplate : Reading to [java.lang.String] as "application/json;charset=ISO-8859-1" o.s.s.o.s.r.a.JwtAuthenticationProvider : Authenticated token .s.r.w.a.BearerTokenAuthenticationFilter : Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@ecb83e6d, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[SCOPE_message.read, SCOPE_message.write]] o.s.security.web.FilterChainProxy : Secured GET /messages o.s.web.servlet.DispatcherServlet : GET "/messages", parameters={}
HTTP GET
http://192.168.56.1:9000/.well-known/openid-configuration
到授权服务器 获取对应的配置 返回数据的如下
{ "issuer": "http://192.168.56.1:9000", "authorization_endpoint": "http://192.168.56.1:9000/oauth2/authorize", "device_authorization_endpoint": "http://192.168.56.1:9000/oauth2/device_authorization", "token_endpoint": "http://192.168.56.1:9000/oauth2/token", "token_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ], "jwks_uri": "http://192.168.56.1:9000/oauth2/jwks", "userinfo_endpoint": "http://192.168.56.1:9000/userinfo", "end_session_endpoint": "http://192.168.56.1:9000/connect/logout", "response_types_supported": [ "code" ], "grant_types_supported": [ "authorization_code", "client_credentials", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code" ], "revocation_endpoint": "http://192.168.56.1:9000/oauth2/revoke", "revocation_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ], "introspection_endpoint": "http://192.168.56.1:9000/oauth2/introspect", "introspection_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ], "subject_types_supported": [ "public" ], "id_token_signing_alg_values_supported": [ "RS256" ], "scopes_supported": [ "openid" ] }
HTTP GET
http://192.168.56.1:9000/oauth2/jwks
对应是NimbusJwkSetEndpointFilter
public final class NimbusJwkSetEndpointFilter extends OncePerRequestFilter { /** * The default endpoint {@code URI} for JWK Set requests. */ private static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (!this.requestMatcher.matches(request)) { filterChain.doFilter(request, response); return; } JWKSet jwkSet; try { jwkSet = new JWKSet(this.jwkSource.get(this.jwkSelector, null)); } catch (Exception ex) { throw new IllegalStateException("Failed to select the JWK(s) -> " + ex.getMessage(), ex); } response.setContentType(MediaType.APPLICATION_JSON_VALUE); try (Writer writer = response.getWriter()) { // toString() excludes private keys writer.write(jwkSet.toString()); } } } }
/oauth2/jwks 返回的数据 也就是加密算法和私钥
{ "keys": [ { "kty": "RSA", "e": "AQAB", "kid": "24e8e61e-0318-45cb-bf1b-74fc67fee331", "n": "lJ8_64e1A_XSasigEH8k2JksMG5B1xR3DjbznmPguIVDnTY1V5dTiPPED6EuKcJjpnt88SJETcno-vq7X_WUtntVJ187TyLz1zaA2yHykFK5JFKzPMwURRzdFddZFujyQNPSD_bFGtSo326ksqXAdy4I5DhNmgvU1pKYUTk6IK2zsFC8FtPDNHRNSKe-rA2pek4SIDMhSQf07cDo26N8fbJWFvrlh6httIpGnyYpIYs1Yb7YJIf8Mr_y-XmCCvDQQJc-h0C4svu3YbGsoFn_QTsu-5ayErRvZarrQdR2RNowLzXLeje5p4l4yPG4qs56jR0mMLSkdXfVkJuvGYSwZw" } ] }
作用就是来解密 demo-client 携带到 messages-resource 的token。
o.s.s.o.s.r.a.JwtAuthenticationProvider : Authenticated token 从日志就能看出
JwtAuthenticationProvider
这里进行的 token的验证
public final class JwtAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication; Jwt jwt = getJwt(bearer); AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt); if (token.getDetails() == null) { token.setDetails(bearer.getDetails()); } this.logger.debug("Authenticated token"); return token; } private Jwt getJwt(BearerTokenAuthenticationToken bearer) { try { return this.jwtDecoder.decode(bearer.getToken()); } catch (BadJwtException failed) { this.logger.debug("Failed to authenticate since the JWT was invalid"); throw new InvalidBearerTokenException(failed.getMessage(), failed); } catch (JwtException failed) { throw new AuthenticationServiceException(failed.getMessage(), failed); } } }
token 验证成功后 页面就成功访问到资源服务的 message 接口了
我们前面讲到的demo中 用postman 携带token 访问资源服务 操作过,你看 demo-clien 内部自己就携带token去了 是不是很方便。
demo-authorizationserver【授权服务】 选择gitee登录 时
选择gitee登录 时,demo-authorizationserver 输出日志
o.s.security.web.FilterChainProxy : Securing GET /oauth2/authorization/gitee o.s.s.web.DefaultRedirectStrategy : Redirecting to https://gitee.com/oauth/authorize?response_type=code&client_id=29b85c97ed682910eaa4276d84a0c4532f00b962e1b9fe8552520129e65ae432&scope=emails%20user_info&state=wEC1KPetaUh7yDnCYVKvqixMld-7C2AOrSTq1sAOEac%3D&redirect_uri=http://192.168.56.1:9000/login/oauth2/code/gitee
/oauth2/authorization/gitee
也是OAuth2AuthorizationRequestRedirectFilter
处理,这个和先前的/oauth2/authorization/messaging-client-oidc
一样,那么也会走DefaultOAuth2AuthorizationRequestResolver
最后构建 gitee 出授权请求 进而 重定向。因为 demo-authorizationserver 引入
spring-boot-starter-oauth2-client
依赖, 所以它即是 客户端 也是授权服务。 giee登录认证、授权成功后,会回调demo-authorizationserver的http://192.168.56.1:9000/login/oauth2/code/gitee
o.s.security.web.FilterChainProxy : Securing GET /login/oauth2/code/gitee?code=878b7b045bacc8ce85f3c5437dfb9669d5a4584a53237ba9db79b33439a10a01&state=jUPPNYGLGSX4wdUCUIpAxCo22xBnTUDoVL_5eyyhuGI%3D
/login/oauth2/code/gitee
与 之前的/login/oauth2/code/messaging-client-oidc
处理都是一样的 ,对应都是OAuth2LoginAuthenticationFilter
Filter 继承了AbstractAuthenticationProcessingFilter
,这2个 filter去处理。
OAuth2LoginAuthenticationFilter 从 回调的 URL 同样的获取到授权码 然后内部再去请求 gitee 获取到 token 然后存储到 自身 demo-authorizationserver 中,那此时 demo-authorizationserver 相对于 gitee 是客户端,并且已经获取授权,说明demo-authorizationserver 客户端也就不用登录了。
总结
spring-boot-starter-oauth2-client👇
OAuth2AuthorizationRequestRedirectFilter
- /oauth2/authorization 处理的 Filter
DefaultOAuth2AuthorizationRequestResolver
- 从 /oauth2/authorization/{registrationId} URL 上获取到
registrationId
- 通过
registrationId
在yml配置中找到 对应的 client—id 的所有配置- 通过 client—id 构建对应 clien-id 配置的重定向URL,然后进行重定向
OAuth2LoginAuthenticationFilter
- 处理 oauth2 授权服务回调 模板URL /login/oauth2/code/* 的Filter
OidcAuthorizationCodeAuthenticationProvider
- 处理 oauth2 授权服务回调 的请求数据
DefaultAuthorizationCodeTokenResponseClient
- 构建 oauth2 授权授权码模式的认证请求(获取token请求)
spring-security-oauth2-authorization-server👇
OAuth2AuthorizationEndpointFilter
- /oauth2/authorize 处理的 Filter
OAuth2AuthorizationCodeRequestAuthenticationConverter
- 从request 获取 验证OAuth2 授权码模式的授权请求参数,然后封装到 OAuth2AuthorizationCodeRequestAuthenticationToken 对象中返回
OAuth2AuthorizationCodeRequestAuthenticationProvider
- 处理 OAuth2AuthorizationCodeRequestAuthenticationToken 类型的认证逻辑
OAuth2TokenEndpointFilter
- /oauth2/token 处理的 Filter
OAuth2AuthorizationCodeAuthenticationConverter
- 从request 获取 验证OAuth2 授权码模式的认证请求参数,然后封装到OAuth2AuthorizationCodeAuthenticationToken 对象中返回
OAuth2AuthorizationCodeAuthenticationProvider
- 验证授权码是否有效、验证client-id 有效性、对应的scope
- 生成 Access token 、Refresh token、ID token
- 保存授权请求和相关 token
NimbusJwkSetEndpointFilter
- 处理 /oauth2/jwks 的Filter 返回JWT加密算法和私钥
spring-boot-starter-oauth2-resource-server👇
JwtAuthenticationProvider
- 对请求中 token 进行验证
oauth2 客户端认证、授权流程图(以gitee示例)
文章中看这个图不太清晰,到github上下载 github.com/WatermelonP… 看!!!,这个图很重要,包含了整个oauth2的整个流程(详细程度80%)