上一篇(Spring Security OAuth 认证流程浅析:密码模式 ),简单分析了 Spring Security OAuth 密码模式授权流程的源码,这篇来试着分析 OAuth 中最具代表性的授权码模式。
由于内容的连贯性,建议读之前,先阅读上一篇分析密码模式的文章(Spring Security OAuth 认证流程浅析:密码模式 )。同时,阅读以下内容需要你了解 OAuth 的原理,可以阅读这篇文章回顾一下。
授权码模式的流程先简单回顾一下授权码模式的流程。看下图:
A 步骤:用户访问客户端,客户端会将前者重定向到认证服务器。B 步骤:认证服务器返回认证和权限确认的页面,用户选择是否给予客户端授权。C 步骤:当用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码(authorization_code)。D 步骤:客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。E 步骤:认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。 请求授权码(authorization code)
在 Spring Security OAuth 中,获取授权码的方式如下:
GET HOST:PORT/oauth/authorize?response_type=code&client_id={{client_id}}&redirect_uri={{redirect_uri}}&scope={{scope}}&state={{state}} 复制代码
我们可以在 AuthorizationEndpoint 类中,找到处理这个请求的代码。
这个方法的代码比较长,你可以自行查看。
第一次访问这个请求的时候,会做这么几件事儿:
首先,校验 response_type 参数的值,只能是 code 或者 token。使用授权码模式的时候,给的参数值是 code,token 是在简易模式时使用的,这里不考虑。然后,验证 client_id 参数是不是提供了。之后,会判断用户是不是已经认证,因为我们是第一次请求,因此会跑出异常。
这里抛出的异常是 InsufficientAuthenticationException ,它的父类是 AuthenticationException ,异常被 ExceptionTranslationFilter 捕获后,会将此请求跳转到用户登录和授权的页面。
用户登录授权当用户登录并且确认授权后,根据 Spring Security 的流程,会跳转到登录前请求的地址,因此会再次请求获取授权吗的地址。
这次请求后,用户已经被认证过,会进入之后的逻辑,进入请求时 client_id 对应的客户端的重定向地址。可参考下面的代码片段:
if ( authorizationRequest.isApproved ()) { if ( responseTypes.contains ( "token" )) { return getImplicitGrantResponse ( authorizationRequest ) ; } if ( responseTypes.contains ( "code" )) { return new ModelAndView ( getAuthorizationCodeResponse ( authorizationRequest, ( Authentication ) principal )) ; } } 复制代码
在重定向到这个地址的时候,会包含授权服务器生成的授权码,这个请求会发送到客户端程序的后端服务,这样,客户端会接收到这个授权码。
至此,客户端在不接触用户名和密码的情况下,获取到了一个合法的授权码。
请求访问令牌(access token)客户端在得到授权码以后,下一步就是请求访问令牌,请求方式如下:
POST HOST:PORT/oauth/token Authorization: BasicContent-Type: application/x-www-form-urlencoded grant_type=authorization_code&code={{authorization_code}}&redirect_uri={{redirect_uri}}&client_id={{client_id}}&scope={{scope}}&state={{state}} 复制代码
请求的地址是 /oauth/token 与密码模式请求的地址相同,具体的处理逻辑可以参考上一篇分析密码模式的文章(Spring Security OAuth 认证流程浅析:密码模式 ),这里关键的一行代码是:
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); 复制代码
因此,之后的逻辑是由 AuthorizationCodeTokenGranter 类型来处理的。这里掠过与上一篇文章中重复的内容,直接看 getOAuth2Authentication 方法的内容。我把代码贴出来:
@Override protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { Mapparameters = tokenRequest.getRequestParameters(); String authorizationCode = parameters.get("code"); String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI); if (authorizationCode == null) { throw new InvalidRequestException("An authorization code must be supplied."); } OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode); if (storedAuth == null) { throw new InvalidGrantException("Invalid authorization code: " + authorizationCode); } OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request(); // https://jira.springsource.org/browse/SECOAUTH-333 // This might be null, if the authorization was done without the redirect_uri parameter String redirectUriApprovalParameter = pendingOAuth2Request.getRequestParameters().get( OAuth2Utils.REDIRECT_URI); if ((redirectUri != null || redirectUriApprovalParameter != null) && !pendingOAuth2Request.getRedirectUri().equals(redirectUri)) { throw new RedirectMismatchException("Redirect URI mismatch."); } String pendingClientId = pendingOAuth2Request.getClientId(); String clientId = tokenRequest.getClientId(); if (clientId != null && !clientId.equals(pendingClientId)) { // just a sanity check. throw new InvalidClientException("Client ID mismatch"); } // Secret is not required in the authorization request, so it won't be available // in the pendingAuthorizationRequest. We do want to check that a secret is provided // in the token request, but that happens elsewhere. Map combinedParameters = new HashMap (pendingOAuth2Request .getRequestParameters()); // Combine the parameters adding the new ones last so they override if there are any clashes combinedParameters.putAll(parameters); // Make a new stored request with the combined parameters OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters); Authentication userAuth = storedAuth.getUserAuthentication(); return new OAuth2Authentication(finalStoredOAuth2Request, userAuth); } 复制代码
这段代码主要做了这么几件事:
- 从请求当中获取授权码(authorizationCode)和重定向地址(redirectUri)。通过授权码获取 OAuth2Authentication 对象。从 OAuth2Authentication 中获取 OAuth2Request,并校验授权码和重定向地址的合法性。创建新的 OAuth2Request 并创建新的 OAuth2Authentication 对象。
这个方法由父类中的方法调用,最终的结果,会返回到 tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest)) 这行代码,生成最终的 Token 并返回给客户端。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)