Spring Security笔记—基本原理及认证流程
1. 基本原理
- 黄色模块:SecurityContextPersistenceFilter是承接容器的session与spring security的重要filter,主要工作是从session中获取SecurityContext,然后放到上下文中,之后的filter大多依赖这个来获取登录态。其主要是通过HttpSessionSecurityContextRepository来存取的。
- 绿色模块:可选择添加的认证过滤器,用于验证用户登入信息是否正确,正确则保存在session,主要代码AbstractAuthenticationProcessingFilter抽象类的
SecurityContextHolder.getContext().setAuthentication(authResult)
- 橘色模块:FilterSecurityInterceptor 最后的大门,通过WebSecurityConfigurerAdapter或其实现类的
protected void configure(HttpSecurity http) throws Exception
的配置方法,来确定请求是否能访问 - 深蓝色模块:ExceptionTranslationFilter 用于获取橘色模块的异常信息,凡是橘色模块不通过的请求就会报错,被其接受
2.认证流程
2.1 流程图
2.2 认证源码大致方向
- 大致源码流程比较绕口,不过不是想象中麻烦,具体源码我会在2.3 源码详情追综,希望大伙能坚持一下
- 递进流程:用户提交登入信息 —》AbstractAuthenticationProcessingFilter 的 dofilter(…)方法 —》UsernamePasswordAuthenticationFilterr 的 attemptAuthentication(…)方法获取用户名和密码生成未认证的UsernamePasswordAuthenticationToken(是Authentication的子类) —》 ProviderManager(AuthenticatonManager的子类)的 authenticate(…) 方法接受UsernamePasswordAuthenticationToken参数 —》 AbstractUserDetailsAuthenticationProvider(AuthenticationProvider的子类)authenticate(…)的方法里面的的 retrieveUser(…) —》DaoAuthenticationProvider的 retrieveUser(…) ----》CustomUserDetailService(自己写的,是UserDetailService的子类)的loadUserByUsername(…) 获取用户信息
- 返回流程:CustomUserDetailService 的 loadUserByUsername(…) 获取用户信息后—》AbstractUserDetailsAuthenticationProvider 的 createSuccessAuthentication(…) 把未认证的UsernamePasswordAuthenticationToken变成已认证的状态 —》AbstractAuthenticationProcessingFilter的successfulAuthentication(…) 的SecurityContextHolder.getContext().setAuthentication(authResult);把UsernamePasswordAuthenticationToken存到Session
2.2 源码详解
我会抠出 2.2 认证源码大致方向的代码里面关键部分并标序号注释讲解,建议大家用debug模式打点,亲自测试更好记忆
"---------------AbstractAuthenticationProcessingFilter ---------------------------------"
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
... 省略部分
try {
// 1. 调用UsernamePasswordAuthenticationFilterr(其子类)的attemptAuthentication(...)方法,跳到下个分割线处
authResult = attemptAuthentication(request, response);
if (authResult == null) {
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) ...异常处理省略
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 16. 获取序号15的Authentication对象,调用该方法
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);
}
// 17. 获取序号15的Authentication对象,保存在session中
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
// 18.成功处理
successHandler.onAuthenticationSuccess(request, response, authResult);
}
"--------------------UsernamePasswordAuthenticationFilterr--------------------"
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();
// 2. 生成UsernamePasswordAuthenticationToken(Authentication的子类)对象,跳到下个分割线处
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
setDetails(request, authRequest);
// 4. this.getAuthenticationManager() 获取Authentication的实现类,即ProviderManager,跳到下个分割线处
// 15.获取序号14的Authenticaition对象,返回上个分割线
return this.getAuthenticationManager().authenticate(authRequest);
}
"--------------UsernamePasswordAuthenticationToken构造方法------------------------------"
// 3. 因为刚登入所以是false,未认证状态, 回到上个分割线处
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
"--------------------------------ProviderManager-----------------------------------"
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
...省略部分
// 5. 选择那种认证方式
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) { // 判断该认证用那种AuthenticationProvider的实现类实现
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
// 6. 找到相应的认证方式的子类后,即AbstractUserDetailsAuthenticationProvider类调用方法,跳到下个分割线处
result = provider.authenticate(authentication);
if (result != null) {
// 13.把序号12的UserDetails对象,赋值到result(Authentication)对象
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) ...异常处理省略
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
// 14. 序号13的result对象,到上个分割线
return result;
}
}
"--------------------AbstractUserDetailsAuthenticationProvider----------------------"
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
...省略部分
if (user == null) {
cacheWasUsed = false;
try {
// 7. 通过DaoAuthenticationProvider(即AbstractUserDetailsAuthenticationProvide的子类)实现类方法来获取UserDetail对象,跳到下个分割线处
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
// 11. 获取序号10的的UserDetails对象
}
catch (UsernameNotFoundException notFound) ... 异常处理省略
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
// xxx.check(user) 用于验证用户信息是否合理,跟UserDetails构造方法有关,里面有一个为false,则报错不通过
// User user = new User("zhangsan", /// 用户名
// password, // 密码
// true, // 账号是否失效
// true, // 账号是否过期
// true, // 密码是否过期
// true, // 账号是否冻结
// AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
try {
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) ...异常处理省略
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// 12. 返回序号11的的UserDetails对象, 通过createSuccessAuthentication使其变成已认证状态
return createSuccessAuthentication(principalToReturn, authentication, user);
}
"------------------------------------DaoAuthenticationProvider--------------------------"
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// 8. 通过自定义UserDetailService的子类来获取UserDetails对象---用户信息,跳到下个分割线
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
// 10. 获取序号9的UserDetails对象,返回上个分割线
return loadedUser;
}
catch (UsernameNotFoundException ex) ...异常处理省略
}
"----------------------CustomUserDetailService-----------------------------------"
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 9。获取用户信息,不连接数据库(虚拟账号),获取成功跳到上个分割线
log.info("【CustomUserDetailService】根据用户名获取用户信息, username = {}", username);
String password = passwordEncoder.encode("123");
User user = new User("zhangsan", /// 用户名
password, // 密码
true, // 账号是否失效
true, // 账号是否过期
true, // 密码是否过期
true, // 账号是否冻结
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
log.info("【CustomUserDetailService】 password = {}", password);
log.info("【CustomUserDetailService】获取User对象,user = {}", user);
return user;
}