获取access_token
- 其实debug发现,
clientId和clientSecret也会在org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter进行校验,形成一个UsernamePasswordAuthenticationToken ,权限就是oauth_client_details表中配置的authorities。
这里需要注意,设置了security.allowFormAuthenticationForClients();,使得clientId和clientSecret可以以拼接的方式加上参数,如果不配置得用basic来装载,如果是使用basic,就不是走ClientCredentialsTokenEndpointFilter,而是走BasicAuthenticationFilter,在返回统一json结果时,需要走BasicAuthenticationFilter,在文章《https://www.jianshu.com/p/5a76d246b37f
》说明。
@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,本来是post请求的,而且clientId和clientSecret不能放在url中,要想它们可以这样做,需要设置一下,在 继承 AuthorizationServerConfigurerAdapter类中重写中
configure(AuthorizationServerEndpointsConfigurer endpoints) 方法 allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
configure(AuthorizationServerSecurityConfigurer security) 方法 allowFormAuthenticationForClients()
设置如下:
@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验证端口认证权限访问
security.checkTokenAccess("isAuthenticated()");
// 开启/oauth/token_key验证端口无权限访问
security.tokenKeyAccess("permitAll()");
/*
*
* 主要是让/oauth/token支持client_id和client_secret做登陆认证
* 如果开启了allowFormAuthenticationForClients,那么就在BasicAuthenticationFilter之前
* 添加ClientCredentialsTokenEndpointFilter,使用ClientDetailsUserDetailsService来进行登陆认证
*
*/
security.allowFormAuthenticationForClients();
}
- 请求后,一如既往,还是根据clientId去查询相关信息,比较scope是否一致、是否存在该clientId,是否存在grantType参数以及它是不是为authorization_code,有无code授权码,都验证无问题,就生成access_token,其实就是一个uuid。
这里要注意的是
- org.springframework.security.oauth2.provider.token.DefaultTokenServices 这个类中创建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第一选择是数据库中配置的,其次是DefaultTokenService中的默认12个小时。其实我们还可以在配置中进行设置的,还有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;
}
-
授权码code参数只能用一次,因为在获取access_token的时候,已经做了删除code的操作,已存在的token,再用新code请求,只要token还在有效期内,就会返回已存在的token,只是有效期是延续之前的,而不是重新完整有效期!沿着代码追寻下去可得:
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));
}
//找到这个类的授权码模式得继承类,如下图
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, null);
}