獲取access_token
- 其實(shí)debug發(fā)現(xiàn)描馅,
clientId和clientSecret也會(huì)在org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter進(jìn)行校驗(yàn)眠寿,形成一個(gè)UsernamePasswordAuthenticationToken 姨丈,權(quán)限就是oauth_client_details表中配置的authorities衩婚。
這里需要注意,設(shè)置了security.allowFormAuthenticationForClients();粥惧,使得clientId和clientSecret可以以拼接的方式加上參數(shù)抛腕,如果不配置得用basic來(lái)裝載,如果是使用basic啰劲,就不是走ClientCredentialsTokenEndpointFilter,而是走BasicAuthenticationFilter檀何,在返回統(tǒng)一json結(jié)果時(shí)蝇裤,需要走BasicAuthenticationFilter,在文章《http://www.reibang.com/p/5a76d246b37f
》說(shuō)明频鉴。
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" });
}
String clientId = request.getParameter("client_id");
String clientSecret = request.getParameter("client_secret");
// If the request is already authenticated we can assume that this
// filter is not needed
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
return authentication;
}
if (clientId == null) {
throw new BadCredentialsException("No client credentials presented");
}
if (clientSecret == null) {
clientSecret = "";
}
clientId = clientId.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
clientSecret);
return this.getAuthenticationManager().authenticate(authRequest);
}
- 查看源碼:
org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
@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);
}
獲取access_token栓辜,本來(lái)是post請(qǐng)求的,而且clientId和clientSecret不能放在url中垛孔,要想它們可以這樣做藕甩,需要設(shè)置一下,在 繼承 AuthorizationServerConfigurerAdapter類中重寫中
configure(AuthorizationServerEndpointsConfigurer endpoints) 方法 allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
configure(AuthorizationServerSecurityConfigurer security) 方法 allowFormAuthenticationForClients()
設(shè)置如下:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(passwordEncoder);
// 開啟/oauth/check_token驗(yàn)證端口認(rèn)證權(quán)限訪問(wèn)
security.checkTokenAccess("isAuthenticated()");
// 開啟/oauth/token_key驗(yàn)證端口無(wú)權(quán)限訪問(wèn)
security.tokenKeyAccess("permitAll()");
/*
*
* 主要是讓/oauth/token支持client_id和client_secret做登陸認(rèn)證
* 如果開啟了allowFormAuthenticationForClients周荐,那么就在BasicAuthenticationFilter之前
* 添加ClientCredentialsTokenEndpointFilter,使用ClientDetailsUserDetailsService來(lái)進(jìn)行登陸認(rèn)證
*
*/
security.allowFormAuthenticationForClients();
}
- 請(qǐng)求后狭莱,一如既往,還是根據(jù)clientId去查詢相關(guān)信息概作,比較scope是否一致腋妙、是否存在該clientId,是否存在grantType參數(shù)以及它是不是為authorization_code讯榕,有無(wú)code授權(quán)碼骤素,都驗(yàn)證無(wú)問(wèn)題,就生成access_token,其實(shí)就是一個(gè)uuid愚屁。
這里要注意的是
- org.springframework.security.oauth2.provider.token.DefaultTokenServices 這個(gè)類中創(chuàng)建access_token济竹,注意有效期
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
- 具體以下,得知access_token第一選擇是數(shù)據(jù)庫(kù)中配置的集绰,其次是DefaultTokenService中的默認(rèn)12個(gè)小時(shí)规辱。其實(shí)我們還可以在配置中進(jìn)行設(shè)置的,還有refresh_token也是該原理栽燕!
private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.
protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getAccessTokenValiditySeconds();
if (validity != null) {
return validity;
}
}
return accessTokenValiditySeconds;
}
public class Oauth2JdbcAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenServices(customTokenService());
}
}
@Bean
public DefaultTokenServices customTokenService(){
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setAccessTokenValiditySeconds(3);
tokenServices.setRefreshTokenValiditySeconds(6);
return tokenServices;
}
-
授權(quán)碼code參數(shù)只能用一次,因?yàn)樵讷@取access_token的時(shí)候改淑,已經(jīng)做了刪除code的操作碍岔,已存在的token,再用新code請(qǐng)求朵夏,只要token還在有效期內(nèi)蔼啦,就會(huì)返回已存在的token,只是有效期是延續(xù)之前的仰猖,而不是重新完整有效期捏肢!沿著代碼追尋下去可得:
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);
}
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
//找到這個(gè)類的授權(quán)碼模式得繼承類奈籽,如下圖
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, null);
}