赞
踩
有的老铁可能还没没怎么了解OAuth2,没关系给你一个链接去先去看看
→http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
首先我们可以找到位于org.springframework.security.oauth2.provider.endpoint这里面的TokenEnEndpoint类有两个端点(都一样);在里面我们看到很熟悉的@ReRequestMaping(Principal principal,@RequestParam Map<String,String> parameters),里面有俩个参数.解释下:
- @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.");
- }
-
- 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.<String> 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);
-
- }
(认证环节)重点看:这段代码
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
- private TokenGranter tokenGranter() {
- if (tokenGranter == null) {
- tokenGranter = new TokenGranter() {
- private CompositeTokenGranter delegate;
-
- @Override
- public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
- if (delegate == null) {
- delegate = new CompositeTokenGranter(getDefaultTokenGranters());
- }
- return delegate.grant(grantType, tokenRequest);
- }
- };
- }
- return tokenGranter;
- }
这是CompositeTokenGranter中的代码
- public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
- for (TokenGranter granter : tokenGranters) {
- OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
- if (grant!=null) {
- return grant;
- }
- }
- return null;
- }
grant()方法就在这个接口中的方法;他被AbstractTokenGranter抽象类实现,而AbstractTokenGranter又有分别被下面4个继承(我们也可以自定义一个XxxTokenGranter类去继承这样我们就可以重写里面的方法了)
TokenGranter还有被一个叫CompositeTokenGranter的类实现,会根据你前端传递的参数grant_type来判断到底进哪个模式
而这个模式也是可以自定义的 ,也就是上面图中的tokenGranter()方法回默认用CompositeTokenGranter实现先去遍历(4中模式),
这里的默认也就是说在AuthorizationServerEndpointsConfigurer类中未配置TokenGranter这个属性,如果要配置的其实也很简单
看下面:
我们要做的只是写一个配置类来继承AuthorizationServerConfigurerAdapter这个适配器,重写三个configure方法
-
- /**
- * @author Dave Syer
- *
- */
- public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
-
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- }
-
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- }
-
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- }
-
- }
其中 endpoints.tokenGranter()就是配置让我们自定义的配置tokenGranter的,建议配置CompositeTokenGranter这个.
因为只有子类ClientCredentialsTokenGranter重写了grant(),其他子类重用父类grant()方法;其实在这里我们也可以自己去继承AbstractTokenGranter类来自定义一个XxGranter;然后在自定义的认证配置里面(也就是继承AuthorizationServerConfigurerAdapter适配器)的endpoints端点配置中去搞他.
- @Autowired
- private AuthenticationManager authenticationManager;
-
- @Autowired
- private Environment env;
-
- @Autowired
- private UserService userService;
-
- @Autowired
- private CaptchaService captchaService;
-
- @Autowired
- private TokenStore tokenStore;
-
- @Autowired
- private TokenEnhancerChain tokenEnhancerChain;
-
- @Configuration
- @EnableAuthorizationServer
- public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
-
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- endpoints.tokenStore(tokenStore)
- .tokenEnhancer(tokenEnhancerChain)
- .authenticationManager(authenticationManager);
-
- endpoints.tokenGranter(tokenGranter(endpoints))
- .userDetailsService(userService)
- .reuseRefreshTokens(true);
- }
-
- private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer configurer) {
- AuthorizationServerTokenServices tokenService = configurer.getTokenServices();
- OAuth2RequestFactory requestFactory = configurer.getOAuth2RequestFactory();
- ClientDetailsService clientDetailsService = configurer.getClientDetailsService();
-
- List<TokenGranter> tokenGranters = new ArrayList<>();
- tokenGranters.add(new ClientCredentialsTokenGranter(tokenService, clientDetailsService, requestFactory));
- tokenGranters.add(new RefreshTokenGranter(tokenService, clientDetailsService, requestFactory));
- //UsernamePasswordGranter就是我们需要的自定义的XXGranter继承AbstractTokenGranter类
- UsernamePasswordGranter tokenGranter =
- new UsernamePasswordGranter(authenticationManager,
- tokenService, clientDetailsService,
- requestFactory, captchaService,
- userService);
- tokenGranters.add(tokenGranter);
-
- return new CompositeTokenGranter(tokenGranters);
- }
-
-
-
- }
-
不管是不是自定义Granter,认证还是流程还是必须走AuthenticationManager接口的authenticate方法滴!然后就是走ProvideManager中的authenticate方法去遍历providers(认证提供者好多个)其中的authenticate方法
首先你的User类你的必须实现UserDetail接口和里面的参数, UserDetailsService中的loadUserByUsername()方法在认证(authenticate方法)过程中是肯定要用到.一般来说我们用UserService去继承UserDetailsService然后在他是实现类里面UserServiceImpl去实现loadUserByUsername()方法.
其实跟前面的TokenGranter也有点类似, 1.大总管(AuthenticationManager) 2.小总管(ProviderManager) 3.....然后后面.....
在ProviderManager中的authenticate方法中会遍历List<AuthenticationProvider> providers这个认证供应者(百度一下),AuthenticationProvider是一个接口,所有每个provider的实现中又有一个authenticate方法,好了进入这里就是真正的认证了.
在这里最好自己打个断点看看往哪走,在进入AbstractUserDetailsAuthenticationProvider实现下面放代码
-
- package org.springframework.security.authentication.dao;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.springframework.security.authentication.AccountExpiredException;
- import org.springframework.security.authentication.AuthenticationProvider;
- import org.springframework.security.authentication.BadCredentialsException;
- import org.springframework.security.authentication.CredentialsExpiredException;
- import org.springframework.security.authentication.DisabledException;
- import org.springframework.security.authentication.LockedException;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.core.SpringSecurityMessageSource;
- import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
- import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
- import org.springframework.security.core.userdetails.UserCache;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsChecker;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.security.core.userdetails.cache.NullUserCache;
-
- import org.springframework.beans.factory.InitializingBean;
-
- import org.springframework.context.MessageSource;
- import org.springframework.context.MessageSourceAware;
- import org.springframework.context.support.MessageSourceAccessor;
-
- import org.springframework.util.Assert;
-
- public abstract class AbstractUserDetailsAuthenticationProvider implements
- AuthenticationProvider, InitializingBean, MessageSourceAware {
-
-
- 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 {
- 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();
- }
-
- return createSuccessAuthentication(principalToReturn, authentication, user);
- }
-
-
- }
这里应该基本上没啥问题,仔细看一下就明白了. 其中retrieveUser()点进去看就能明白我们之前继承UserDetailService实现的loadUserByUsername()就会被在这调用到, 还有一个就是校验密码的时候----additionalAuthenticationChecks()方法点进去看
- protected void additionalAuthenticationChecks(UserDetails userDetails,
- UsernamePasswordAuthenticationToken authentication)
- throws AuthenticationException {
- Object salt = null;
-
- if (this.saltSource != null) {
- salt = this.saltSource.getSalt(userDetails);
- }
-
- if (authentication.getCredentials() == null) {
- logger.debug("Authentication failed: no credentials provided");
-
- throw new BadCredentialsException(messages.getMessage(
- "AbstractUserDetailsAuthenticationProvider.badCredentials",
- "Bad credentials"));
- }
-
- String presentedPassword = authentication.getCredentials().toString();
-
- if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
- presentedPassword, salt)) {
- logger.debug("Authentication failed: password does not match stored value");
-
- throw new BadCredentialsException(messages.getMessage(
- "AbstractUserDetailsAuthenticationProvider.badCredentials",
- "Bad credentials"));
- }
- }
重点在密码验证这if(!passwordEncoder.isPasswordVlid(userDetails.getPassword(),presentedPassword.salt)){..............},还有前面的UserDetailService的loadUserByUsernam()方法~~哎奇怪是不是感觉少个配置啊,就是下面这个东东
先上个图看看吧
- package com.hz.coreconfig;
-
- import com.hz.pojo.User;
- import com.hz.service.UserService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-
- /**
- * @author hz
- * @date 2018-8-22 16:10
- */
- @Slf4j
- @Configuration
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- UserService userService;
-
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- /**
- * userDetailsService(userService):配置用户服务
- * passwordEncoder(User.passwordEncoder):配置密码校验规则,在DaoAuthenticationProvider类中
- */
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userService)
- .passwordEncoder(new BCryptPasswordEncoder(11));
- }
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests().anyRequest().permitAll();
- }
- }
在.userDetailService()中配置的就是我们自己的LoadUserByUsername()方法, .passwordEncode()中配置的就是我们需要密码校验的格式;
好了基本上/oauth/token的流程就是这样了,我也是看了好几天源码,边看边debug才明白的.其实刚开始看真的很懵逼,没办法只能一步一步debug下去.这一字一句都是我自己亲自敲出来的,很开心这是我第一篇博客(其实光写这个篇我花了半天时间,生怕哪里写错了,然后被大佬指出来~~~那就很low很尴尬了),但肯定不是最后一篇!
当然错误肯定会有,希望看官大佬不要吝啬指教~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。