当前位置:   article > 正文

Spring Security Oauth2系列(三)_org.springframework.security.oauth2.core.oauth2aut

org.springframework.security.oauth2.core.oauth2authenticationexception: clie

首先在讲这个话题之前,我想把自己遇见的最大的问题分享给大家

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sat Mar 17 14:24:30 CST 2018
There was an unexpected error (type=Unauthorized, status=401).
Authentication Failed: Could not obtain access token

这个问题相信大家遇见得可能不是很多,可以调试跟踪client的OAuth2ClientAuthenticationProcessingFilter类中的源码(注意是client,因为这个地方是在完成了认证授权过后获取token的步骤)

调试源码篇幅比较长,有兴趣的读者可以自行尝试调试,主要是OAuth2RestTemplate类中的2个获取accessToken的方法和AuthorizationCodeAccessTokenProvider类中的obtainAccessToken的方法

错误分析:

这个错误只在使用Aut horizationCode中才会出现,就说客户端反复刷新带有code和state的参数导致的,由于code已经使用过一次,刷新过后会导致code过期,所以你反复使用会认为是非法的操作,所以可能是"CSRF攻击"。

解决方法:

  1. server:
  2. session:
  3. cookie:
  4. name: x x x

客户端配置文件添加这句话即可,有兴趣参考国外大神GitHub连接: https://github.com/eugenp/tutorials/tree/master/spring-security-sso

获取authorization_code相关类解析

开始进入正题,主要讲解与spring-security-oauth2部分相关源码,spring-security部分源码请大家在本社区的security自行了解学习org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint

主要判断请求用户是否已经被用户授权,若已授权则返回新的authorization_code , 反之 跳转到用户授权页面

  1. @RequestMapping({"/oauth/authorize"})
  2. public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) {
  3. // 根据请求参数封装 认证请求对象 ---- > AuthorizationRequest
  4. // Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
  5. // query off of the authorization request instead of referring back to the parameters map. The contents of the
  6. // parameters map will be stored without change in the AuthorizationRequest object once it is created.
  7. AuthorizationRequest authorizationRequest = this.getOAuth2RequestFactory().createAuthorizationRequest(parameters);
  8. //获取请求中的response_type类型,并检验; 此方法只支持 code 类型和 token类型
  9. Set<String> responseTypes = authorizationRequest.getResponseTypes();
  10. if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
  11. throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
  12. } else if (authorizationRequest.getClientId() == null) {
  13. throw new InvalidClientException("A client id must be provided");
  14. } else {
  15. try {
  16. //大家在自定义授权页面的时候在security中设置拦截了/oauth/authorize的,经常会出现匿名用户访问的原因就在这里,因为你不拦截的话他是不会进入security的自定义认证流程中去
  17. if (principal instanceof Authentication && ((Authentication)principal).isAuthenticated()) {
  18. //获取客户端详情
  19. ClientDetails client = this.getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
  20. // The resolved redirect URI is either the redirect_uri from the parameters or the one from
  21. // clientDetails. Either way we need to store it on the AuthorizationRequest.
  22. String redirectUriParameter = (String)authorizationRequest.getRequestParameters().get("redirect_uri");
  23. // 如果数据库中配置了Client的redirect client则请求的redirect URL必须与数据库中配置的相匹配OK
  24. //如果数据库中无配置,则直接返回请求中携带的redirect url
  25. String resolvedRedirect = this.redirectResolver.resolveRedirect(redirectUriParameter, client);
  26. if (!StringUtils.hasText(resolvedRedirect)) {
  27. throw new RedirectMismatchException("A redirectUri must be either supplied or preconfigured in the ClientDetails");
  28. } else {
  29. authorizationRequest.setRedirectUri(resolvedRedirect);
  30. //根据ClientDetail 校验请求的scoppe
  31. // We intentionally only validate the parameters requested by the client (ignoring any data that may have
  32. // been added to the request by the manager).
  33. this.oauth2RequestValidator.validateScope(authorizationRequest, client);
  34. // 此处检测请求的用户是否已经被授权,或者有配置默认授权的权限; 若已经有accessToke存在或者被配置默认授权的权限则返回含有授权的对象
  35. //用到userApprovalHandler---- > TokenStoreUserApprovalHandler
  36. // Some systems may allow for approval decisions to be remembered or approved by default. Check for
  37. // such logic here, and set the approved flag on the authorization request accordingly.
  38. authorizationRequest = this.userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication)principal);
  39. // TODO: is this call necessary?
  40. boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal);
  41. //若已经授权则直接返回对应的视图,返回的视图中包含新生成的authorization_code(固定长度的随机字符串)值,此处新生成的code会存与库中
  42. // Validation is all done, so we can check for auto approval...
  43. authorizationRequest.setApproved(approved);
  44. if (authorizationRequest.isApproved()) {
  45. if (responseTypes.contains("token")) {
  46. return this.getImplicitGrantResponse(authorizationRequest);
  47. }
  48. if (responseTypes.contains("code")) {
  49. return new ModelAndView(this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal));
  50. }
  51. }
  52. // Place auth request into the model so that it is stored in the session
  53. // for approveOrDeny to use. That way we make sure that auth request comes from the session,
  54. // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session.
  55. model.put("authorizationRequest", authorizationRequest);
  56. //未被授权者跳转到授权界面,让用户选择是否授权
  57. return this.getUserApprovalPageResponse(model, authorizationRequest, (Authentication)principal);
  58. }
  59. } else {
  60. throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed.");
  61. }
  62. } catch (RuntimeException var11) {
  63. sessionStatus.setComplete();
  64. throw var11;
  65. }
  66. }
  67. }

用于处理用户授权页面的结果 , 用户是否授予第三方权限 ; 请求中必须包参数 user_oauth_approval

  1. @RequestMapping(
  2. value = {"/oauth/authorize"},
  3. method = {RequestMethod.POST},
  4. params = {"user_oauth_approval"}
  5. )
  6. public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal) {
  7. if (!(principal instanceof Authentication)) {
  8. sessionStatus.setComplete();
  9. throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorizing an access token.");
  10. } else {
  11. //获取当前session中存放的 authorizationRequest
  12. AuthorizationRequest authorizationRequest = (AuthorizationRequest)model.get("authorizationRequest");
  13. if (authorizationRequest == null) {
  14. sessionStatus.setComplete();
  15. throw new InvalidRequestException("Cannot approve uninitialized authorization request.");
  16. } else {
  17. RedirectView var8;
  18. try {
  19. Set<String> responseTypes = authorizationRequest.getResponseTypes();
  20. authorizationRequest.setApprovalParameters(approvalParameters);
  21. //根据用户是否授权更新 authorizationRequest 对象中的 approved 属性;
  22. authorizationRequest = this.userApprovalHandler.updateAfterApproval(authorizationRequest, (Authentication)principal);
  23. boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal);
  24. authorizationRequest.setApproved(approved);
  25. if (authorizationRequest.getRedirectUri() == null) {
  26. sessionStatus.setComplete();
  27. throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");
  28. }
  29. if (authorizationRequest.isApproved()) {
  30. View var12;
  31. if (responseTypes.contains("token")) {
  32. var12 = this.getImplicitGrantResponse(authorizationRequest).getView();
  33. return var12;
  34. }
  35. //生成Authorization_code 并储存,返回给客户端
  36. var12 = this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal);
  37. return var12;
  38. }
  39. var8 = new RedirectView(this.getUnsuccessfulRedirect(authorizationRequest, new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")), false, true, false);
  40. } finally {
  41. sessionStatus.setComplete();
  42. }
  43. return var8;
  44. }
  45. }
  46. }
  47. //部分无关紧要源码省略
  48. ....................
  49. }

获取AccessToken请求相关类解析

org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter(此Filter是在用户已经进行过身份认证,并且已经通过的条件下)
  1. //此方法主要作用为:提取客户端的Client_id 和 Client_secret 传递给你AuthenticationManager进行认证
  2. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
  3. //验证请求的方法是否支持 , POST 、 GET 此处只支持POST方法
  4. if (this.allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
  5. throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[]{"POST"});
  6. } else {
  7. //获取客户端信息
  8. String clientId = request.getParameter("client_id");
  9. String clientSecret = request.getParameter("client_secret");
  10. //如果身份已经认证 直接放行,无需在进行认证
  11. //(此处有疑问,SecurityContextHolder.getContext() 获取的Authentication一定是当前用户的认证信息吗,如何保证??????????)
  12. // If the request is already authenticated we can assume that this
  13. // filter is not needed
  14. Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  15. if (authentication != null && authentication.isAuthenticated()) {
  16. return authentication;
  17. } else if (clientId == null) {
  18. throw new BadCredentialsException("No client credentials presented");
  19. } else {
  20. if (clientSecret == null) {
  21. clientSecret = "";
  22. }
  23. clientId = clientId.trim();
  24. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId, clientSecret);
  25. //提交给AuthenticationManager 进行身份认证 ---- > 类 AbstractUserDetailsAuthenticationProvider
  26. return this.getAuthenticationManager().authenticate(authRequest);
  27. }
  28. }
  29. }
org.springframework.security.oauth2.provider.endpoint.TokenEndpoint

//此方法的主要作用为:
1、获取客户端详情并根据请求参数组装TokenRequest
2、校验请求的Scope
3、为客户端是生成Token

  1. @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
  2. public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
  3. Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
  4. if (!(principal instanceof Authentication)) {
  5. throw new InsufficientAuthenticationException(
  6. "There is no client authentication. Try adding an appropriate authentication filter.");
  7. }
  8. //通过clientId 获取客户端详情
  9. String clientId = getClientId(principal);
  10. ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
  11. //获取请求的相关参数,如grant_type,client_id,scope(此处会依据用户的权限进行过滤)等, 封装组建TokenRequest
  12. TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
  13. if (clientId != null && !clientId.equals("")) {
  14. // Only validate the client details if a client authenticated during this
  15. // request.
  16. if (!clientId.equals(tokenRequest.getClientId())) {
  17. // double check to make sure that the client ID in the token request is the same as that in the
  18. // authenticated client
  19. throw new InvalidClientException("Given client ID does not match authenticated client");
  20. }
  21. }
  22. //对客户端传入的Scope进行校验 , Scope超限将直接抛出异常
  23. if (authenticatedClient != null) {
  24. oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
  25. }
  26. if (!StringUtils.hasText(tokenRequest.getGrantType())) {
  27. throw new InvalidRequestException("Missing grant type");
  28. }
  29. if (tokenRequest.getGrantType().equals("implicit")) {
  30. throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
  31. }
  32. // grant_type=authorzation_code 时清空scope
  33. if (isAuthCodeRequest(parameters)) {
  34. // The scope was requested or determined during the authorization step
  35. if (!tokenRequest.getScope().isEmpty()) {
  36. logger.debug("Clearing scope of incoming token request");
  37. tokenRequest.setScope(Collections.<String> emptySet());
  38. }
  39. }
  40. //grant_type=refresh_token 时 需要设置scope ,因为它有自己的scope
  41. if (isRefreshTokenRequest(parameters)) {
  42. // A refresh token has its own default scopes, so we should ignore any added by the factory here.
  43. tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
  44. }
  45. //为客户端生成token (此处会验证客户端的grant_type,只有检验通过才会生成Token) ------- >类 DefaultTokenServices
  46. OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
  47. if (token == null) {
  48. throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
  49. }
  50. //封装返回
  51. return getResponse(token);
  52. }
org.springframework.security.oauth2.provider.token.DefaultTokenServices

生成accessTokenRefreshToken

  1. @Transactional
  2. public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
  3. //首先尝试获取当前存在的Token
  4. OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
  5. OAuth2RefreshToken refreshToken = null;
  6. //如果accesToken已存在并且没有失效,则重新保存并返回;如果accessToken失效,则提取refrehToken并清除老的AccessToken;
  7. //如果accessToken为null,则直接生成新的Token
  8. if (existingAccessToken != null) {
  9. if (existingAccessToken.isExpired()) {
  10. if (existingAccessToken.getRefreshToken() != null) {
  11. refreshToken = existingAccessToken.getRefreshToken();
  12. // The token store could remove the refresh token when the
  13. // access token is removed, but we want to
  14. // be sure...
  15. tokenStore.removeRefreshToken(refreshToken);
  16. }
  17. tokenStore.removeAccessToken(existingAccessToken);
  18. }
  19. else {
  20. // Re-store the access token in case the authentication has changed
  21. tokenStore.storeAccessToken(existingAccessToken, authentication);
  22. return existingAccessToken;
  23. }
  24. }
  25. //老的refreshToken 不失效将会复用,失效的话生成新的RefresshToken
  26. // Only create a new refresh token if there wasn't an existing one
  27. // associated with an expired access token.
  28. // Clients might be holding existing refresh tokens, so we re-use it in
  29. // the case that the old access token
  30. // expired.
  31. if (refreshToken == null) {
  32. refreshToken = createRefreshToken(authentication);
  33. }
  34. // But the refresh token itself might need to be re-issued if it has
  35. // expired.
  36. else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
  37. ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
  38. if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
  39. refreshToken = createRefreshToken(authentication);
  40. }
  41. }
  42. //生成新的accessToken,并储存 , 保存refreshToken
  43. OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
  44. tokenStore.storeAccessToken(accessToken, authentication);
  45. // In case it was modified
  46. refreshToken = accessToken.getRefreshToken();
  47. if (refreshToken != null) {
  48. tokenStore.storeRefreshToken(refreshToken, authentication);
  49. }
  50. return accessToken;
  51. }

资源访问相关类

org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter

主要功能就是获取请求中携带的Token,然后通过Token提取Authentication,然后将Authentication放入上下文 ; 获取Authentication成功将会允许访问资源

  1. try {
  2. //提取请求携带的Token组建一个认证Authentication ,Token提取过程:从Hander和url中获取携带的Token
  3. Authentication authentication = tokenExtractor.extract(request);
  4. if (authentication == null) {
  5. if (stateless && isAuthenticated()) {
  6. if (debug) {
  7. logger.debug("Clearing security context.");
  8. }
  9. SecurityContextHolder.clearContext();
  10. }
  11. if (debug) {
  12. logger.debug("No token in request, will continue chain.");
  13. }
  14. }
  15. else {
  16. request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
  17. if (authentication instanceof AbstractAuthenticationToken) {
  18. AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
  19. needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
  20. }
  21. //获取Token携带的认证信息 , Oauth2AuthenticationMananger主要做三件是1、通过token获取用户的Oauth2Authentcation对象(TokenServices);2、验证访问的资源resourceId是否符合范围;3、验证客户端访问的Scope(clientDetailsService)
  22. Authentication authResult = authenticationManager.authenticate(authentication);
  23. if (debug) {
  24. logger.debug("Authentication success: " + authResult);
  25. }
  26. //将当前的Authentication 放入Context中 ,访问后面资源
  27. eventPublisher.publishAuthenticationSuccess(authResult);
  28. SecurityContextHolder.getContext().setAuthentication(authResult);
  29. }
  30. }
  31. catch (OAuth2Exception failed) {
  32. SecurityContextHolder.clearContext();
  33. if (debug) {
  34. logger.debug("Authentication request failed: " + failed);
  35. }
  36. eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
  37. new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
  38. authenticationEntryPoint.commence(request, response,
  39. new InsufficientAuthenticationException(failed.getMessage(), failed));
  40. return;
  41. }
  42. chain.doFilter(request, response);
  43. }
org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager

Oauth2AuthenticationMananger主要功能:

  1. 通过token获取用户的Oauth2Authentcation对象(TokenServices);
  2. 验证访问的资源resourceId是否符合范围;
  3. 验证客户端访问的Scope(clientDetailsService)

    1. if (authentication == null) {
    2. throw new InvalidTokenException("Invalid token (token not found)");
    3. }
    4. String token = (String) authentication.getPrincipal();
    5. //通过token获取用户的Oauth2Authentcation对象(TokenServices);
    6. OAuth2Authentication auth = tokenServices.loadAuthentication(token);
    7. if (auth == null) {
    8. throw new InvalidTokenException("Invalid token: " + token);
    9. }
    10. //验证访问的资源resourceId是否符合范围;
    11. Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
    12. if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
    13. throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
    14. }
    15. //验证客户端访问的Scope(clientDetailsService)
    16. checkClientDetails(auth);
    17. if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
    18. OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
    19. // Preserve the authentication details if any from the one loaded by token services
    20. details.setDecodedDetails(auth.getDetails());
    21. }
    22. auth.setDetails(authentication.getDetails());
    23. auth.setAuthenticated(true);
    24. return auth;

    spring-security-oauth2源码太多,个人能力有限只能将自己对它的初步理解进行梳理梳理,具体想如何掌握最好请各位大神们自己搭建环境进行源码调试,自然对流程能有个大楷的认识,如果以上观点有错误还请大神们多多指教!
    参考本人github地址:https://github.com/dqqzj/spring4all/tree/master/oauth2

文转载地址  http://www.spring4all.com/article/897
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/83436?site
推荐阅读
相关标签
  

闽ICP备14008679号