赞
踩
在《授权服务器是如何实现授权的呢?》中,我们可以了解到服务端实现授权的流程,同时了解"/oauth/authorize"授权接口在AuthorizationEndpoint类中定义。这一节,我们将详细分析AuthorizationEndpoint类的实现。
AuthorizationEndpoint授权端点继承自AbstractEndpoint抽象类,这里我们先分析一下AbstractEndpoint抽象类的实现逻辑。
AbstractEndpoint抽象类实现了InitializingBean接口,这样在afterPropertiesSet()方法中就可以实现一些字段的校验和初始化工作了。主要实现了TokenGranter、ClientDetailsService、OAuth2RequestFactory和WebResponseExceptionTranslator等对象初始化校验工作,保证了tokenGranter和clientDetailsService不能为空,oAuth2RequestFactory如果为空,则使用默认的DefaultOAuth2RequestFactory对象。同时还提供了这些属性字段的get、set等方法。
public class AbstractEndpoint implements InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
private WebResponseExceptionTranslator<OAuth2Exception> providerExceptionHandler = new DefaultWebResponseExceptionTranslator();
private TokenGranter tokenGranter;
private ClientDetailsService clientDetailsService;
private OAuth2RequestFactory oAuth2RequestFactory;
private OAuth2RequestFactory defaultOAuth2RequestFactory;
public void afterPropertiesSet() throws Exception {
Assert.state(tokenGranter != null, "TokenGranter must be provided");
Assert.state(clientDetailsService != null, "ClientDetailsService must be provided");
defaultOAuth2RequestFactory = new DefaultOAuth2RequestFactory(getClientDetailsService());
if (oAuth2RequestFactory == null) {
oAuth2RequestFactory = defaultOAuth2RequestFactory;
}
}
//省略……
}
AuthorizationEndpoint是OAuth2规范中的授权端点的实现。接收授权请求,处理用户授权,该请求需要经过认证的用户才能正常访问。在该类中,定义了"/oauth/authorize"接口,用于处理客户端的授权请求。
当重定向到授权服务器的"/oauth/authorize"请求时,比如“http://localhost:8080/oauth/authorize?response_type=code&client_id=resource1&scope=all&state=XXX&redirect_uri=XXX”,这个时候访问的就是"/oauth/authorize",对应的是AuthorizationEndpoint类的authorize()方法,实现逻辑如下:
@RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
SessionStatus sessionStatus, Principal principal) {
//通过OAuth2RequestFactory创建授权请求对象AuthorizationRequest,其中OAuth2RequestFactory对象在抽象类中已经完成了初始化
AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
//获取responseTypes类型参数并进行判断,仅支持token或code两种
Set<String> responseTypes = authorizationRequest.getResponseTypes();
if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
}
//校验clientId,不能为空
if (authorizationRequest.getClientId() == null) {
throw new InvalidClientException("A client id must be provided");
}
try {
//校验principal,首先principal对象必须是Authentication类型的,同时必须是已经授权的用户信息,所在在进行用户登录授权时,第一次访问该方法时,因为用户未经授权,所以会抛出InsufficientAuthenticationException异常,然后就会跳转到统一的登录页面
if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
throw new InsufficientAuthenticationException(
"User must be authenticated with Spring Security before authorization can be completed.");
}
//通过ClientDetailsService获取客户端详细信息-ClientDetails对象,其中,ClientDetailsService对象在抽象类中已经完成了初始化
ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
//解析实际跳转路径地址resolvedRedirect参数,并保存到authorizationRequest对象中。其中,通过redirectResolver.resolveRedirect()方法,验证grantTypes类型,只支持implicit 或 authorization_code,同时验证请求中的redirectUri链接是否已经在客户端信息中进行注册,没有注册就会抛出RedirectMismatchException异常。
String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
if (!StringUtils.hasText(resolvedRedirect)) {
throw new RedirectMismatchException(
"A redirectUri must be either supplied or preconfigured in the ClientDetails");
}
authorizationRequest.setRedirectUri(resolvedRedirect);
//校验请求中的scope和客户端中注册的scope是否匹配,不匹配就抛出InvalidScopeException。
oauth2RequestValidator.validateScope(authorizationRequest, client);
//检验用户是否授权,默认使用的是DefaultUserApprovalHandler对象,实现逻辑等同于是直接判断authorizationRequest对象的approved属性
authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
(Authentication) principal);
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);
// 如果已经经过用户授权,如果responseTypes包含token这调用getImplicitGrantResponse()方法,进行跳转,如果responseTypes包含code,则调用getAuthorizationCodeResponse()方法,进行跳转。
if (authorizationRequest.isApproved()) {
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest);
}
if (responseTypes.contains("code")) {
return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
(Authentication) principal));
}
}
// 如果没有经过用户授权,就会保存authorizationRequest对象,然后调用getUserApprovalPageResponse()方法,跳转到"forward:/oauth/confirm_access";即,登录授权确认页面,该地址在WhitelabelApprovalEndpoint类中定义,并提供了授权确认的接口"/oauth/confirm_access"。
model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));
return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
}
catch (RuntimeException e) {
sessionStatus.setComplete();
throw e;
}
}
当重定向到授权确认页面时,用户选择同意或者不同意进行登录授权,这个时候会提交“http://localhost:8080/oauth/authorize” POST请求,对应AuthorizationEndpoint类的approveOrDeny()方法,参数如下(下面参数是同意授权):
这个时候,就会请求到AuthorizationEndpoint提供的"/oauth/authorize" POST 请求,即用户同意授权请求,该方法处理逻辑如下:
@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
SessionStatus sessionStatus, Principal principal) {
//验证principal参数是否是Authentication类型,如果不是直接抛出InsufficientAuthenticationException异常
if (!(principal instanceof Authentication)) {
sessionStatus.setComplete();
throw new InsufficientAuthenticationException(
"User must be authenticated with Spring Security before authorizing an access token.");
}
//通过model参数获取AuthorizationRequest对象,该参数在上一步骤("/oauth/authorize")中,被存储到了参数中进行传递。如果该参数没有获取到,直接抛出InvalidRequestException异常。然后再比较authorizationRequest参数是否发生变化,如果监测到变化,就抛出InvalidRequestException异常。
AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get(AUTHORIZATION_REQUEST_ATTR_NAME);
if (authorizationRequest == null) {
sessionStatus.setComplete();
throw new InvalidRequestException("Cannot approve uninitialized authorization request.");
}
// 判断authorizationRequest参数是否发生变化,如果发生变化,说明请求发生了变化就会抛出InvalidRequestException异常
@SuppressWarnings("unchecked")
Map<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME);
if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) {
throw new InvalidRequestException("Changes were detected from the original authorization request.");
}
try {
//获取当前授权请求的responseTypes参数
Set<String> responseTypes = authorizationRequest.getResponseTypes();
//检验用户是否授权,默认使用的是DefaultUserApprovalHandler对象,实现逻辑等同于是直接判断authorizationRequest对象的approved属性,并进行保存
authorizationRequest.setApprovalParameters(approvalParameters);
authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,
(Authentication) principal);
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);
//判断当前授权请求redirectUri是否为空,如果为空,则抛出InvalidRequestException异常
if (authorizationRequest.getRedirectUri() == null) {
sessionStatus.setComplete();
throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");
}
//判断当前授权请求是否已经被授权,如果未被授权,说明出现了异常,调用getUnsuccessfulRedirect()方法,并返回RedirectView对象。
if (!authorizationRequest.isApproved()) {
return new RedirectView(getUnsuccessfulRedirect(authorizationRequest,
new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")),
false, true, false);
}
//如果responseTypes包含token这调用getImplicitGrantResponse()方法,进行跳转
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest).getView();
}
//否则,调用getAuthorizationCodeResponse()方法,进行跳转。即跳转地址为“http://localhost:8100/login/oauth2/code/qriver?code=afGuXC&state=XXX”,这个方法中会调用generateCode()方法,生成授权码字符串,实际上是由authorizationCodeServices创建并管理的。
return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
}
finally {
sessionStatus.setComplete();
}
}
在"/oauth/authorize"接口对应authorize()方法中,调用了getUserApprovalPageResponse() 方法,实现了创建向授权同意页面跳转的视图。默认跳转地址为:“forward:/oauth/confirm_access”。
private ModelAndView getUserApprovalPageResponse(Map<String, Object> model,
AuthorizationRequest authorizationRequest, Authentication principal) {
if (logger.isDebugEnabled()) {
logger.debug("Loading user approval page: " + userApprovalPage);
}
model.putAll(userApprovalHandler.getUserApprovalRequest(authorizationRequest, principal));
return new ModelAndView(userApprovalPage, model);
}
其中,“/oauth/confirm_access”接口对应定义在WhitelabelApprovalEndpoint类中,提供了默认的同意授权视图页面,通过重新配置userApprovalPage地址,可以实现自定义页面的配置。
@FrameworkEndpoint
@SessionAttributes("authorizationRequest")
public class WhitelabelApprovalEndpoint {
@RequestMapping("/oauth/confirm_access")
public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
final String approvalContent = createTemplate(model, request);
if (request.getAttribute("_csrf") != null) {
model.put("_csrf", request.getAttribute("_csrf"));
}
View approvalView = new View() {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType(getContentType());
response.getWriter().append(approvalContent);
}
};
return new ModelAndView(approvalView, model);
}
protected String createTemplate(Map<String, Object> model, HttpServletRequest request) {
//省略……
}
}
当responseType=token时,该方法会被调用,主要用于implicit授权方式。在该方法中,调用getAccessTokenForImplicitGrant()方法获取accessToken,然后调用appendAccessToken()方法封装视图参数,最后返回ModelAndView()视图。
private ModelAndView getImplicitGrantResponse(AuthorizationRequest authorizationRequest) {
try {
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(authorizationRequest, "implicit");
OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request);
if (accessToken == null) {
throw new UnsupportedResponseTypeException("Unsupported response type: token");
}
return new ModelAndView(new RedirectView(appendAccessToken(authorizationRequest, accessToken), false, true,
false));
}
catch (OAuth2Exception e) {
return new ModelAndView(new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, true), false,
true, false));
}
}
getAccessTokenForImplicitGrant()方法的实现逻辑如下,主要通过调用TokenGranter的grant()方法生成对应的accessToken,这里不再展开分析,后期再Token端点的学习中再展开分析。
private OAuth2AccessToken getAccessTokenForImplicitGrant(TokenRequest tokenRequest,
OAuth2Request storedOAuth2Request) {
OAuth2AccessToken accessToken = null;
// These 1 method calls have to be atomic, otherwise the ImplicitGrantService can have a race condition where
// one thread removes the token request before another has a chance to redeem it.
synchronized (this.implicitLock) {
accessToken = getTokenGranter().grant("implicit",
new ImplicitTokenRequest(tokenRequest, storedOAuth2Request));
}
return accessToken;
}
当使用授权码模式时,该方法才会被调用,在getImplicitGrantResponse()方法中会直接返回token,而这里则是生成对应的code进行返回,然后再由客户端拿着code来兑换对应的token。具体实现如下:
private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) {
try {
return new RedirectView(getSuccessfulRedirect(authorizationRequest,
generateCode(authorizationRequest, authUser)), false, true, false);
}
catch (OAuth2Exception e) {
return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false, true, false);
}
}
在getAuthorizationCodeResponse()方法中,首先调用generateCode()方法生成对应code,然后再调用getSuccessfulRedirect()方法,封装返回视图需要的参数。
generateCode()方法的实现如下,在该方法中,主要通过调用authorizationCodeServices的createAuthorizationCode()方法实现了授权码code的生成。
private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication)
throws AuthenticationException {
try {
OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);
return code;
}
catch (OAuth2Exception e) {
if (authorizationRequest.getState() != null) {
e.addAdditionalInformation("state", authorizationRequest.getState());
}
throw e;
}
}
getSuccessfulRedirect()方法,实现授权码code的封装,实现如下:
private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {
if (authorizationCode == null) {
throw new IllegalStateException("No authorization code found in the current request scope.");
}
Map<String, String> query = new LinkedHashMap<String, String>();
query.put("code", authorizationCode);
String state = authorizationRequest.getState();
if (state != null) {
query.put("state", state);
}
return append(authorizationRequest.getRedirectUri(), query, false);
}
在前面generateCode()方法中,实现了授权码的产生,这里我们详细分析一下AuthorizationCodeServices接口及其实现类,看看授权码是如何产生和被消费的。
AuthorizationCodeServices 用来管理授权码,提供了创建授权码、消费授权码等方法。框架提供了InMemoryAuthorizationCodeServices和JdbcAuthorizationCodeServices两个实现类,默认使用InMemoryAuthorizationCodeServices实例对象。
public interface AuthorizationCodeServices {
//创建授权码
String createAuthorizationCode(OAuth2Authentication authentication);
//消费授权码
OAuth2Authentication consumeAuthorizationCode(String code)
throws InvalidGrantException;
}
在AuthorizationCodeServices接口中定义了两个方法:第一个是创建授权码code方法createAuthorizationCode(),第二个方法是消费授权码方法consumeAuthorizationCode()方法。
引入了RandomValueStringGenerator对象,用于创建授权码。同时,增加了store()和remove()两个抽象方法,分别用于存储授权码和移除授权码。
public abstract class RandomValueAuthorizationCodeServices implements AuthorizationCodeServices {
private RandomValueStringGenerator generator = new RandomValueStringGenerator();
protected abstract void store(String code, OAuth2Authentication authentication);
protected abstract OAuth2Authentication remove(String code);
public String createAuthorizationCode(OAuth2Authentication authentication) {
String code = generator.generate();
store(code, authentication);
return code;
}
public OAuth2Authentication consumeAuthorizationCode(String code)
throws InvalidGrantException {
OAuth2Authentication auth = this.remove(code);
if (auth == null) {
throw new InvalidGrantException("Invalid authorization code: " + code);
}
return auth;
}
}
默认实现类InMemoryAuthorizationCodeServices,继承自抽象类RandomValueAuthorizationCodeServices,实现了store()、remove()两个方法,这里定义了一个authorizationCodeStore变量(ConcurrentHashMap<String, OAuth2Authentication>类型),用于存储授权码。
public class InMemoryAuthorizationCodeServices extends RandomValueAuthorizationCodeServices {
protected final ConcurrentHashMap<String, OAuth2Authentication> authorizationCodeStore = new ConcurrentHashMap<String, OAuth2Authentication>();
@Override
protected void store(String code, OAuth2Authentication authentication) {
this.authorizationCodeStore.put(code, authentication);
}
@Override
public OAuth2Authentication remove(String code) {
OAuth2Authentication auth = this.authorizationCodeStore.remove(code);
return auth;
}
}
JdbcAuthorizationCodeServices实现类,和InMemoryAuthorizationCodeServices类似,只不过是存储到了数据库中,这里不再重复贴出代码了。
在这里,我们提到了产生授权码的时机,也分析了授权码管理类的实现方式,那么授权码是什么时候被消费的呢?这里涉及到了AuthorizationCodeTokenGranter类,我们在后续的章节中再专门分析该类的实现和使用方式。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。