SpringSecurity登录原理(源码级讲解)(转)
一、简单叙述
首先会进入UsernamePasswordAuthenticationFilter并且设置权限为null和是否授权为false,然后进入ProviderManager查找支持UsernamepasswordAuthenticationToken的provider并且调用provider.authenticate(authentication);再然后就是UserDetailsService接口的实现类(也就是自己真正具体的业务了),这时候都检查过了后,就会回调UsernamePasswordAuthenticationFilter并且设置权限(具体业务所查出的权限)和设置授权为true(因为这时候确实所有关卡都检查过了)。
PS:云里雾绕的?没关系,接下里看我们每一步骤都具体的深入到源码级别的去分析。
二、源码分析
UsernamePasswordAuthenticationFilter
// 继承了AbstractAuthenticationProcessingFilter public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 认证请求的方式必须为POST 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); return this.getAuthenticationManager().authenticate(authRequest); } }
可以发现继承了AbstractAuthenticationProcessingFilter,那我们就来看下此类
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { // 过滤器doFilter方法 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; /* * 判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。 */ if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } Authentication authResult; try { // 很关键!!!调用了子类(UsernamePasswordAuthenticationFilter)的方法 authResult = attemptAuthentication(request, response); if (authResult == null) { // return immediately as subclass has indicated that it hasn't completed // authentication return; } // 最终认证成功后,会处理一些与session相关的方法(比如将认证信息存到session等操作)。 sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); // 认证失败后的一些处理。 unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } /* * 最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中 * 并调用成功处理器做相应的操作。 */ successfulAuthentication(request, response, chain, authResult); } }
PS:看到这里估计很多人在骂娘了,什么玩意,直接复制粘贴也不讲解,不要急,上面只是看下类结构,下面来具体分析!这里只分析主要代码,不是很主要也不是很相关的不作讲解,有兴趣的自己去读。
(一)、 父类的处理流程
1、继承了父类,父类是个过滤器,所以肯定先执行AbstractAuthenticationProcessingFilter.doFilter(),此方法首先判断当前的filter是否可以处理当前请求,不可以的话则交给下一个filter处理。
/* * 判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。 */ if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; }
2、调用此抽象类的子类UsernamePasswordAuthenticationFilter.attemptAuthentication(request, response)方法做具体的操作。
// 很关键!!!调用了子类(UsernamePasswordAuthenticationFilter)的方法 authResult = attemptAuthentication(request, response);
3、最终认证成功后做一些成功后的session操作,比如将认证信息存到session等。
// 最终认证成功后,会处理一些与session相关的方法(比如将认证信息存到session等操作)。 sessionStrategy.onAuthentication(authResult, request, response);
4、最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中并调用成功处理器做相应的操作。
successfulAuthentication(request, response, chain, authResult); protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); } // 将当前的认证信息放到SecurityContextHolder中 SecurityContextHolder.getContext().setAuthentication(authResult); rememberMeServices.loginSuccess(request, response, authResult); // Fire event if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this.getClass())); } // 调用成功处理器,可以自己实现AuthenticationSuccessHandler接口重写方法写自己的逻辑 successHandler.onAuthenticationSuccess(request, response, authResult); }
(二)、子类的处理流程
1、父类的authResult = attemptAuthentication(request, response);触发了自类的方法。
2、此方法首先判断请求方式是不是POST提交,必须是POST
// 认证请求的方式必须为POST if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); }
3、从请求中获取username和password,并做一些处理
// 获取用户名 String username = obtainUsername(request); // 获取密码 String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } // 用户名去空白 username = username.trim();
4、封装Authenticaiton类的实现类UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password);
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { super((Collection)null); this.principal = principal; this.credentials = credentials; this.setAuthenticated(false); }
PS:为什么这个构造器设置权限为null?
super((Collection)null);,并且设置是否授权为false?this.setAuthenticated(false);道理很简单,因为我们这是刚刚登陆过来,你的账号密码对不对我们都没验证呢,所以这里是未授权,权限null。
5、调用AuthenticationManager的authenticate方法进行验证
return this.getAuthenticationManager().authenticate(authRequest);
(三)、AuthenticationManager处理流程
1、怎么触发的?
return this.getAuthenticationManager().authenticate(authRequest);
PS:交由
AuthenticationManager接口的ProviderManager实现类处理。
2、ProviderManager.authenticate(Authentication authentication);
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class toTest = authentication.getClass(); Object lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); // 拿到全部的provider Iterator e = this.getProviders().iterator(); // 遍历provider while(e.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)e.next(); // 挨着个的校验是否支持当前token if(provider.supports(toTest)) { if(debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { // 找到后直接break,并由当前provider来进行校验工作 result = provider.authenticate(authentication); if(result != null) { this.copyDetails(authentication, result); break; } } catch (AccountStatusException var11) { this.prepareException(var11, authentication); throw var11; } catch (InternalAuthenticationServiceException var12) { this.prepareException(var12, authentication); throw var12; } catch (AuthenticationException var13) { lastException = var13; } } } // 若没有一个支持,则尝试交给父类来执行 if(result == null && this.parent != null) { try { result = this.parent.authenticate(authentication); } catch (ProviderNotFoundException var9) { ; } catch (AuthenticationException var10) { lastException = var10; } } .......................... }
**3、此方法遍历所有的Providers,然后依次执行验证方法看是否支持UsernamepasswordAuthenticationToken**
// 拿到全部的provider Iterator e = this.getProviders().iterator(); // 遍历provider while(e.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)e.next(); // 挨着个的校验是否支持当前token if(provider.supports(toTest)) { if(debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } } }
4、若有一个能够支持当前token,则直接交由此provider处理并break。
// 找到后直接break,并由当前provider来进行校验工作 result = provider.authenticate(authentication); if(result != null) { this.copyDetails(authentication, result); break; }
5、若没一个provider验证成功,则交由父类来尝试处理
// 若没有一个支持,则尝试交给父类来执行 if(result == null && this.parent != null) { try { result = this.parent.authenticate(authentication); } catch (ProviderNotFoundException var9) { ; } catch (AuthenticationException var10) { lastException = var10; } }
(四)、AuthenticationProvider处理流程
1、怎么触发的?
// 由上一步的ProviderManager的authenticate方法来触发 result = provider.authenticate(authentication);
PS:这里交由
AuthenticationProvider接口的实现类DaoAuthenticationProvider来处理。
2、DaoAuthenticationProvider
// 继承了AbstractUserDetailsAuthenticationProvider public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { /* * 调用UserDetailsService接口的loadUserByUsername方法, * 此方法就是我们自己定义的类去实现接口重写的方法,处理我们自己的业务逻辑。 */ loadedUser = this.getUserDetailsService().loadUserByUsername(username); } catch (UsernameNotFoundException var6) { if(authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null); } throw var6; } catch (Exception var7) { throw new InternalAuthenticationServiceException(var7.getMessage(), var7); } if(loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); } else { return loadedUser; } } }
3、继承了AbstractUserDetailsAuthenticationProvider
// 实现了AuthenticationProvider接口 public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); String username = authentication.getPrincipal() == null?"NONE_PROVIDED":authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if(user == null) { cacheWasUsed = false; try { // 调用自类retrieveUser user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); } catch (UsernameNotFoundException var6) { this.logger.debug("User \'" + username + "\' not found"); if(this.hideUserNotFoundExceptions) { throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } throw var6; } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try { /* * 前检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结 * User接口) */ this.preAuthenticationChecks.check(user); // 子类具体实现 this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } catch (AuthenticationException var7) { if(!cacheWasUsed) { throw var7; } cacheWasUsed = false; user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); this.preAuthenticationChecks.check(user); this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } // 检测用户密码是否过期 this.postAuthenticationChecks.check(user); if(!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if(this.forcePrincipalAsString) { principalToReturn = user.getUsername(); } return this.createSuccessAuthentication(principalToReturn, authentication, user); } }
4、AbstractUserDetailsAuthenticationProvider.authenticate()首先调用了user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
PS:调用的是
DaoAuthenticationProvider.retrieveUser()
5、调用我们自己的业务处理类
/* * 调用UserDetailsService接口的loadUserByUsername方法, * 此方法就是我们自己定义的类去实现接口重写的方法,处理我们自己的业务逻辑。 */ loadedUser = this.getUserDetailsService().loadUserByUsername(username);
比如:
/** * @author chentongwei@bshf360.com 2018-03-26 13:15 */ @Service public class MyUserDetailsService implements UserDetailsService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("表单登录用户名:" + username); return buildUser(username); } private UserDetails buildUser(String username) { /** * passwordEncoder.encode这步骤应该放到注册接口去做,而这里只需要传一个从db查出来的pwd即可。 * * passwordEncoder.encode("123456")每次打印出来都是不同的,虽然是同一个(123456)密码, * 但是他会随机生成一个盐(salt),他会把随机生成的盐混到加密的密码里。Springsecurity验证(matches方法)的时候会将利用此盐解析出pwd,进行匹配。 * 这样的好处是:如果数据库里面有10个123456密码。但是被破解了1个,那么另外九个是安全的,因为db里存的串是不一样的。 */ String password = passwordEncoder.encode("123456"); logger.info("数据库密码是:" + password); // 这个User不一定必须用SpringSecurity的,可以写一个自定义实现UserDetails接口的类,然后把是否锁定等判断逻辑写进去。 return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
PS:注意:实现
UserDetailsService接口。可返回我们自己定义的User类,但User类要实现UserDetails接口
6、调用完retrieveUser方法继续回到抽象类的authenticate方法
7、首先做一些检查
/* * 前检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结 * User接口) */ this.preAuthenticationChecks.check(user); // 检测用户密码是否过期 this.postAuthenticationChecks.check(user);
8、调用createSuccessAuthentication方法进行授权成功
return this.createSuccessAuthentication(principalToReturn, authentication, user); // 成功授权 protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { // 回调UsernamePasswordAuthenticationToken的构造器,这里调用的是授权成功的构造器 UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities())); // 将认证信息的一块内容放到details result.setDetails(authentication.getDetails()); return result; }
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { // 不在是null,而是传来的权限,这个权限就是我们自己定义的detailsService类所返回的,可以从db查 super(authorities); this.principal = principal; this.credentials = credentials; // 这里是true,不在是false。 super.setAuthenticated(true); }
9、回到起点
AbstractAuthenticationProcessingFilter.doFilter()
进行session存储和成功后的处理器的调用等
三、总结
只是简单说下类之间的调用顺序。
UsernamePasswordAuthenticationFilter Authentication AuthenticationManager AuthenticationProvider UserDetailsService // 回到起点进行后续操作,比如缓存认证信息到session和调用成功后的处理器等等 UsernamePasswordAuthenticationFilter
四、Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="login" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
http.formLogin()
// 默认表单登录页
.loginPage(SecurityConstant.DEFAULT_UNAUTHENTICATION_URL)
// 登录接口
.loginProcessingUrl(SecurityConstant.DEFAULT_LOGIN_PROCESSING_URL_FORM)
/** * 常量 * * @author chentongwei@bshf360.com 2018-03-26 11:40 */ public interface SecurityConstant { /** * 默认登录页 */ String DEFAULT_LOGIN_PAGE_URL = "/default-login.html"; /** * 默认的登录接口 */ String DEFAULT_LOGIN_PROCESSING_URL_FORM = "/login"; }
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; /** * @author chentongwei@bshf360.com 2018-03-26 13:15 */ @Service public class MyUserDetailsService implements UserDetailsService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("表单登录用户名:" + username); return buildUser(username); } private UserDetails buildUser(String username) { /** * passwordEncoder.encode这步骤应该放到注册接口去做,而这里只需要传一个从db查出来的pwd即可。 * * passwordEncoder.encode("123456")每次打印出来都是不同的,虽然是同一个(123456)密码, * 但是他会随机生成一个盐(salt),他会把随机生成的盐混到加密的密码里。Springsecurity验证(matches方法)的时候会将利用此盐解析出pwd,进行匹配。 * 这样的好处是:如果数据库里面有10个123456密码。但是被破解了1个,那么另外九个是安全的,因为db里存的串是不一样的。 */ String password = passwordEncoder.encode("123456"); logger.info("数据库密码是:" + password); // 这个User不一定必须用SpringSecurity的,可以写一个自定义实现UserDetails接口的类,然后把是否锁定等判断逻辑写进去。 return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
大功告成!
只需要一个html,一段配置,一个Service自己的业务类即可。
疑问:
1、接口login在哪定义的?
2、用户名username和密码password在哪接收的?
3、没有控制器怎么进入我们的MyUserDetailsService的方法?
解答:
1、SpringSecurity内置的,并且只能为POST
public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); }
2、名称不能变,必须是username和password
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { // ~ Static fields/initializers // ===================================================================================== public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; }
3、自己看我上面的源码分析
作者:编程界的小学生
链接:https://www.jianshu.com/p/a65f883de0c1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
感谢您的阅读,您的支持是我写博客动力。

浙公网安备 33010602011771号