当前位置:   article > 正文

Spring Security OAuth2 源码解析 (一)_org.springframework.security.oauth2.core

org.springframework.security.oauth2.core

有的老铁可能还没没怎么了解OAuth2,没关系给你一个链接去先去看看 

     →http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

OAuth 2.0 定义了四种授权方式:

  1. 授权码模式(authorization code)
  2. 简化模式(implicit)
  3. 密码模式(resource owner password credentials)
  4. 客户端模式(client credentials)

重点讲一讲密码模式(也就是我目前碰到的)

首先我们可以找到位于org.springframework.security.oauth2.provider.endpoint这里面的TokenEnEndpoint类有两个端点(都一样);在里面我们看到很熟悉的@ReRequestMaping(Principal principal,@RequestParam Map<String,String> parameters),里面有俩个参数.解释下:

  • principal :这个其实是在Filter阶段就已经认证好的客户端信息(有兴趣的可以去debug一下);
  • parameters:这个就是前端传递过去的参数啦;
  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. String clientId = getClientId(principal);
  9. ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
  10. TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
  11. if (clientId != null && !clientId.equals("")) {
  12. // Only validate the client details if a client authenticated during this
  13. // request.
  14. if (!clientId.equals(tokenRequest.getClientId())) {
  15. // double check to make sure that the client ID in the token request is the same as that in the
  16. // authenticated client
  17. throw new InvalidClientException("Given client ID does not match authenticated client");
  18. }
  19. }
  20. if (authenticatedClient != null) {
  21. oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
  22. }
  23. if (!StringUtils.hasText(tokenRequest.getGrantType())) {
  24. throw new InvalidRequestException("Missing grant type");
  25. }
  26. if (tokenRequest.getGrantType().equals("implicit")) {
  27. throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
  28. }
  29. if (isAuthCodeRequest(parameters)) {
  30. // The scope was requested or determined during the authorization step
  31. if (!tokenRequest.getScope().isEmpty()) {
  32. logger.debug("Clearing scope of incoming token request");
  33. tokenRequest.setScope(Collections.<String> emptySet());
  34. }
  35. }
  36. if (isRefreshTokenRequest(parameters)) {
  37. // A refresh token has its own default scopes, so we should ignore any added by the factory here.
  38. tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
  39. }
  40. OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
  41. if (token == null) {
  42. throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
  43. }
  44. return getResponse(token);
  45. }

 (认证环节)重点看:这段代码

OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
  • getTokenGranter():这个方法主要就是为了拿到tokenGranter,从这个类的set方法中我们可以看到tokenGranter值其实是从AuthorizationServerEndpointsConfiguration这个类中被赋值的(实例化了TokenEndpoint),我们可以通过继承AuthorizationServerConfigurerAdapterz这个适配器来自定义配置AuthorizationServerEndpointsConfigurer内的属性,从而配置认证授权端点配置
  • grant():听名字就知道大概的意思,接下来的操作都在里面(这里的grant()调用的是在AuthorizationServerEndpointsConfigurer类中的方法,可以仔细去看看,意思就是说默认使用CompositeTokenGranter实现先去遍历)来段代码看看
    1. private TokenGranter tokenGranter() {
    2. if (tokenGranter == null) {
    3. tokenGranter = new TokenGranter() {
    4. private CompositeTokenGranter delegate;
    5. @Override
    6. public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
    7. if (delegate == null) {
    8. delegate = new CompositeTokenGranter(getDefaultTokenGranters());
    9. }
    10. return delegate.grant(grantType, tokenRequest);
    11. }
    12. };
    13. }
    14. return tokenGranter;
    15. }

    这是CompositeTokenGranter中的代码

    1. public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
    2. for (TokenGranter granter : tokenGranters) {
    3. OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
    4. if (grant!=null) {
    5. return grant;
    6. }
    7. }
    8. return null;
    9. }

     

TokenGranter接口

grant()方法就在这个接口中的方法;他被AbstractTokenGranter抽象类实现,而AbstractTokenGranter又有分别被下面4个继承(我们也可以自定义一个XxxTokenGranter类去继承这样我们就可以重写里面的方法了)

  1. AuthorizationCodeTokenGranter : 授权码模式
  2. ClientCredentialsTokenGranter : 客户端模式
  3. ImplicitTokenGranter : 简化模式
  4. ResourceOwnerPasswordTokenGranter : 密码模式

TokenGranter还有被一个叫CompositeTokenGranter的类实现,会根据你前端传递的参数grant_type来判断到底进哪个模式

而这个模式也是可以自定义的 ,也就是上面图中的tokenGranter()方法回默认用CompositeTokenGranter实现先去遍历(4中模式),

这里的默认也就是说在AuthorizationServerEndpointsConfigurer类中未配置TokenGranter这个属性,如果要配置的其实也很简单

看下面:

AuthorizationServerConfigurer 和 AuthorizationServerConfigurerAdapter

我们要做的只是写一个配置类来继承AuthorizationServerConfigurerAdapter这个适配器,重写三个configure方法

  1. /**
  2. * @author Dave Syer
  3. *
  4. */
  5. public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
  6. @Override
  7. public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
  8. }
  9. @Override
  10. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  11. }
  12. @Override
  13. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  14. }
  15. }

其中 endpoints.tokenGranter()就是配置让我们自定义的配置tokenGranter的,建议配置CompositeTokenGranter这个.

因为只有子类ClientCredentialsTokenGranter重写了grant(),其他子类重用父类grant()方法;其实在这里我们也可以自己去继承AbstractTokenGranter类来自定义一个XxGranter;然后在自定义的认证配置里面(也就是继承AuthorizationServerConfigurerAdapter适配器)的endpoints端点配置中去搞他.

  1. @Autowired
  2. private AuthenticationManager authenticationManager;
  3. @Autowired
  4. private Environment env;
  5. @Autowired
  6. private UserService userService;
  7. @Autowired
  8. private CaptchaService captchaService;
  9. @Autowired
  10. private TokenStore tokenStore;
  11. @Autowired
  12. private TokenEnhancerChain tokenEnhancerChain;
  13. @Configuration
  14. @EnableAuthorizationServer
  15. public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  16. @Override
  17. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  18. endpoints.tokenStore(tokenStore)
  19. .tokenEnhancer(tokenEnhancerChain)
  20. .authenticationManager(authenticationManager);
  21. endpoints.tokenGranter(tokenGranter(endpoints))
  22. .userDetailsService(userService)
  23. .reuseRefreshTokens(true);
  24. }
  25. private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer configurer) {
  26. AuthorizationServerTokenServices tokenService = configurer.getTokenServices();
  27. OAuth2RequestFactory requestFactory = configurer.getOAuth2RequestFactory();
  28. ClientDetailsService clientDetailsService = configurer.getClientDetailsService();
  29. List<TokenGranter> tokenGranters = new ArrayList<>();
  30. tokenGranters.add(new ClientCredentialsTokenGranter(tokenService, clientDetailsService, requestFactory));
  31. tokenGranters.add(new RefreshTokenGranter(tokenService, clientDetailsService, requestFactory));
  32. //UsernamePasswordGranter就是我们需要的自定义的XXGranter继承AbstractTokenGranter类
  33. UsernamePasswordGranter tokenGranter =
  34. new UsernamePasswordGranter(authenticationManager,
  35. tokenService, clientDetailsService,
  36. requestFactory, captchaService,
  37. userService);
  38. tokenGranters.add(tokenGranter);
  39. return new CompositeTokenGranter(tokenGranters);
  40. }
  41. }

不管是不是自定义Granter,认证还是流程还是必须走AuthenticationManager接口的authenticate方法滴!然后就是走ProvideManager中的authenticate方法去遍历providers(认证提供者好多个)其中的authenticate方法

关于UserDetail 和 UserDetailsService

首先你的User类你的必须实现UserDetail接口和里面的参数, UserDetailsService中的loadUserByUsername()方法在认证(authenticate方法)过程中是肯定要用到.一般来说我们用UserService去继承UserDetailsService然后在他是实现类里面UserServiceImpl去实现loadUserByUsername()方法.

关于authenticate方法,也就是真正的认证了

其实跟前面的TokenGranter也有点类似, 1.大总管(AuthenticationManager) 2.小总管(ProviderManager) 3.....然后后面.....

在ProviderManager中的authenticate方法中会遍历List<AuthenticationProvider> providers这个认证供应者(百度一下),AuthenticationProvider是一个接口,所有每个provider的实现中又有一个authenticate方法,好了进入这里就是真正的认证了.

在这里最好自己打个断点看看往哪走,在进入AbstractUserDetailsAuthenticationProvider实现下面放代码

  1. package org.springframework.security.authentication.dao;
  2. import org.apache.commons.logging.Log;
  3. import org.apache.commons.logging.LogFactory;
  4. import org.springframework.security.authentication.AccountExpiredException;
  5. import org.springframework.security.authentication.AuthenticationProvider;
  6. import org.springframework.security.authentication.BadCredentialsException;
  7. import org.springframework.security.authentication.CredentialsExpiredException;
  8. import org.springframework.security.authentication.DisabledException;
  9. import org.springframework.security.authentication.LockedException;
  10. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  11. import org.springframework.security.core.Authentication;
  12. import org.springframework.security.core.AuthenticationException;
  13. import org.springframework.security.core.SpringSecurityMessageSource;
  14. import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
  15. import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
  16. import org.springframework.security.core.userdetails.UserCache;
  17. import org.springframework.security.core.userdetails.UserDetails;
  18. import org.springframework.security.core.userdetails.UserDetailsChecker;
  19. import org.springframework.security.core.userdetails.UserDetailsService;
  20. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  21. import org.springframework.security.core.userdetails.cache.NullUserCache;
  22. import org.springframework.beans.factory.InitializingBean;
  23. import org.springframework.context.MessageSource;
  24. import org.springframework.context.MessageSourceAware;
  25. import org.springframework.context.support.MessageSourceAccessor;
  26. import org.springframework.util.Assert;
  27. public abstract class AbstractUserDetailsAuthenticationProvider implements
  28. AuthenticationProvider, InitializingBean, MessageSourceAware {
  29. public Authentication authenticate(Authentication authentication)
  30. throws AuthenticationException {
  31. Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
  32. messages.getMessage(
  33. "AbstractUserDetailsAuthenticationProvider.onlySupports",
  34. "Only UsernamePasswordAuthenticationToken is supported"));
  35. // Determine username
  36. String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
  37. : authentication.getName();
  38. boolean cacheWasUsed = true;
  39. UserDetails user = this.userCache.getUserFromCache(username);
  40. if (user == null) {
  41. cacheWasUsed = false;
  42. try {
  43. user = retrieveUser(username,
  44. (UsernamePasswordAuthenticationToken) authentication);
  45. }
  46. catch (UsernameNotFoundException notFound) {
  47. logger.debug("User '" + username + "' not found");
  48. if (hideUserNotFoundExceptions) {
  49. throw new BadCredentialsException(messages.getMessage(
  50. "AbstractUserDetailsAuthenticationProvider.badCredentials",
  51. "Bad credentials"));
  52. }
  53. else {
  54. throw notFound;
  55. }
  56. }
  57. Assert.notNull(user,
  58. "retrieveUser returned null - a violation of the interface contract");
  59. }
  60. try {
  61. preAuthenticationChecks.check(user);
  62. additionalAuthenticationChecks(user,
  63. (UsernamePasswordAuthenticationToken) authentication);
  64. }
  65. catch (AuthenticationException exception) {
  66. if (cacheWasUsed) {
  67. // There was a problem, so try again after checking
  68. // we're using latest data (i.e. not from the cache)
  69. cacheWasUsed = false;
  70. user = retrieveUser(username,
  71. (UsernamePasswordAuthenticationToken) authentication);
  72. preAuthenticationChecks.check(user);
  73. additionalAuthenticationChecks(user,
  74. (UsernamePasswordAuthenticationToken) authentication);
  75. }
  76. else {
  77. throw exception;
  78. }
  79. }
  80. postAuthenticationChecks.check(user);
  81. if (!cacheWasUsed) {
  82. this.userCache.putUserInCache(user);
  83. }
  84. Object principalToReturn = user;
  85. if (forcePrincipalAsString) {
  86. principalToReturn = user.getUsername();
  87. }
  88. return createSuccessAuthentication(principalToReturn, authentication, user);
  89. }
  90. }

这里应该基本上没啥问题,仔细看一下就明白了.  其中retrieveUser()点进去看就能明白我们之前继承UserDetailService实现的loadUserByUsername()就会被在这调用到, 还有一个就是校验密码的时候----additionalAuthenticationChecks()方法点进去看

  1. protected void additionalAuthenticationChecks(UserDetails userDetails,
  2. UsernamePasswordAuthenticationToken authentication)
  3. throws AuthenticationException {
  4. Object salt = null;
  5. if (this.saltSource != null) {
  6. salt = this.saltSource.getSalt(userDetails);
  7. }
  8. if (authentication.getCredentials() == null) {
  9. logger.debug("Authentication failed: no credentials provided");
  10. throw new BadCredentialsException(messages.getMessage(
  11. "AbstractUserDetailsAuthenticationProvider.badCredentials",
  12. "Bad credentials"));
  13. }
  14. String presentedPassword = authentication.getCredentials().toString();
  15. if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
  16. presentedPassword, salt)) {
  17. logger.debug("Authentication failed: password does not match stored value");
  18. throw new BadCredentialsException(messages.getMessage(
  19. "AbstractUserDetailsAuthenticationProvider.badCredentials",
  20. "Bad credentials"));
  21. }
  22. }

重点在密码验证这if(!passwordEncoder.isPasswordVlid(userDetails.getPassword(),presentedPassword.salt)){..............},还有前面的UserDetailService的loadUserByUsernam()方法~~哎奇怪是不是感觉少个配置啊,就是下面这个东东

其实我们还需要一个继承WebSecurityConfigConfigurerAdpter的Security配置类

先上个图看看吧

  1. package com.hz.coreconfig;
  2. import com.hz.pojo.User;
  3. import com.hz.service.UserService;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.security.authentication.AuthenticationManager;
  9. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  10. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  11. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  12. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  13. /**
  14. * @author hz
  15. * @date 2018-8-22 16:10
  16. */
  17. @Slf4j
  18. @Configuration
  19. @EnableGlobalMethodSecurity(prePostEnabled = true)
  20. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  21. @Autowired
  22. UserService userService;
  23. @Bean
  24. @Override
  25. public AuthenticationManager authenticationManagerBean() throws Exception {
  26. return super.authenticationManagerBean();
  27. }
  28. /**
  29. * userDetailsService(userService):配置用户服务
  30. * passwordEncoder(User.passwordEncoder):配置密码校验规则,在DaoAuthenticationProvider类中
  31. */
  32. @Override
  33. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  34. auth.userDetailsService(userService)
  35. .passwordEncoder(new BCryptPasswordEncoder(11));
  36. }
  37. @Override
  38. protected void configure(HttpSecurity http) throws Exception {
  39. http.authorizeRequests().anyRequest().permitAll();
  40. }
  41. }

在.userDetailService()中配置的就是我们自己的LoadUserByUsername()方法, .passwordEncode()中配置的就是我们需要密码校验的格式;

好了基本上/oauth/token的流程就是这样了,我也是看了好几天源码,边看边debug才明白的.其实刚开始看真的很懵逼,没办法只能一步一步debug下去.这一字一句都是我自己亲自敲出来的,很开心这是我第一篇博客(其实光写这个篇我花了半天时间,生怕哪里写错了,然后被大佬指出来~~~那就很low很尴尬了),但肯定不是最后一篇!

当然错误肯定会有,希望看官大佬不要吝啬指教~

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

闽ICP备14008679号