1、过滤器链加载源码
1.1、过滤器链加载流程分析
在第二部分的时候讲解的时候说springSecurity中主要功能是由过滤器链来完成的, 那么spring boot是如何加载这个流程的呢?
1.2、过滤器链加载流程源码分析?
1. spring boot启动中会加载spring.factories文件, 在文件中有对应针对Spring Security的过滤器链的配置信息
# 安全过滤器自动配置
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
2. SecurityFilterAutoConfiguration类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)// Security配置类
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,
SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)// 这个类加载完后去加载
SecurityAutoConfiguration配置
public class SecurityFilterAutoConfiguration {
.....
}
3. SecurityAutoConfiguration类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class,
WebSecurityEnablerConfiguration.class,//web安全启用配置
SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
.....
}
4. WebSecurityEnablerConfiguration类
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
}
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
@EnableWebSecurity注解有两个作用,1: 加载了WebSecurityConfiguration配置类, 配置安全认证策略。2: 加载了AuthenticationConfiguration, 配置了认证信息。
5. WebSecurityConfiguration类
// springSecurity过滤器链声明
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain()throws Exception{
boolean hasConfigurers=webSecurityConfigurers!=null &&!webSecurityConfigurers.isEmpty();
if(!hasConfigurers){
WebSecurityConfigurerAdapter adapter=objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter(){
});
webSecurity.apply(adapter);
}
return webSecurity.build();// 构建filter
}
2、认证流程源码
2.1、认证流程分析
在整个过滤器链中, UsernamePasswordAuthenticationFilter是来处理整个用户认证的流程的, 所以下面我们主要针对用户认证来看下源码是如何实现的?
2.2、认证流程源码跟踪?
UsernamePasswordAuthenticationFilter
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)throws AuthenticationException{
// 1.检查是否是post请求
if(postOnly&&!request.getMethod().equals("POST")){
throw new AuthenticationServiceException(
"Authentication method not supported: "+
request.getMethod());
}
// 2.获取用户名和密码
String username=obtainUsername(request);
String password=obtainPassword(request);
if(username==null){
username="";
}
if(password==null){
password="";
}
username=username.trim();
// 3.创建AuthenticationToken,此时是未认证的状态
UsernamePasswordAuthenticationToken authRequest=new UsernamePasswordAuthenticationToken(username,password);
// Allow subclasses to set the "details" property
setDetails(request,authRequest);
// 4.调用AuthenticationManager进行认证.
return this.getAuthenticationManager().authenticate(authRequest);
}
UsernamePasswordAuthenticationToken
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;//设置用户名
this.credentials = credentials;//设置密码
setAuthenticated(false);//设置认证状态为-未认证
}
AuthenticationManager-->ProviderManager-->AbstractUserDetailsAuthenticationProvider
public Authentication authenticate(Authentication authentication)
throws AuthenticationException{
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class,
authentication,
()->messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// 1.获取用户名
String username=(authentication.getPrincipal()==null)?
"NONE_PROVIDED"
:authentication.getName();
// 2.尝试从缓存中获取
boolean cacheWasUsed=true;
UserDetails user=this.userCache.getUserFromCache(username);
if(user==null){
cacheWasUsed=false;
try{
// 3.检索User
user=retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
.....
}
try{
// 4. 认证前检查user状态
preAuthenticationChecks.check(user);
// 5. 附加认证证检查
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
.....
// 6. 认证后检查user状态
postAuthenticationChecks.check(user);
.....
// 7. 创建认证成功的UsernamePasswordAuthenticationToken并将认证状态设置为true
return createSuccessAuthentication(principalToReturn, authentication,user);
}
retrieveUser方法
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//调用自定义UserDetailsService的loadUserByUsername的方法
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
....
}
additionalAuthenticationChecks方法
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
.....
// 1.获取前端密码
String presentedPassword = authentication.getCredentials().toString();
// 2.与数据库中的密码进行比对
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
AbstractAuthenticationProcessingFilter--doFilter方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
.....
Authentication authResult;
try {
//1.调用子类方法
authResult = attemptAuthentication(request, response);
...
//2.session策略验证
sessionStrategy.onAuthentication(authResult, request, response);
}
....
// 3.成功身份验证
successfulAuthentication(request, response, chain, authResult);
}
successfulAuthentication方法
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
....
// 1.将认证的用户放入SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authResult);
// 2.检查是不是记住我
rememberMeServices.loginSuccess(request, response, authResult);
...
// 3.调用自定义MyAuthenticationService的onAuthenticationSuccess方法
successHandler.onAuthenticationSuccess(request, response, authResult);
}
3、记住我流程源码
在整个过滤器链中, RememberMeAuthenticationFilter是来处理记住我用户认证的流程的, 所以下面我们主要针对记住我看下源码是如何实现的?
3.1、记住我流程分析?
3.2、记住我流程源码跟踪?
AbstractAuthenticationProcessingFilter--successfulAuthentication方法
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
....
// 1.将认证的用户放入SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authResult);
// 2.检查是不是记住我
rememberMeServices.loginSuccess(request, response, authResult);
...
// 3.调用自定义MyAuthenticationService的onAuthenticationSuccess方法
successHandler.onAuthenticationSuccess(request, response, authResult);
}
loginSuccess方法-->onLoginSuccess
protected void onLoginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication) {
// 1.获取用户名
String username = successfulAuthentication.getName();
// 2.创建persistentToken
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
username, generateSeriesData(), generateTokenData(), new Date());
try {
// 3. 插入数据库
tokenRepository.createNewToken(persistentToken);
// 4. 写入浏览器cookie
addCookie(persistentToken, request, response);
}
catch (Exception e) {
logger.error("Failed to save persistent token ", e);
}
}
RememberMeAuthenticationFilter
public void doFilter(ServletRequest req,ServletResponse res,FilterChain chain)
throws IOException,ServletException{
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if(SecurityContextHolder.getContext().getAuthentication()==null)
{
// 1. 检查是否是记住我登录. 如果是完成自动登录
Authentication rememberMeAuth = rememberMeServices.autoLogin(request, response);
if(rememberMeAuth!=null){
try{
// 2.调用authenticationManager再次认证
rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
// 3.将认证的用户在重新放入SecurityContext中
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
......
}
......
}
.....
// 4.调用下一个过滤器
chain.doFilter(request,response);
}
}
autoLogin方法
public final Authentication autoLogin(HttpServletRequest request,
HttpServletResponse response) {
// 1.获取rememberMeCookie
String rememberMeCookie = extractRememberMeCookie(request);
// 2.检查是否存在
if (rememberMeCookie == null) {
return null;
}
.....
UserDetails user = null;
try {
// 3.解码Cookie
String[] cookieTokens = decodeCookie(rememberMeCookie);
// 4.根据cookie完成自动登录
user = processAutoLoginCookie(cookieTokens, request, response);
// 5.检查user状态
userDetailsChecker.check(user);
logger.debug("Remember-me cookie accepted");
// 6.创建认证成功的RememberMeAuthenticationToken并将认证状态设置为true
return createSuccessfulAuthentication(request, user);
}
.....
return null;
}
processAutoLoginCookie方法
protected UserDetails processAutoLoginCookie(String[] cookieTokens,
HttpServletRequest request, HttpServletResponse response) {
....
// 1.获取系列码和token
final String presentedSeries = cookieTokens[0];
final String presentedToken = cookieTokens[1];
// 2.根据token去数据库中查询
PersistentRememberMeToken token = tokenRepository.getTokenForSeries(presentedSeries);
.......
// 3.在创建一个新的token
PersistentRememberMeToken newToken = new PersistentRememberMeToken(
token.getUsername(), token.getSeries(), generateTokenData(),
new Date());
try {
// 4.修改数据库token信息
tokenRepository.updateToken(newToken.getSeries(),
newToken.getTokenValue(),
newToken.getDate());
// 5.写入浏览器
addCookie(newToken, request, response);
}
// 6.根据用户名调用UserDetailsService查询UserDetail
return
getUserDetailsService().loadUserByUsername(token.getUsername());
4、csrf流程源码
在整个过滤器链中, CsrfFilter是起到csrf防护的, 所以下面我们主要针对记住我看下源码是如何实现的?
4.1、csrf流程分析
4.2、csrf流程源码跟踪?
CsrfFilter
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 1.取出token
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = csrfToken == null;
if (missingToken) {
// 2. 如果没有token,就重新生成token
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
// 3. 将csrfToken值放入request域中
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
// 4. 匹配请求是否为post请求,不是则放行
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match " + this.requireCsrfProtectionMatcher);
}
filterChain.doFilter(request, response);
} else {
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
// 5.从request请求参数中取出csrfToken
actualToken = request.getParameter(csrfToken.getParameterName());
}
// 6.匹配两个token是否相等
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
this.logger.debug(LogMessage.of(() -> {
return "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request);
}));
AccessDeniedException exception = !missingToken ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
} else {
// // 7. 如果相等则放行
filterChain.doFilter(request, response);
}
}
}
5、授权流程源码
在整个过滤器链中, FilterSecurityInterceptor是来处理整个用户授权流程的, 也是距离用户API最后一个非常重要的过滤器链,所以下面我们主要针对用户授权来看下源码是如何实现的?
5.1、授权流程分析
AffirmativeBased(基于肯定)的逻辑是: 一票通过权
ConsensusBased(基于共识)的逻辑是: 赞成票多于反对票则表示通过,反对票多于赞成票则将抛出AccessDeniedException
UnanimousBased(基于一致)的逻辑: 一票否决权?
5.2、授权流程源码跟踪
FilterSecurityInterceptor
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.security.web.access.intercept;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
public FilterSecurityInterceptor() {
}
public void init(FilterConfig arg0) {
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
// 调用
this.invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
if (fi.getRequest() != null && this.observeOncePerRequest) {
fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
//前置调用
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.finallyInvocation(token);
}
// 后置调用
super.afterInvocation(token, (Object)null);
}
}
public boolean isObserveOncePerRequest() {
return this.observeOncePerRequest;
}
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
}
AbstractSecurityInterceptor的beforeInvocation方法?
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
boolean debug = this.logger.isDebugEnabled();
if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());
} else {
// 1. 获取security的系统配置权限
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (attributes != null && !attributes.isEmpty()) {
if (debug) {
this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
}
// 2. 获取用户认证的信息
Authentication authenticated = this.authenticateIfRequired();
// Attempt authorization
try {
// 3.调用决策管理器
this.accessDecisionManager.decide(authenticated, object, attributes);
} catch (AccessDeniedException var7) {
this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
// 4.无权限则抛出异常让ExceptionTranslationFilter捕获
throw var7;
}
........
}
AffirmativeBased的decide方法
public class AffirmativeBased extends AbstractAccessDecisionManager {
public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
Iterator var5 = this.getDecisionVoters().iterator();
while(var5.hasNext()) {
AccessDecisionVoter voter = (AccessDecisionVoter)var5.next();
int result = voter.vote(authentication, object, configAttributes);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Voter: " + voter + ", returned: " + result);
}
//一票通过,只要有一个投票器通过就允许访问
switch (result) {
case AccessDecisionVoter.ACCESS_DENIED:
++deny;
break;
case AccessDecisionVoter.ACCESS_GRANTED:
return;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
} else {
this.checkAllowIfAllAbstainDecisions();
}
}
}
ExceptionTranslationFilter
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
// 1.调用下一个过滤器即FilterSecurityInterceptor
chain.doFilter(request, response);
} catch (IOException var7) {
throw var7;
} catch (Exception var8) {
// 2.捕获FilterSecurityInterceptor并判断异常类型
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var8);
RuntimeException securityException = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
securityException = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (securityException == null) {
this.rethrow(var8);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var8);
}
// 3.如果是AccessDeniedException异常则处理Spring Security异常
this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException);
}
}
handleSpringSecurityException方法
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
this.handleAuthenticationException(request, response, chain, (AuthenticationException)exception);
} else if (exception instanceof AccessDeniedException) {
this.handleAccessDeniedException(request, response, chain, (AccessDeniedException)exception);
}
}
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
if (!isAnonymous && !this.authenticationTrustResolver.isRememberMe(authentication)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Sending %s to access denied handler since access is denied", authentication), exception);
}
//如果是AccessDeniedException异常则调用AccessDeniedHandler的handle方法
this.accessDeniedHandler.handle(request, response, exception);
} else {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied", authentication), exception);
}
this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));
}
}
|