2019独角兽企业重金招聘Python工程师标准>>>
Springsecurity-oauth2的版本是2.2.1.RELEASE.
使用postman进行/oauth/token的时候,服务端Springsecurity是怎么处理的呢?
图1
图2
图3
上面的图2和图3,我们就会从服务端获得token。
来看BasicAuthenticationFilter的实现,如下List-1所示
List-1
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean debug = this.logger.isDebugEnabled();
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Basic ")) {
try {
String[] tokens = this.extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String username = tokens[0];
if (debug) {
this.logger.debug("Basic Authentication Authorization header found for user '" + username + "'");
}
if (this.authenticationIsRequired(username)) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, tokens[1]);
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
Authentication authResult = this.authenticationManager.authenticate(authRequest);
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
this.onSuccessfulAuthentication(request, response, authResult);
}
} catch (AuthenticationException var10) {
SecurityContextHolder.clearContext();
if (debug) {
this.logger.debug("Authentication request for failed: " + var10);
}
this.rememberMeServices.loginFail(request, response);
this.onUnsuccessfulAuthentication(request, response, var10);
if (this.ignoreFailure) {
chain.doFilter(request, response);
} else {
this.authenticationEntryPoint.commence(request, response, var10);
}
return;
}
chain.doFilter(request, response);
} else {
chain.doFilter(request, response);
}
}
private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
} catch (IllegalArgumentException var7) {
throw new BadCredentialsException("Failed to decode basic authentication token");
}
String token = new String(decoded, this.getCredentialsCharset(request));
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
} else {
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
}
BasicAuthenticationFilter会判断request头部是否有Authorization,且该字段的值是否以"Basic "开头,之后获得"Basic "后面的值,看extractAndDecodeHeader的实现,得到ClientID和ClientSecrect,之后会调用ClientDetailsService,获得Client及Client secrect的信息。将得到的Authentication放入SecurityContextHolder.getContext().setAuthentication()放入到Context中,这样SpringSecurity的FilterChainProxy后续Filter就不会跑出异常,这样请求就能顺利到达处理/oauth/token的EndPoint——看org.springframework.security.oauth2.provider.endpoint.TokenEndpoint。
List-2
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
......
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity postAccessToken(Principal principal, @RequestParam
Map 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. 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);
}
如上List-2所示,到了/oauth/token后,还会再次调用ClientDetailService获取ClientId和ClientSecrect,之后用我们请求的几个参数,构造TokenRequest,这个类就是POJO,没有什么。之后用TokenGranter构造OAuth2AccessToken,TokenGranter的OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest)方法,用我们请求的参数,构造OAuth2AccessToken。TokenGranter间接调用ResourceOwnerPasswordTokenGranter,之后调用ProviderManager,ProviderManager再调用AuthenticationManager,AuthenticationManager调用DaoAuthenticationProvider,从数据库中获取用户信息,之后移除password,之后创建Token。
经过源码分析,图3中的access_token是JDK的UUID值,如下List-3中,new DefaultOAuth2AccessToken时,UUID.randoUUID().toString()的值作为参数传入。
List-3
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
......
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;
}
......
如List-4所示,先创建OAuth2RefreshToken(是interface,真实是其实现类DefaultOAuth2RefreshToken),在方法createRefreshToken中可以看到,refresh_token的值也是JDK的UUID,之后在创建OAuth2AccessToken(是interface,真实是其实现类DefaultOAuth2AccessToken),传入作为返回客户端的refresh_token,也就是图3中的refresh_token,所以有源码可知,access_token和refresh_token都是JDK的UUID.randomUUID().toString。
List-4
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
......
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
......
return accessToken;
}
private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
return null;
}
int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
String value = UUID.randomUUID().toString();
if (validitySeconds > 0) {
return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis()
+ (validitySeconds * 1000L)));
}
return new DefaultOAuth2RefreshToken(value);
}
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;
}
在使用oauth2会遇到clientId、clientSecret、accessTokenValiditySeconds、refreshTokenValiditySeconds、additionalInformation,这些可以在ClientDetails的实现类BaseClientDetails中看到。
accessTokenValiditySeconds是accessToken过期时间,refreshTokenValiditySeconds是refreshToken过期时间。
OAuth2AccessTokenJackson1Serializer/OAuth2AccessTokenJackson2Serializer用这个做的序列化,OAuth2AccessToken这个类上有注解。OAuth2AccessToken的实现类DefaultOAuth2AccessToken也只是POJO,并无额外的逻辑,在序列化到HttpResponse时用了jackson的序列化工具,所以我们可以看到返回有access_token、refresh_token字段