1.工作原理及过程
OAuth 2.0的运行流程如下图
本篇案列主要基于授权码模式(authorization code)一下是授权码模式的运行流程如下图
2.时序图展示
3.核心源码解析
基于spring security Oauth2主要就三个类:
1.AbstractAuthenticationProcessingFilter 主要是用来对用户的访问请求进行拦截认证的入口
2.AuthorizationEndpoint 主要是匹配授权模式(1.授权码模式 2:隐匿模式 3:客户端模式 4密码模式)返回不同的ModelAndView(重定向到login路由)对象 并且携带凭证、权限信息
3.TokenPoint 校验凭证信息 并且颁发Token
3.1.1 AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter.doFilter()方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//判断该请求是否需要进行权限校验 内部调用requiresAuthenticationRequestMatcher.matches
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
//对该请求的用户信息进行认证返回的是一个Authentication对象
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//认证成功 封装request请求 并将authentication存储到上下文对象中最后进行路由转发
this.successfulAuthentication(request, response, chain, authResult);
}
}
这里重点提一下requiresAuthentication方法内部调用的是requiresAuthenticationRequestMatcher方法
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return this.requiresAuthenticationRequestMatcher.matches(request);
}
我们在进行spring security oauth2的配置的时候会有这么一个类WebSecurityConfigurerAdapter 的自定义实现的子类里面的antMatchers方法这里面 .antMatchers("/", "/login**")被装载进requiresAuthenticationRequestMatcher。
@EnableOAuth2Sso
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**")
.permitAll()
.anyRequest()
.authenticated();
}
}
3.1.2 AuthorizationEndpoint
AuthorizationEndpoint.auhorize方法
@RequestMapping({"/oauth/authorize"})
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) {
//根据response_type、client_id、state、redirect_uri生成封装的authorizationRequest
AuthorizationRequest authorizationRequest = this.getOAuth2RequestFactory().createAuthorizationRequest(parameters);
//获取返回类型这里默认值是code
Set<String> responseTypes = authorizationRequest.getResponseTypes();
if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
} else if (authorizationRequest.getClientId() == null) {
throw new InvalidClientException("A client id must be provided");
} else {
try {
if (principal instanceof Authentication && ((Authentication)principal).isAuthenticated()) {
//根据client_id获取ClinetDetails对象 这里我们默认是在内存中进行配置的,所以调用的是inMemoryClientDetailsService
ClientDetails client = this.getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
//获取重定向的uri 这里默认是login 路由
String redirectUriParameter = (String)authorizationRequest.getRequestParameters().get("redirect_uri");
String resolvedRedirect = this.redirectResolver.resolveRedirect(redirectUriParameter, client);
if (!StringUtils.hasText(resolvedRedirect)) {
throw new RedirectMismatchException("A redirectUri must be either supplied or preconfigured in the ClientDetails");
} else {
//设置重定向路由
authorizationRequest.setRedirectUri(resolvedRedirect);
//验证scope范围
this.oauth2RequestValidator.validateScope(authorizationRequest, client);
authorizationRequest = this.userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication)principal);
//用户是否许可、一种是默认方式为true 一种是手动授权方式
boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal);
authorizationRequest.setApproved(approved);
if (authorizationRequest.isApproved()) {
if (responseTypes.contains("token")) {
return this.getImplicitGrantResponse(authorizationRequest);
}
if (responseTypes.contains("code")) {
//返回视图重定向到登录页面并且携带用户信息、client等信息
return new ModelAndView(this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal));
}
}
model.put("authorizationRequest", authorizationRequest);
model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", this.unmodifiableMap(authorizationRequest));
return this.getUserApprovalPageResponse(model, authorizationRequest, (Authentication)principal);
}
} else {
throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed.");
}
} catch (RuntimeException var11) {
sessionStatus.setComplete();
throw var11;
}
}
}
3.1.2 TokenPoint
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.");
} else {
//获取clientId信息
String clientId = this.getClientId(principal);
//获取ClientDetails信息
ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
} else {
if (authenticatedClient != null) {
this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
} else if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
} else {
if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
this.logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.emptySet());
}
if (this.isRefreshTokenRequest(parameters)) {
tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
}
//调用DefaultTokenService 的createAccessToken方法
OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
} else {
return this.getResponse(token);
}
}
}
}
}
生成token
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = this.getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (long)validitySeconds * 1000L));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return (OAuth2AccessToken)(this.accessTokenEnhancer != null ? this.accessTokenEnhancer.enhance(token, authentication) : token);
}
总结:
访问受限资源-------filter拦截-------验证是否认证没有---------定向到登录页面-------------filter拦截---------------进行用户身份校验-通过----------生成authenticaiotn信息并且定向到/oauth/authorize----------对authentication进行一些校验、授权、封装重定向到登录页面--------filter拦截-重现走一遍认证逻辑--------颁发token