JAVA——springSecurity——底层原理分析:处理认证请求和非认证请求的流程,主要过滤器链的作用
Posted 叶不修233
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA——springSecurity——底层原理分析:处理认证请求和非认证请求的流程,主要过滤器链的作用相关的知识,希望对你有一定的参考价值。
JAVA——springSecurity底层原理分析:处理认证请求和非认证请求的流程,12条过滤器链的作用
一、认证请求(login)
1.spring管理过滤器的生命周期
在加载时启动一个叫DelegatingFilterProxy的过滤器代理对象,让spring管理这些过滤器的生命周期
DelegatingFilterProxy类——doFilter()
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException
//启动一个叫DelegatingFilterProxy的过滤器代理对象
Filter delegateToUse = this.delegate;
if (delegateToUse == null)
synchronized(this.delegateMonitor)
delegateToUse = this.delegate;
if (delegateToUse == null)
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null)
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
delegateToUse = this.initDelegate(wac);
this.delegate = delegateToUse;
this.invokeDelegate(delegateToUse, request, response, filterChain);
2.初始化一个FilterChainProxy过滤器链代理对象
初始化一个过滤链FilterChainProxy过滤器链的代理对象,在这个过滤器链代理对象中有一个过滤器链集合,每一个过滤器链都有一组过滤器来处理不同的请求
FilterChainProxy类——doFilter()
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
if (this.currentPosition == this.size)
if (FilterChainProxy.logger.isDebugEnabled())
FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " reached end of additional filter chain; proceeding with original chain");
this.firewalledRequest.reset();
this.originalChain.doFilter(request, response);
else
++this.currentPosition;
Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
if (FilterChainProxy.logger.isDebugEnabled())
FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " at position " + this.currentPosition + " of " + this.size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'");
//迭代过滤器
nextFilter.doFilter(request, response, this);
3.处理认证请求
(1)判断是否是认证请求
AbstractAuthenticationProcessingFilter类——doFilter()
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//(1)判断是否是认证请求,如果不是,直接放行
if (!this.requiresAuthentication(request, response))
chain.doFilter(request, response);
else
//此时/login是认证的请求,执行else部分代码
if (this.logger.isDebugEnabled())
this.logger.debug("Request is to process authentication");
...
(2)调用试图认证的方法
AbstractAuthenticationProcessingFilter类——doFilter()
如果是认证请求,调用试图认证的方法: attemptAuthentication()
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//(1)判断是否是认证请求,如果不是,直接放行
if (!this.requiresAuthentication(request, response))
chain.doFilter(request, response);
else
//此时/login是认证的请求,执行else部分代码
if (this.logger.isDebugEnabled())
this.logger.debug("Request is to process authentication");
//声明认证的token对象 authResult
Authentication authResult;
try
//(2)如果是认证请求,调用试图认证的方法: attemptAuthentication()
authResult = this.attemptAuthentication(request, response);
if (authResult == null)
return;
...
(3)判断请求类型是否为post
UsernamePasswordAuthenticationFilter类——attemptAuthentication()
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
//当前必须是post请求,如果不是则抛出异常
if (this.postOnly && !request.getMethod().equals("POST"))
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
else
//是post请求,则获取表求中的用户名与密码
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null)
username = "";
if (password == null)
password = "";
username = username.trim();
//将用户名与密码封装至 UsernamePasswordAuthenticationToken对象中
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
//获取认证管理器ProviderManager对象,并调用认证的方法
return this.getAuthenticationManager().authenticate(authRequest);
(4)做认证处理
通过ProviderManager认证管理器对象,调用authenticate()方法来做认证处理,在该方法中又去调用了一个认证器DaoAuthenticationProvider的authenticate()方法【注:该类没有此方法,需要从父类AbstractUserDetailsAuthenticationProvider继承中的方法authenticate()调用】
ProviderManager类——authenticate()
public Authentication authenticate(Authentication authentication) throws AuthenticationException
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
Iterator var8 = this.getProviders().iterator();
while(var8.hasNext())
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
if (provider.supports(toTest))
if (debug)
logger.debug("Authentication attempt using " + provider.getClass().getName());
try
//进入这个方法
result = provider.authenticate(authentication);
if (result != null)
this.copyDetails(authentication, result);
break;
...
AbstractUserDetailsAuthenticationProviderf类——authenticate()
调用retrieveUser()方法,查询用户. 封装成UserDetails接口对象返回
public Authentication authenticate(Authentication authentication) throws AuthenticationException
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () ->
return 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
//查询用户. 以UserDetails接口对象返回
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
...
public interface UserDetails extends Serializable
//权限集合
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword(); //密码
String getUsername(); //用户名
boolean isAccountNonExpired(); //true 表示没有过期
boolean isAccountNonLocked(); //是否没有被锁定
boolean isCredentialsNonExpired(); //密码是否没有过期
boolean isEnabled(); //是否可用
(5)通过用户名查询用户对象
springSecurity框架自带的loadUserByUsername(username)方法
DaoAuthenticationProvider类——retrieveUser方法
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException
this.prepareTimingAttackProtection();
try
//调用loadUserByUsername(username)方法
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null)
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
else
return loadedUser;
...
InMemoryUserDetailsManager类——loadUserByUsername(username)方法
即从内存中读取username
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
UserDetails user = (UserDetails)this.users.get(username.toLowerCase());
if (user == null)
throw new UsernameNotFoundException(username);
else
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
这里的username我们可以在自定义的配置类WebSecurityConfig中配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
//在内存中配置用户名和密码
auth.inMemoryAuthentication().withUser("tom")
.password(passwordEncoder.encode("123")).roles();
auth.inMemoryAuthentication().withUser("admin")
.password(passwordEncoder.encode("admin")).roles();
②我们自定义的loadUserByUsername(username)方法
要想从自定义的数据库中读取账号和密码,同样可以在我们自定义的配置类WebSecurityConfig中配置,详见三、细节补充——(2)重写loadUserByUsername()方法
(6)检查用户是否通过认证
AbstractUserDetailsAuthenticationProvider类——authenticate()方法,调用preAuthenticationChecks.check()
try
//检验返回的用户是否可以使用
this.preAuthenticationChecks.check(user);
//将用户与提供的表单用户token进行比对
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
catch (AuthenticationException var7)
...
....
DaoAuthenticationProvider类——additionalAuthenticationChecks()方法
验证(内存或数据库)用户名密码是否匹配成功
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException
if (authentication.getCredentials() == null)
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
else
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword()))
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
(7)通过认证,生成token
如果用户名与密码是正确的,则执行父类AbstractUserDetailsAuthenticationProvider中authenticate()中剩下的代码,最后返回return this.createSuccessAuthentication(principalToReturn, authentication, user)
DaoAuthenticationProvider类——createSuccessAuthentication()方法
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user)
//根据给定的principal和密码信息,及权限列表重新生成一个token
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
而这个方法是重新为我们生成一个token,此token包装了用户的权限列表集。然后返回此token,接着一步步的向上返回这个 result结果对象
最终,返回到了源头AbstractAuthenticationProcessingFilter类的doFilter()方法,而此方法的末尾:
this.successfulAuthentication(request, response, chain, authResult);
此方法的关键:
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException
//将重新生成的token装载到SecurityContext中有很大作用---将来做鉴权时使用
SecurityContextHolder.getContext().setAuthentication(authResult);
//此行代码就是认证成功后的处理器
this.successHandler.onAuthenticationSuccess(request, response, authResult);
this.successHandler.onAuthenticationSuccess(request, response, authResult);执行的是SavedRequestAwareAuthenticationSuccessHandler类中的onAuthenticationSucess()方法:
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
if (savedRequest == null)
super.onAuthenticationSuccess(request, response, authentication);
else
String targetUrlParameter = this.getTargetUrlParameter();
if (!this.isAlwaysUseDefaultTargetUrl() && (targetUrlParameter == null || !StringUtils.hasText(request.getParameter(targetUrlParameter))))
this.clearAuthenticationAttributes(request);
String targetUrl = savedRequest.getRedirectUrl();
this.logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
this.getRedirectStrategy().sendRedirect(request, response, targetUrl);
else
this.requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
由于认证成功,【只走到了部份的过滤器】,而我们没有去重写成功后的handler,所以根据security的配置,走默认的url -->/home再次请求,要走一个过滤器链11个
二、非认证请求(home)
1.spring管理过滤器的生命周期
在加载时启动一个叫DelegatingFilterProxy的过滤器代理对象,让spring管理这些过滤器的生命周期
DelegatingFilterProxy类——doFilter()
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException
//启动一个叫DelegatingFilterProxy的过滤器代理对象
Filter delegateToUse = this.delegate;
if (delegateToUse == null)
synchronized(this.delegateMonitor)
delegateToUse = this.delegate;以上是关于JAVA——springSecurity——底层原理分析:处理认证请求和非认证请求的流程,主要过滤器链的作用的主要内容,如果未能解决你的问题,请参考以下文章