过滤器链的加载流程
<!--认证授权过滤器-->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<!--委派过滤器-->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
进入DelegatingFilterProxy
类,查看继承关系,可以看出它是个过滤器
public class DelegatingFilterProxy extends GenericFilterBean {}
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {}
核心方法
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
进入initDelegate
方法,此处targetBeanName
的值为springSecurityFilterChain
,这个方法会返回一个filterChainProxy
对象
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
通过filterChainProxy
对象找其所属的类
进入类中,找doFilter
方法,内部有一个无论如何都会走doFilterInternal(request, response, chain);
方法
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
进入doFilterInternal(request, response, chain);
方法,关键位置getFilters(fwRequest);
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
getFilters(fwRequest);
方法会返回13个过滤器
至此委派过滤器作用到此结束。
下面一次讲述以下各过滤器的作用
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter 主要是使用 SecurityContextRepository在 session 中保存或更新一个 SecurityContext,并将 SecurityContext 给以后的过滤器使用,来为后续 filter 建立所需的上下文。SecurityContext 中存储了当前用户的认证以及权限信息。
简单的说,就是初始化一个类似于 springIOC 的容器。WebAsyncManagerIntegrationFilter
此过滤器用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager
如果 springSecurity 要整合到 spring 工程里面去,必须使用这个过滤器。HeaderWriterFilter
向请求的 Header 中添加相应的信息,可在 http 标签内部使用 security:headers 来控制CsrfFilter
csrf 又称跨域请求伪造,SpringSecurity 会对所有 post 请求验证是否包含系统生成的 csrf 的 token 信息,如果不包含,则报错。起到防止 csrf 攻击的效果。logout.LogoutFilter
匹配 URL 为/logout 的请求,实现用户退出,清除认证信息。UsernamePasswordAuthenticationFilter
认证操作全靠这个过滤器,默认匹配 URL 为/login 且必须为 POST 请求。DefaultLoginPageGeneratingFilter
(弃用)
如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。DefaultLogoutPageGeneratingFilter
(弃用)
由此过滤器可以生产一个默认的退出登录页面BasicAuthenticationFilter
此过滤器会自动解析 HTTP 请求中头部名字为 Authentication,且以 Basic 开头的头信息。
简而言之就是解析 Http 请求头信息。RequestCacheAwareFilter
通过 HttpSessionRequestCache 内部维护了一个 RequestCache,用于缓存HttpServletRequestSecurityContextHolderAwareRequestFilter
针对 ServletRequest 进行了一次包装,使得 request 具有更加丰富的 APIAnonymousAuthenticationFilter
当 SecurityContextHolder 中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder 中。springsecurity 为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。SessionManagementFilter
SecurityContextRepository 限制同一用户开启多个会话的数量ExceptionTranslationFilter
异常转换过滤器位于整个 springSecurityFilterChain 的后方,用来转换整个链路中出现的异常。简单说就是,进行 springSecurity 中的异常处理。FilterSecurityInterceptor
获取所配置资源访问的授权信息,根据 SecurityContextHolder 中存储的用户信息来决定其是否有权限。简单的说就是授权。
问:是不是 springsecurity 一共就这么多过滤器呢?
答案:不是!随着 spring-security.xml 配置的添加,还会出现新的过滤器。
问:是不是 springsecurity 每次都会加载这些过滤器呢?
答案:不是!随着 spring-security.xml 配置的修改,有些过滤器可能会被去掉。
CsrfFilter
过滤器的底层原理
CsrfFilter类中doFilterInternal
方法
- 当request请求方式为
trace / get / head / options
时,直接放行; - 当request请求方式为post时,拦截请求,获取token,如果token存在,放行,不存在交给
this.accessDeniedHandler.handle(request, response, exception);
处理。
@Override
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) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
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);
}
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
this.logger.debug(
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
: new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, exception);
return;
}
filterChain.doFilter(request, response);
}
认证流程分析
还记得 UsernamePasswordAuthenticationFilter 这个过滤器吗,主要负责认证的过滤器
找到这个 UsernamePasswordAuthenticationFilter 这个类,核心方法attemptAuthentication
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
进入authenticate(authRequest)
方法,是个接口方法,找其实现类ProviderManager
,是不是很熟悉?
ProviderManager 内部的authenticate
方法,getProviders()获取所有的认证提供者,找一个合适的来处理,返回值是一个AuthenticationProvider(接口)对象,找其实现类AbstractUserDetailsAuthenticationProvider
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();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
result = provider.authenticate(authentication);
//...
}
}
///....
}
实现类AbstractUserDetailsAuthenticationProvider
中,核心方法authenticate()中,关键方法retrieveUser
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
进入 retrieveUser 方法,是个抽象方法,找其实现类,DaoAuthenticationProvider
中的retrieveUser方法,以下是关键位置
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
进入loadUserByUsername(username);
方法,是一个 UserDetailsService 接口,返回值是UserDetails类型的接口,找UserDetails的实现类—>User,User内部有两个构造方法,完成认证过程。
public User(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
public User(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
//...
}
所以,我们若是想要自定义认证,就必须实现UserDetailsService
接口,重写 loadUserByUsername
方法,返回一个UserDetails
对象,通过User构造函数完成认证。
方式一:
自定义一个类,实现UserDetailsService接口…
方式二:
使用UserService接口,继承UserDetailsService接口,使用UserServiceImpl实现UserService接口,重写方法
无论是那种方式,本质是没有区别的
区别点:
<!-- <bean id="authenticateManage" class="com.herd.aspect.UserInfoAuthenticateManage">-->
<!-- </bean>-->
<!-- <security:authentication-provider user-service-ref="authenticateManage">-->
<security:authentication-provider user-service-ref="userServiceImpl">
</security:authentication-provider>