赞
踩
在《授权服务器是如何实现授权的呢?》中,我们可以了解到服务端实现授权的流程,同时知道,当授权端点AuthorizationEndpoint生成授权码时,就会重定向到客户端的请求地址,这个时候,客户端就会拿着授权码再来授权服务器换取对应的Token,这篇内容,我们就详细分析如何使用授权码code换取Token的。在前面文章中,我们可以了解到客户端是通过“/oauth/token”来换取token的,该接口对应TokenEndpoint类的postAccessToken()方法,我们这篇文章就围绕获取token的TokenEndpoint类进行。
TokenEndpoint 是OAuth2规范中描述的令牌请求的端点,主要实现客户端获取token的能力,提供了"/oauth/token"接口,暴露给客户端用来获取Token。
和授权端点AuthorizationEndpoint 类似,令牌请求端点TokenEndpoint 也继承自AbstractEndpoint抽象类,在《SpringSecurity OAuth2授权端点AuthorizationEndpoint、授权码AuthorizationCodeServices 详解》中,已经分析了AbstractEndpoint抽象类的实现,主要是初始化了TokenGranter、ClientDetailsService、OAuth2RequestFactory和WebResponseExceptionTranslator等对象,这里不再贴出代码进行分析了。
和授权端点AuthorizationEndpoint相比,令牌请求端点TokenEndpoint更加简单一些,因为这里只提供了一个Post类型的"/oauth/token"token请求接口(GET类型的token请求接口忽略,实际是调用POST方式实现的,默认不开启),而授权端点AuthorizationEndpoint涉及到授权接口、授权同意接口等,在授权过程中还涉及到了用户交互操作。
令牌请求端点TokenEndpoint,提供了"/oauth/token"接口,暴露给客户端用来获取Token。默认只支持POST方法,可以通过allowedRequestMethods配置运行GET方法。
该方法的调用发生在授权请求之后,跳转到业务界面之前,即需要访问授权的业务页面时,使用获取的授权码code,来换取对应的token,不过该步骤对前端的浏览器是不可见的,发生授权服务器和业务客户端之间的请求。
"/oauth/token"接口对应的postAccessToken()方法,实现的逻辑如下:
protected String getClientId(Principal principal) {
Authentication client = (Authentication) principal;
if (!client.isAuthenticated()) {
throw new InsufficientAuthenticationException("The client is not authenticated.");
}
String clientId = client.getName();
if (client instanceof OAuth2Authentication) {
// Might be a client and user combined authentication
clientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId();
}
return clientId;
}
&esmp;关于"/oauth/token"接口对应的postAccessToken()方法的完整实现如下:
@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.");
}
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("")) {
// Only validate the client details if a client authenticated during this
// request.
if (!clientId.equals(tokenRequest.getClientId())) {
// double check to make sure that the client ID in the token request is the same as that in the
// authenticated client
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
if (authenticatedClient != null) {
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
}
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
}
if (isAuthCodeRequest(parameters)) {
// The scope was requested or determined during the authorization step
if (!tokenRequest.getScope().isEmpty()) {
logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.<String> emptySet());
}
}
if (isRefreshTokenRequest(parameters)) {
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
在前面的postAccessToken()方法中,用到了ClientDetailsService的loadClientByClientId()方法获取对应ClientDetails信息,这里我们着重看一下ClientDetailsService 实现方式。
ClientDetailsService 客户端信息管理,提供了根据clientId查询客户端详细信息的方法,框架提供了InMemoryClientDetailsService和JdbcClientDetailsService两个实现类。
ClientDetailsService接口定义了一个查询客户端详细信息的接口,如下所示:
public interface ClientDetailsService {
/**
* 查询客户端详细信息
*/
ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException;
}
InMemoryClientDetailsService实现了ClientDetailsService接口,该实现类实现了客户端信息的内存存储,即存储在了定义的clientDetailsStore属性(Map<String, ClientDetails>类型)中,key对应clientId,value对应客户端详细信息ClientDetails。
除了实现了接口中定义的loadClientByClientId()方法,还提供了一个设置客户端信息的方法(即为clientDetailsStore 属性赋值)setClientDetailsStore()。具体实现如下:
public class InMemoryClientDetailsService implements ClientDetailsService {
private Map<String, ClientDetails> clientDetailsStore = new HashMap<String, ClientDetails>();
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
ClientDetails details = clientDetailsStore.get(clientId);
if (details == null) {
throw new NoSuchClientException("No client with requested id: " + clientId);
}
return details;
}
public void setClientDetailsStore(Map<String, ? extends ClientDetails> clientDetailsStore) {
this.clientDetailsStore = new HashMap<String, ClientDetails>(clientDetailsStore);
}
}
JdbcClientDetailsService实现类和InMemoryClientDetailsService接口类似,不过JdbcClientDetailsService不仅实现了ClientDetailsService 接口,还实现了ClientRegistrationService接口,即提供了客户端信息的注册能力。
ClientRegistrationService接口定义,如下:
public interface ClientRegistrationService {
//增加客户端信息
void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException;
//修改客户端信息
void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException;
//更新客户端秘钥
void updateClientSecret(String clientId, String secret) throws NoSuchClientException;
//删除客户端信息
void removeClientDetails(String clientId) throws NoSuchClientException;
//查询客户端信息
List<ClientDetails> listClientDetails();
}
其中,AuthorizationCodeTokenGranter 授权码模式、ClientCredentialsTokenGranter 客户端模式、ImplicitTokenGranter implicit 模式、RefreshTokenGranter 刷新 token 模式、ResourceOwnerPasswordTokenGranter 密码模式。组合代理类 CompositeTokenGranter。
TokenGranter 接口有两个子类,其中AbstractTokenGranter 抽象类是 TokenGranter 接口的通用实现,其他真正实现TokenGranter 功能的类,都继承自AbstractTokenGranter 抽象类,而CompositeTokenGranter子类主要是为了组合使用其他TokenGranter实现类。
public interface TokenGranter {
//生成Token
OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest);
}
在AbstractTokenGranter抽象类中,定义了AuthorizationServerTokenServices、ClientDetailsService、OAuth2RequestFactory和grantType四个字段,并在提供了一个带四个参数的构造函数。
grant()方法的实现逻辑:首先,验证grantType是否匹配,然后通过clientDetailsService对象获取客户端ClientDetails信息,验证客户端是否支持当前请求的grantType类型,最后再通过getAccessToken()方法获取OAuth2AccessToken对象。具体实现如下:
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);
if (logger.isDebugEnabled()) {
logger.debug("Getting access token for: " + clientId);
}
return getAccessToken(client, tokenRequest);
}
在getAccessToken()方法中,首先通过调用getOAuth2Authentication()方法,获取OAuth2Authentication对象,然后又通过tokenServices的createAccessToken()方法创建Token对象,具体实现如下:
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
在getOAuth2Authentication()方法中,首先通过OAuth2RequestFactory对象创建storedOAuth2Request对象,然后根据该对象创建OAuth2Authentication实例并返回,具体实现如下:
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, null);
}
在抽象类AbstractTokenGranter的子类中,就是通过重写grant()方法、getAccessToken()方法或getOAuth2Authentication()方法实现对应功能的。我们分别进行分析:
RefreshTokenGranter子类,是通过重写getAccessToken()方法实现刷新Token功能的,在抽象类中定义的getAccessToken()方法是通过AuthorizationServerTokenServices的createAccessToken()方法创建对象的,而这里通过调用AuthorizationServerTokenServices的refreshAccessToken()方法实现刷新token的功能,关于AuthorizationServerTokenServices后续后专门详细分析,这里暂不展开,具体实现如下:
//RefreshTokenGranter.java
@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
}
ClientCredentialsTokenGranter子类,是通过重写grant()方法实现客户端授权功能的,首先通过调用父类的grant()方法获取对应OAuth2AccessToken 对象,然后再根据allowRefresh参数设置refreshToken为空即可,说明一般客户端的认证不允许刷新token。
//ClientCredentialsTokenGranter.java
@Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
OAuth2AccessToken token = super.grant(grantType, tokenRequest);
if (token != null) {
DefaultOAuth2AccessToken norefresh = new DefaultOAuth2AccessToken(token);
// The spec says that client credentials should not be allowed to get a refresh token
if (!allowRefresh) {
norefresh.setRefreshToken(null);
}
token = norefresh;
}
return token;
}
ResourceOwnerPasswordTokenGranter子类,是通过重写getOAuth2Authentication()方法实现资源授权功能的。
在ResourceOwnerPasswordTokenGranter子类中,又增加了一个AuthenticationManager字段的定义,主要用来实现用户名密码的验证,并生成对应的Authentication对象,具体实现如下:
//ResourceOwnerPasswordTokenGranter.java
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String username = parameters.get("username");
String password = parameters.get("password");
// Protect from downstream leaks of password
parameters.remove("password");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
在ResourceOwnerPasswordTokenGranter重写的getOAuth2Authentication()方法中,首先获取用户名密码构建UsernamePasswordAuthenticationToken对象,然后通过AuthenticationManager进行校验,返回校验过的Authentication对象,否则就会抛出对应的异常,认证成功后,再通过OAuth2RequestFactory创建OAuth2Request对象,最后new一个OAuth2Authentication实例对象并返回,并在父类中根据该对象创建OAuth2AccessToken对象。即该过程中,首先完成了用户名密码的校验,然后才生成对应的token。
AuthorizationCodeTokenGranter子类,和ResourceOwnerPasswordTokenGranter类一样,都是通过重写getOAuth2Authentication()方法实现对应功能的。但是在AuthorizationCodeTokenGranter类中,引入了AuthorizationCodeServices属性,通过调用consumeAuthorizationCode()方法,获取授权码对应的用户认证信息OAuth2Authentication,然后再根据认证信息获取存储的OAuth2Request对象,再获取其中的redirectUri和ClientId参数与调用传递参数对比校验,再创建新的OAuth2Request对象,并结合获取的Authentication对象,new一个OAuth2Authentication实例对象进行返回,并在父类中根据该对象创建OAuth2AccessToken对象。具体实现如下:
//AuthorizationCodeTokenGranter.java
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = 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();
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)) {
throw new InvalidClientException("Client ID mismatch");
}
Map<String, String> combinedParameters = new HashMap<String, String>(pendingOAuth2Request
.getRequestParameters());
combinedParameters.putAll(parameters);
OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters);
Authentication userAuth = storedAuth.getUserAuthentication();
return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);
}
ImplicitTokenGranter子类,和AuthorizationCodeTokenGranter 、ResourceOwnerPasswordTokenGranter类一样,都是通过重写getOAuth2Authentication()方法实现对应功能的。不过在ImplicitTokenGranter重写的getOAuth2Authentication()方法中,不需要再做校验,直接获取SpringSecurity上下文中存储的用户认证信息即可,具体实现如下:
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest clientToken) {
Authentication userAuth = SecurityContextHolder.getContext().getAuthentication();
if (userAuth==null || !userAuth.isAuthenticated()) {
throw new InsufficientAuthenticationException("There is no currently logged in user");
}
Assert.state(clientToken instanceof ImplicitTokenRequest, "An ImplicitTokenRequest is required here. Caller needs to wrap the TokenRequest.");
OAuth2Request requestForStorage = ((ImplicitTokenRequest)clientToken).getOAuth2Request();
return new OAuth2Authentication(requestForStorage, userAuth);
}
TokenGranter接口的CompositeTokenGranter实现类,这里不再详细信息,就是代理真的实现类使用,可以组合多种TokenGranter实现类,循环调用即可。
在TokenGranter接口的几个实现类中,仔细回想一下,其实真正实现token生成的其实是AuthorizationServerTokenServices对象,在RefreshTokenGranter实现类中是通过调用refreshAccessToken()方法实现,而在AuthorizationCodeTokenGranter、ImplicitTokenGranter和ResourceOwnerPasswordTokenGranter三个子类中,是通过重写getOAuth2Authentication()方法,获取对应的认证信息-OAuth2Authentication对象,然后再使用获取到的认证信息,调用AuthorizationServerTokenServices对象的createAccessToken()方法来生成token对象,而ClientCredentialsTokenGranter实现类则是直接使用了抽象类中的定义方法,实际上也是通过AuthorizationServerTokenServices对象的createAccessToken()方法来生成token对象,所以归根结底,生成token的方法又落到了AuthorizationServerTokenServices对象上。后续,我们专门一篇博文分析AuthorizationServerTokenServices的实现。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。