spring-security-oauth2核心类源码解析
2018-02-06 18:45
771 查看
获取authorization_code相关类解析
类UsernamePasswordAuthenticationFilter(extends AbstractAuthenticationProcessingFilter)
//主要功能就是获取用户的用户名和密码,并创建UsernamePasswordAuthenticationToken 传递给 ProviderManager 进行身份证认证 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); //提交给AuthenticationManager 进行身份认证 ---- > 类 AbstractUserDetailsAuthenticationProvider return this.getAuthenticationManager().authenticate(authRequest); }
类AuthorizationEndpoint (extends AbstractEndpoint)
//主要判断请求用户是否已经被用户授权,若已授权则返回新的authorization_code , 反之 跳转到用户授权页面
@RequestMapping(value = "/oauth/authorize") public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) { //根据请求参数封装 认证请求对象 ---- > AuthorizationRequest // Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should // query off of the authorization request instead of referring back to the parameters map. The contents of the // parameters map will be stored without change in the AuthorizationRequest object once it is created. AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters); //获取请求中的response_type类型,并检验; 此方法只支持 code 类型和 token类型 Set<String> responseTypes = authorizationRequest.getResponseTypes(); if (!responseTypes.contains("token") && !responseTypes.contains("code")) { throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes); } if (authorizationRequest.getClientId() == null) { throw new InvalidClientException("A client id must be provided"); } try { if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) { throw new InsufficientAuthenticationException( "User must be authenticated with Spring Security before authorization can be completed."); } //获取客户端详情 ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId()); // The resolved redirect URI is either the redirect_uri from the parameters or the one from // clientDetails. Either way we need to store it on the AuthorizationRequest. String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI); // 如果数据库中配置了Client的redirect client则请求的redirect URL必须与数据库中配置的相匹配OK //如果数据库中无配置,则直接返回请求中携带的redirect url String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client); if (!StringUtils.hasText(resolvedRedirect)) { throw new RedirectMismatchException( "A redirectUri must be either supplied or preconfigured in the ClientDetails"); } authorizationRequest.setRedirectUri(resolvedRedirect); //根据ClientDetail 校验请求的scoppe // We intentionally only validate the parameters requested by the client (ignoring any data that may have // been added to the request by the manager). oauth2RequestValidator.validateScope(authorizationRequest, client); // 此处检测请求的用户是否已经被授权,或者有配置默认授权的权限; 若已经有accessToke存在或者被配置默认授权的权限则返回含有授权的对象 //用到userApprovalHandler---- > TokenStoreUserApprovalHandler // Some systems may allow for approval decisions to be remembered or approved by default. Check for // such logic here, and set the approved flag on the authorization request accordingly. authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,(Authentication) principal); // TODO: is this call necessary? boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal); authorizationRequest.setApproved(approved); //若已经授权则直接返回对应的视图,返回的视图中包含新生成的authorization_code(固定长度的随机字符串)值,此处新生成的code会存与库中 // Validation is all done, so we can check for auto approval... if (authorizationRequest.isApproved()) { if (responseTypes.contains("token")) { return getImplicitGrantResponse(authorizationRequest); } if (responseTypes.contains("code")) { return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal)); } } // Place auth request into the model so that it is stored in the session // for approveOrDeny to use. That way we make sure that auth request comes from the session, // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session. model.put("authorizationRequest", authorizationRequest); //未被授权者跳转到授权界面,让用户选择是否授权 return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal); } catch (RuntimeException e) { sessionStatus.setComplete(); throw e; } }
//用于处理用户授权页面的结果 , 用户是否授予第三方权限 ; 请求中必须包参数 user_oauth_approval @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL) public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal) { if (!(principal instanceof Authentication)) { sessionStatus.setComplete(); throw new InsufficientAuthenticationException( "User must be authenticated with Spring Security before authorizing an access token."); } //获取当前session中存放的 authorizationRequest AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest"); if (authorizationRequest == null) { sessionStatus.setComplete(); throw new InvalidRequestException("Cannot approve uninitialized authorization request."); } try { Set<String> responseTypes = authorizationRequest.getResponseTypes(); authorizationRequest.setApprovalParameters(approvalParameters); //根据用户是否授权更新 authorizationRequest 对象中的 approved 属性; 授予为true authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,(Authentication) principal); boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal); authorizationRequest.setApproved(approved); if (authorizationRequest.getRedirectUri() == null) { sessionStatus.setComplete(); throw new InvalidRequestException("Cannot approve request when no redirect URI is provided."); } if (!authorizationRequest.isApproved()) { return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")), false, true, false); } if (responseTypes.contains("token")) { return getImplicitGrantResponse(authorizationRequest).getView(); } //生成Authorization_code 并储存,返回给客户端 return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal); } finally { sessionStatus.setComplete(); } }
获取AccessToken请求相关类解析
类 ClientCredentialsTokenEndpointFilter (extends AbstractAuthenticationProcessingFilter)此Filter是在用户已经进行过身份认证,并且已经通过的条件下
//此方法主要作用为:提取客户端的Client_id 和 Client_secret 传递给你AuthenticationManager进行认证 @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { //验证请求的方法是否支持 , POST 、 GET 此处只支持POST方法 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"); //如果身份已经认证 直接放行,无需在进行认证 //(此处有疑问,SecurityContextHolder.getContext() 获取的Authentication一定是当前用户的认证信息吗,如何保证??????????) // 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); //提交给AuthenticationManager 进行身份认证 ---- > 类 AbstractUserDetailsAuthenticationProvider return this.getAuthenticationManager().authenticate(authRequest); }
类 AbstractUserDetailsAuthenticationProvider
(implements AuthenticationProvider,InitializingBean, MessageSourceAware)
//此方法主要功能为:1、 通过用户名(或客户端ID)获取用户信息(客户端信息);2、账号检验(基本检验、密码检验);3、账号检验通过则创建UsernamePasswordAuthenticationToken对象 public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported")); // Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { //获取用户的先关信息(此处的用户可能为客户端Client) 需要 userDetailsService 或者 clientDetailsService ----- > DaoAuthenticationProvider user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { logger.debug("User '" + username + "' not found"); if (hideUserNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { throw notFound; } } Assert.notNull(user,"retrieveUser returned null - a violation of the interface contract"); } try { //账号的基本检测,如:是否不可用、是否锁定、是否过期 preAuthenticationChecks.check(user); //检验账号密码是否正确 additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } } //账号的基本检测,如:是否不可用、是否锁定、是否过期 postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } //此处会根据传入参数创建UsernamePasswordAuthenticationToken对象并返回 return createSuccessAuthentication(principalToReturn, authentication, user); }
类 TokenEndpoint (extends AbstractEndpoint)
//此方法的主要作用为:1、获取客户端详情并根据请求参数组装TokenRequest;2、校验请求的Scope;3、为客户端是生成Token @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."); } //通过clientId 获取客户端详情 String clientId = getClientId(principal); ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); //获取请求的相关参数,如grant_type,client_id,scope(此处会依据用户的权限进行过滤)等, 封装组建TokenRequest 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"); } } //对客户端传入的Scope进行校验 , Scope超限将直接抛出异常 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"); } // grant_type=authorzation_code 时清空scope 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()); } } //grant_type=refresh_token 时 需要设置scope ,因为它有自己的scope 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))); } //为客户端生成token (此处会验证客户端的grant_type,只有检验通过才会生成Token) ------- >类 DefaultTokenServices OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); if (token == null) { throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType()); } //封装返回 return getResponse(token); }
类 DefaultTokenServices
(Implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean)
//生成accessToken和RefreshToken @Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { //首先尝试获取当前存在的Token OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); OAuth2RefreshToken refreshToken = null; //如果accesToken已存在并且没有失效,则重新保存并返回;如果accessToken失效,则提取refrehToken并清除老的AccessToken; //如果accessToken为null,则直接生成新的Token if (existingAccessToken != null) { if (existingAccessToken.isExpired()) { if (existingAccessToken.getRefreshToken() != null) { refreshToken = existingAccessToken.getRefreshToken(); // The token store could remove the refresh token when the // access token is removed, but we want to // be sure... tokenStore.removeRefreshToken(refreshToken); } tokenStore.removeAccessToken(existingAccessToken); } else { // Re-store the access token in case the authentication has changed tokenStore.storeAccessToken(existingAccessToken, authentication); return existingAccessToken; } } //老的refreshToken 不失效将会复用,失效的话生成新的RefresshToken // Only create a new refresh token if there wasn't an existing one // associated with an expired access token. // Clients might be holding existing refresh tokens, so we re-use it in // the case that the old access token // expired. if (refreshToken == null) { refreshToken = createRefreshToken(authentication); } // But the refresh token itself might need to be re-issued if it has // expired. else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken; if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { refreshToken = createRefreshToken(authentication); } } //生成新的accessToken,并储存 , 保存refreshToken OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); // In case it was modified refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken; }
资源访问相关类
OAuth2AuthenticationProcessingFilter类//主要功能就是获取请求中携带的Token,然后通过Token提取Authentication,然后将Authentication放入上下文 ; 获取Authentication成功将会允许访问资源 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final boolean debug = logger.isDebugEnabled(); final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; try { //提取请求携带的Token组建一个认证Authentication ,Token提取过程:从Hander和url中获取携带的Token Authentication authentication = tokenExtractor.extract(request); if (authentication == null) { if (stateless && isAuthenticated()) { if (debug) { logger.debug("Clearing security context."); } SecurityContextHolder.clearContext(); } if (debug) { logger.debug("No token in request, will continue chain."); } } else { request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal()); if (authentication instanceof AbstractAuthenticationToken) { AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication; needsDetails.setDetails(authenticationDetailsSource.buildDetails(request)); } //获取Token携带的认证信息 , Oauth2AuthenticationMananger主要做三件是1、通过token获取用户的Oauth2Authentcation对象(TokenServices);2、验证访问的资源resourceId是否符合范围;3、验证客户端访问的Scope(clientDetailsService) Authentication authResult = authenticationManager.authenticate(authentication); if (debug) { logger.debug("Authentication success: " + authResult); } //将当前的Authentication 放入Context中 ,访问后面资源 eventPublisher.publishAuthenticationSuccess(authResult); SecurityContextHolder.getContext().setAuthentication(authResult); } } catch (OAuth2Exception failed) { SecurityContextHolder.clearContext(); if (debug) { logger.debug("Authentication request failed: " + failed); } eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), new PreAuthenticatedAuthenticationToken("access-token", "N/A")); authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(failed.getMessage(), failed)); return; } chain.doFilter(request, response); }
类OAuth2AuthenticationManager (implementsAuthenticationManager,InitializingBean)
Oauth2AuthenticationMananger主要做三件是:
1. 通过token获取用户的Oauth2Authentcation对象(TokenServices);
2. 验证访问的资源resourceId是否符合范围;
3. 验证客户端访问的Scope(clientDetailsService)
public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication == null) { throw new InvalidTokenException("Invalid token (token not found)"); } String token = (String) authentication.getPrincipal(); //通过token获取用户的Oauth2Authentcation对象(TokenServices); OAuth2Authentication auth = tokenServices.loadAuthentication(token); if (auth == null) { throw new InvalidTokenException("Invalid token: " + token); } //验证访问的资源resourceId是否符合范围; Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds(); if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) { throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")"); } //验证客户端访问的Scope(clientDetailsService) checkClientDetails(auth); if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) { OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails(); // Preserve the authentication details if any from the one loaded by token services details.setDecodedDetails(auth.getDetails()); } auth.setDetails(authentication.getDetails()); auth.setAuthenticated(true); return auth; }
相关文章推荐
- spring 核心与源码解析(0):前言
- (八)Spring核心框架 - AOP的原理及源码解析
- Spring核心框架 - AOP的原理及源码解析
- spring-security核心类解析--整理中....
- Spring核心框架 - AOP的原理及源码解析
- spring 核心与源码解析(1):IoC之BeanFactory
- spring 核心与源码解析(2):IoC之ApplicationContext
- (六)Spring核心框架 - IOC的源码解析
- spring 核心与源码解析(3):AOP如何使用
- spring-security核心类解析--整理中....
- Spring源码解析(五)——自定义标签解析
- spark SQL源码阅读002——sql.core包核心类——002执行SQL语法2次解析SQL词(analyse)
- Spring源码解析(八)——实例创建(下)
- Spring源码追踪4——SpringMVC View解析
- Spring源码解析:默认标签的解析过程
- 网关 Spring-Cloud-Gateway 源码解析 —— 调试环境搭建
- springboot源码解析:自己实现一个springboot自动配置
- 附4 springboot源码解析-run()
- 【第二章:源码解析】Spring的BeanFactory的接口注解
- Spring 源码解析 ---- 事件监听