照例先拜读大佬文章
3. 过滤器详解
按照大佬博文的走向,前面几篇并没有提到过滤器这个东西。然而在看前面几篇博文的时候,就会产生疑问:
Authentication 是从哪里来的,问什么它出现在认证环节的时候,里面就已经封装好了数据。
读到当前篇章的时候这个问题得到解答。SpringSecurity使用springSecurityFilterChian作为安全过滤的入口。
回答上面的问题,答案如下:
用户的信息在经过AbstractAuthenticationProcessingFilter
的时候,AbstractAuthenticationProcessingFilter#doFilter
方法调用了其子类UsernamePasswordAuthenticationFilter
实现的attemptAuthentication()
方法来对用户的数据做了一次UsernamePasswordAuthenticaionToken
封装,并设置了Details
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
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();
//将用户的信息封装了一次
UsernamePasswordAuthenticationToken authRequest
= new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//提交封装好的信息给AuthenticationManager认证
return this.getAuthenticationManager().authenticate(authRequest);
}
当然除了上面的UsernamePasswordAuthenticationFilter
以外还有很多的Filter在数据流转的过程起作用,以下:
|
|
---|---|
SecurityContextPersistenceFilter | 主要两个职责: 1. 请求来临时,创建SecurityContext 安全上下文信息; 2.请求结束时清空SecurityContextHodler |
HeaderWriterFilter | 用来给http响应添加一些Header,比如X-Frame-Options,X-XSS-Protection*,X-Content-Type-Options |
CsrfFilter | Security4.x默认开启的过滤器,用于防止CSRF攻击 |
LogoutFilter | 处理注销的过滤器 |
UsernamePasswordAuthenticationFilter | 封装用户信息,提交认证等流程都在这个过滤器中实现 |
RequestCacheAwareFilter | 内部维护一个RequestCache,用于缓存request请求 |
SecurityContextHolderAwareRequestFilter | 对ServletRequest进行再一次的封装,使得Request具有更加丰富的API |
AnonymousAuthenticationFilter | 匿名身份过滤器,用于处理不用登陆也能访问的资源,这时的用户用户拥有匿名的身份 |
SessionManagerFilter | 和Session相关的过滤器,内部存储了一个SessionAuthenticationStrategy,两者组合使用: 1.防止session-fixation protection attack ; 2. 限同一用户开启多个会话的数量 |
ExceptionTranslationFilter | 和异常处理相关的过滤器,但是一般不自己处理异常而是将一场交给内部维护的一些类去处理 |
FilterSecurityInterceptor | 这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限?这些判断都是通过该类进行的。 |
以上黑体加粗的都是被认为是Security 的核心过滤器。
SecurityContextPersistenceFilter
在我以前的开发场景中,如果我需要保存用户信息,我会使用session来保存用户信息。Security也是通过session来保存用户信息,后续的通过sessionid
来鉴别用户是否已经登陆。
Security使用了SecurityContextHolder来存储用户信息:
SecurityContextPersistenceFilter
public class SecurityContextPersistenceFilter extends GenericFilterBean {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
private SecurityContextRepository repo;
private boolean forceEagerSessionCreation = false;
public SecurityContextPersistenceFilter() {this(new HttpSessionSecurityContextRepository());}
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {this.repo = repo;}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
//包装一下request和response
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
//从Session中获取安全上下文信息
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
//请求开始时设置安全上下文信息,这样就避免用户直接从Session中获取安全上下文信息
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
//请求结束后清空安全上下文信息
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
}
HttpSessionSecurityContextRepository
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
// 'SPRING_SECURITY_CONTEXT'是安全上下文默认存储在Session中的键值
public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
...
private final Object contextObject = SecurityContextHolder.createEmptyContext();
private boolean allowSessionCreation = true;
private boolean disableUrlRewriting = false;
private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
//从当前request中取出安全上下文,如果session为空,则会返回一个新的安全上下文
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
context = generateNewContext();
}
...
return context;
}
...
public boolean containsContext(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
return session.getAttribute(springSecurityContextKey) != null;
}
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
if (httpSession == null) {
return null;
}
...
// Session存在的情况下,尝试获取其中的SecurityContext
Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);
if (contextFromSession == null) {
return null;
}
...
return (SecurityContext) contextFromSession;
}
//初次请求时创建一个新的SecurityContext实例
protected SecurityContext generateNewContext() {
return SecurityContextHolder.createEmptyContext();
}
}
UsernamePasswordAuthenticationFilter
表单认证是最常用的一种认证方式,一个最直观的业务场景便是允许用户 在表单中输入用户名和密码进行登陆,而这背后的UsernamePasswordAuthenticationFilter
,在整个SpringSecurity的认证体系中扮演了一个至关重要的角色。
上述时序图,主要的职责就是调取身份验证器校验用户的身份。
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
//获取表单中的用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);
...
username = username.trim();
//组装成username+password形式的token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//交给内部的AuthenticationManager去认证,并返回认证信息
return this.getAuthenticationManager().authenticate(authRequest);
}
很简单的一个方法,从request中去获得用户名和密码,做一下token封装,调一下认证管理器认证一下。然而事实并没有那么简单,由此我也知道了Abstract类的作用了。父类AbstractAuthenticationProcessingFilter
实现了大量的细节,把这个比较不重要而又很重要的交给我们的定制。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
//包含了一个身份认证器
private AuthenticationManager authenticationManager;
//用于实现remeberMe
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private RequestMatcher requiresAuthenticationRequestMatcher;
//这两个Handler很关键,分别代表了认证成功和失败相应的处理器
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
...
Authentication authResult;
try {
//此处实际上就是调用UsernamePasswordAuthenticationFilter的attemptAuthentication方法
authResult = attemptAuthentication(request, response);
if (authResult == null) {
//子类未完成认证,立刻返回
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
//在认证过程中可以直接抛出异常,在过滤器中,就像此处一样,进行捕获
catch (InternalAuthenticationServiceException failed) {
//内部服务异常
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
//认证失败
unsuccessfulAuthentication(request, response, failed);
return;
}
//认证成功
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//注意,认证成功后过滤器把authResult结果也传递给了成功处理器
successfulAuthentication(request, response, chain, authResult);
}
}
AnonymousAuthenticationFilter
Spring Security 为了整体逻辑上的统一,即便没有认证通过的用户也赋予了一个身份。AnonymousAuthenticationFilter
这个过滤器位于UsernamePasswordAuthenticationFilter
后面ExceptionTranslationFilter
前面,中间还有类似的用户身份认证过滤器BasicAuthticationFilter
,RememberMeAuthenticationFilter
,如果没有在前面的过滤器中给SecurityContext设置上用户信息,那么在这个过滤器中就会设置一个匿名的用户身份。
public class AnonymousAuthenticationFilter extends GenericFilterBean implements
InitializingBean {
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private String key;
private Object principal;
private List<GrantedAuthority> authorities;
//自动创建一个"anonymousUser"的匿名用户,其具有ANONYMOUS角色
public AnonymousAuthenticationFilter(String key) {
this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
}
/**
* @param key key用来识别该过滤器创建的身份
* @param principal principal代表匿名用户的身份
* @param authorities authorities代表匿名用户的权限集合
*/
public AnonymousAuthenticationFilter(String key, Object principal,
List<GrantedAuthority> authorities) {
Assert.hasLength(key, "key cannot be null or empty");
Assert.notNull(principal, "Anonymous authentication principal must be set");
Assert.notNull(authorities, "Anonymous authorities must be set");
this.key = key;
this.principal = principal;
this.authorities = authorities;
}
...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
//过滤器链都执行到匿名认证过滤器这儿了还没有身份信息,塞一个匿名身份进去
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
}
chain.doFilter(req, res);
}
protected Authentication createAuthentication(HttpServletRequest request) {
//创建一个AnonymousAuthenticationToken
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
principal, authorities);
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
...
}
对比AnonymousAuthenticationFilter
和UsernamePasswordAuthenticationFilter
就可以发现一些门道了,UsernamePasswordAuthenticationToken
对应AnonymousAuthenticationToken
,他们都是Authentication
的实现类,而Authentication则是被SecurityContextHolder(SecurityContext)
持有的,一切都被串联在了一起。