4.1 过滤链源码分析
4.1.1 过滤器链加载流程分析
4.1.2 过滤器链加载流程源码分析
- SpringBoot的自动配置会加载spring.factories文件,在文件中有针对Spring Security的过滤器链 的配置信息org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,表明过滤器链的自动配置类是SecurityFilterAutoConfiguration类。
- SecurityFilterAutoConfiguration类 以及相关导入的类 ```java @Configuration( proxyBeanMethods = false ) @ConditionalOnWebApplication( type = Type.SERVLET ) @EnableConfigurationProperties({SecurityProperties.class}) @ConditionalOnClass({AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class}) // 在SecurityFilterAutoConfiguration类加载完成后加载SecurityAutoConfiguration类 @AutoConfigureAfter({SecurityAutoConfiguration.class}) public class SecurityFilterAutoConfiguration {
}
```java
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({DefaultAuthenticationEventPublisher.class})
@EnableConfigurationProperties({SecurityProperties.class}) // 定义默认的用户名和密码
@Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {
public SecurityAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean({AuthenticationEventPublisher.class})
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
SecurityProperties类:定义默认的用户名和密码
@ConfigurationProperties(
prefix = "spring.security"
)
public class SecurityProperties {
public static final int BASIC_AUTH_ORDER = 2147483642;
public static final int IGNORED_ORDER = -2147483648;
public static final int DEFAULT_FILTER_ORDER = -100;
private final SecurityProperties.Filter filter = new SecurityProperties.Filter();
private final SecurityProperties.User user = new SecurityProperties.User();
public SecurityProperties() {
}
public SecurityProperties.User getUser() {
return this.user;
}
public SecurityProperties.Filter getFilter() {
return this.filter;
}
// 定义了自动配置的user,也就是未连接数据库我们启动时可以使用登录的用户密码
public static class User {
private String name = "user";
private String password = UUID.randomUUID().toString();
private List<String> roles = new ArrayList();
private boolean passwordGenerated = true;
public User() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
if (StringUtils.hasLength(password)) {
this.passwordGenerated = false;
this.password = password;
}
}
public List<String> getRoles() {
return this.roles;
}
public void setRoles(List<String> roles) {
this.roles = new ArrayList(roles);
}
public boolean isPasswordGenerated() {
return this.passwordGenerated;
}
}
public static class Filter {
private int order = -100;
private Set<DispatcherType> dispatcherTypes;
public Filter() {
this.dispatcherTypes = new HashSet(Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Set<DispatcherType> getDispatcherTypes() {
return this.dispatcherTypes;
}
public void setDispatcherTypes(Set<DispatcherType> dispatcherTypes) {
this.dispatcherTypes = dispatcherTypes;
}
}
}
WebSecurityEnablerConfiguration类:开启web安全启动配置
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingBean(
name = {"springSecurityFilterChain"}
)
@ConditionalOnClass({EnableWebSecurity.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableWebSecurity // 开启Security安全功能
class WebSecurityEnablerConfiguration {
WebSecurityEnablerConfiguration() {
}
}
EnableWebSecurity注解类:开启security安全功能
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
WebSecurityConfiguration类:安全配置类,生成过滤器链
@Configuration(
proxyBeanMethods = false
)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
// 此方法返回的就是过滤器链
@Bean(
name = {"springSecurityFilterChain"}
)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
Assert.state(!hasConfigurers || !hasFilterChain, "Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
if (!hasConfigurers && !hasFilterChain) {
WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {
});
this.webSecurity.apply(adapter);
}
Iterator var7 = this.securityFilterChains.iterator();
while(true) {
while(var7.hasNext()) {
SecurityFilterChain securityFilterChain = (SecurityFilterChain)var7.next();
this.webSecurity.addSecurityFilterChainBuilder(() -> {
return securityFilterChain;
});
Iterator var5 = securityFilterChain.getFilters().iterator();
while(var5.hasNext()) {
Filter filter = (Filter)var5.next();
if (filter instanceof FilterSecurityInterceptor) {
this.webSecurity.securityInterceptor((FilterSecurityInterceptor)filter);
break;
}
}
}
var7 = this.webSecurityCustomizers.iterator();
while(var7.hasNext()) {
WebSecurityCustomizer customizer = (WebSecurityCustomizer)var7.next();
customizer.customize(this.webSecurity);
}
return (Filter)this.webSecurity.build();
}
}
}
生成过滤器链的过程,就是会读取我们自己编写的配置类,设置相关的过滤器信息到configure上,还有一些默认的过滤器也设置到configure,然后对configure进行一个个初始化,生成相应的过滤器类,最后对生成的过滤器进行排序返回。
4.2 认证源码分析
4.2.1 认证流程分析
4.2.2 认证流程源码跟踪
UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
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 {
// 从request请求中通过配置的参数获取用户名密码
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
// 创建UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// 调用认证管理器的认证方法
// 验证完成后就到父类的doFilter()方法,负责把认证信息放入SercurityContext
// 然后调用SuceessHandler来处理登录成功逻辑
return this.getAuthenticationManager().authenticate(authRequest);
}
}
}
ProviderManager
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
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;
int currentPosition = 0;
int size = this.providers.size();
// 获取providers,发现两个都不支持解析UsernamePasswordAuthenticationToken
Iterator var9 = this.getProviders().iterator();
while(var9.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var9.next();
// 判断支不支持
if (provider.supports(toTest)) {
if (logger.isTraceEnabled()) {
Log var10000 = logger;
String var10002 = provider.getClass().getSimpleName();
++currentPosition;
var10000.trace(LogMessage.format("Authenticating request with %s (%d/%d)", var10002, currentPosition, size));
}
try {
// 验证方法
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
}
if (result == null && this.parent != null) {
try {
// 判断两个认证器都不支持,所以交给父类去验证
// 父类同样是执行这个方法,发现Providers是DaoAuthenticationProvider
// provider.supports(toTest)判断时是
// 支持UsernamePasswordAuthenticationToken的
parentResult = this.parent.authenticate(authentication);
result = parentResult;
} catch (ProviderNotFoundException var12) {
} catch (AuthenticationException var13) {
parentException = var13;
lastException = var13;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
}
AbstractUserDetailsAuthenticationProvider: ProviderManager中调用的的result = provider.authenticate(authentication)方法
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
// 获取用户名
String username = this.determineUsername(authentication);
boolean cacheWasUsed = true;
// 通过用户名查询缓存中的user信息
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
// 检查user信息
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("Failed to find user '" + username + "'");
if (!this.hideUserNotFoundExceptions) {
throw var6;
}
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
// 检查用户是否锁定、启用等等
this.preAuthenticationChecks.check(user);
// 检查用户是否合法,比对参数:user是数据库查询的,authentication是前端传的
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) {
// 检查无误则把user放入缓存
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// 创建新的UsernamePasswordAuthenticationToken,已认证的,有授权信息的
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
}
DaoAuthenticationProvider: 上一步代码调用的检查user方法user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication)
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
// 调用我们自定义的MyUserDetailsService来获取UserDetails然后返回
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
// 上一步代码调用的
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since 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("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
}
4.2.3 总结
- 调用UsernamePasswordAuthenticationFilter类,从request中通过配置的参数取出账号密码,然后用账号密码生成UsernamePasswordAuthenticationToken,此时的token是未认证的。
- 调用认证管理器ProviderManager的认证方法authenticate(Authentication authentication)进行认证。
- 先匹配provider,通过比对发现DaoAuthenticationProvider支持UsernamePasswordAuthenticationToken的解析,然后调用provider.authenticate(authentication)进行验证。
- 验证其实是调用AbstractUserDetailsAuthenticationProvider中的authenticate(authentication)方法
- 获取用户名,查询user有没有缓存,没有则调用this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication)进行user检查。
- 对user进行前置检查,主要检查用户是否锁定,是否启用等等。
- this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication)检查用户是否合法,两个参数分别数据库查询的user和前端传的认证信息,来进行密码的比对。
- 检查无误把user放入缓存。
- 创建新的UsernamePasswordAuthenticationToken,已认证的,有授权信息的。
- 上一步的this.retrieveUser和this.additionalAuthenticationChecks都是在DaoAuthenticationProvider类中实现的。
- this.retrieveUser:调用我们自定义的UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username),得到userDetails信息并返回。
- this.additionalAuthenticationChecks:获取密码并比对前端与数据库的密码是否一致。
- 验证完成后就到UsernamePasswordAuthenticationFilter父类的doFilter()方法,负责把认证信息放入SercurityContext,以便后续我们可以通过SercurityContext获取认证信息,然后调用SuceessHandler来处理登录成功逻辑。
4.3 rememberMe源码分析
4.4 Csrf源码分析
4.4.1 csrf流程分析
4.4.2 csrf源码跟踪
CsrfFilter
public final class CsrfFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = csrfToken == null;
if (missingToken) {
// 生成token并封装,token值就是通过UUID生成
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
// 把token放入request请求域中
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match " + this.requireCsrfProtectionMatcher);
}
// 执行doFilter放行,放行到页面时要取出csrfToken的值,其实就是执行getToken方法
// 其实就是执行SaveOnAccessCsrfToken的getToken方法
filterChain.doFilter(request, response);
} else {
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
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 {
filterChain.doFilter(request, response);
}
}
}
}
LazyCsrfTokenRepository
public final class LazyCsrfTokenRepository implements CsrfTokenRepository {
private static final class SaveOnAccessCsrfToken implements CsrfToken {
public String getToken() {
// 把token存到session当中
this.saveTokenIfNecessary();
// 获取token返回
return this.delegate.getToken();
}
}
}
第一次请求总结:第一次请求页面的时候,会通过CsrfFilter来拦截请求,生成token并且封装放入request请求域中(token值是通过UUID生成),然后页面通过${_csrf.token}取值的时候,其实就是调用SaveOnAccessCsrfToken的getToken方法,将token保存到session域中,然后返回token值,这样,我们的页面上就有csrfToken的值了,下次请求就会带上这个token值。
第二次请求总结:this.tokenRepository.loadToken(request)可以从session中获取到csrfToken的值并返回,然后通过页面传过来的token值与session域中的token值进行比对,然后放行。
4.5 授权源码分析
在整个过滤器链中, FilterSecurityInterceptor是来处理整个用户授权流程的, 也是距离用户API最后一
个非常重要的过滤器链。
4.5.1 授权流程分析
- AffirmativeBased(基于肯定)的逻辑是: 一票通过权
- ConsensusBased(基于共识)的逻辑是: 赞成票多于反对票则表示通过,反对票多于赞成票则将抛出 AccessDeniedException
- UnanimousBased(基于一致)的逻辑:一票否决权
总结:FilterSecurityInterceptor类通过读取系统配置,获得了用户权限,然后交给AccessDecisionManager类去调用决策器AccessDecisionVoter进行权限认证,决策策略如上三种所示,决策通过则可以调用API,决策不通过则抛出异常,可以被异常过滤器ExceptionTranslationFilter捕获。
4.5.2 授权源码跟踪
FilterSecurityInterceptor
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.invoke(new FilterInvocation(request, response, chain));
}
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} else {
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
// 前置处理
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
}
AbstractSecurityInterceptor
public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
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 {
// 获取我们在配置类中编写的权限列表(antMatch的配置)
// getAttributes()通过遍历权限列表,匹配到与当前请求url相同的权限,并返回
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (CollectionUtils.isEmpty(attributes)) {
Assert.isTrue(!this.rejectPublicInvocations, () -> {
return "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'";
});
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Authorized public object %s", object));
}
this.publishEvent(new PublicInvocationEvent(object));
return null;
} else {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
}
// 从SecurityContext上下文中获取用户的所有权限信息
Authentication authenticated = this.authenticateIfRequired();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
}
// 通过this.accessDecisionManager.decide(authenticated, object, attributes)调用决策器去决定权限是否通过,默认是AffirmativeBased(一票通过),投票的返回值0表示弃权,1表示通过,-1表示不通过,通过则返回放行
this.attemptAuthorization(object, attributes, authenticated);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes));
}
if (this.publishAuthorizationSuccess) {
this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs != null) {
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs));
}
return new InterceptorStatusToken(origCtx, true, attributes, object);
} else {
this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null");
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
}
}
}
}
}