SpringBoot2.x过滤器OncePerRequestFilter(Spring内置Filter)
JAVA && Spring && SpringBoot2.x — 学习目录
SpringBoot2.x(Spring)
含有内置的Filter
。即OncePerRequestFilter
顾名思义:仅执行一次的Filter
。图1是OncePerRequestFilter
的子类:
在Spring中,Filter默认继承OncePerRequestFilter类。来过滤请求。
1. OncePerRequestFilter存在的意义
OncePerRequestFilter
是在一次外部请求中只过滤一次。对于服务器内部之间的forward
等请求,不会再次执行过滤方法。
在SpringBoot2.x环境下,服务器内部转发(forward)
一个请求,代码如图2所示:
- 自定义实现
Filter
接口的类,在过滤请求时打印日志。结果如图三所示:
根据上图所示,实际上请求在服务器内部转发时,并未进行过滤。可以看到上,实现Filter
接口,也会在一次请求中只过滤一次。
实际上,
OncePerRequestFilter
是为了兼容不同的web 容器,也就是说其实不是所有的容器都过滤一次。Servlet版本不同,执行的过程也不同。例如:在Servlet2.3中,Filter会过滤一切请求,包括服务器内部使用forward和<%@ include file=/login.jsp%>的情况,但是在servlet2.4中,Filter默认只会过滤外部请求。
对于异步请求(即为避免线程阻塞,需要委托另一个线程处理),也只过滤一次请求。
【小家Spring】——关于OncePerRequestFilter
源码:org.springframework.web.filter.OncePerRequestFilter#doFilter
中,通过更改request中的Filter状态,防止内部请求时多次调用Filter,核心代码如图4所示。
采用的是模板方法模式,子类实现org.springframework.web.filter.OncePerRequestFilter#doFilterInternal
方法。对请求进行过滤。
在Spring环境中若想使用Filter,建议继承
OncePerRequestFilter
,而非原生的Servlet Filter接口。
2. OncePerRequestFilter方法
OncePerRequestFilter
是采用的模板方法模式,子类需要实现父类定义的钩子方法(算法逻辑父类已经实现),便可以进行过滤。
2.1 初始化方法
org.springframework.web.filter.GenericFilterBean#init
中实现init方法,子类若是想执行init方法。需要实现org.springframework.web.filter.GenericFilterBean#initFilterBean
默认的钩子方法,源码如图5所示。
但initFilterBean()
方法,在两个地方使用到,一个是init
方法中,一个是afterPropertiesSet
方法(即Filter若放到Spring容器,初始化时执行该方法)。实际上会执行两次初始化方法,如图6所示:
SpringBoot2.x将Filter加入到容器的几种方法
2.2 过滤方法
以SpringBoot2.x自动装载的编码过滤器为例,如图7所示:
源码:org.springframework.web.filter.CharacterEncodingFilter
源码:org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
总结:在Spring环境中,推荐实现OncePerRequestFilter类,而非实现原生的Filter接口。
前两篇简单的讲了一下spring security的基本用法以及相关扩展,今天跟着我一起学习下spring security的原理吧。spring security是有一系列的过滤器组成的一条链,见下图:
其中绿色的过滤器需要满足一定的条件才会执行,其他三个颜色的过滤器一定会执行,红色的ExceptionTranslationFilter
和黄色的FilterSecurityInterceptor
在过滤器链中的顺序一定是倒数第二和倒数第一的位置。
本篇我们介绍几个主要的过滤器,下面一起进入到代码中,看看这四种颜色的过滤器是怎么发挥其作用的。
原理
SecurityContextPersistenceFilter
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// ensure that filter is only applied once per request
//确保这个过滤器只会执行一次
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
//设置标识
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//默认情况下为false
if (this.forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (this.logger.isDebugEnabled() && session.isNew()) {
this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
//在当前过滤器的构造函数中创建了HttpSessionSecurityContextRepository,所以默认情况下this.repo为HttpSessionSecurityContextRepository实例,调用其loadContext方法,返回SecurityContext对象,一会再看这个方法是怎么将SecurityContext对象返回的
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
try {
//将SecurityContext放入到SecurityContextHolder中
SecurityContextHolder.setContext(contextBeforeChainExecution);
if (contextBeforeChainExecution.getAuthentication() == null) {
logger.debug("Set SecurityContextHolder to empty SecurityContext");
}
else {
if (this.logger.isDebugEnabled()) {
this.logger
.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
}
}
//调用过滤器链中的下一个过滤器
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
//最后当请求结束的时候,从SecurityContextHolder中获取SecurityContext对象
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents before anything else.
//从SecurityContextHolder中清空当前请求绑定的SecurityContext对象
SecurityContextHolder.clearContext();
//调用HttpSessionSecurityContextRepository的saveContext方法
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
//从请求中删除标记
request.removeAttribute(FILTER_APPLIED);
this.logger.debug("Cleared SecurityContextHolder to complete request");
}
}
下面进入到HttpSessionSecurityContextRepository
的loadContext
方法
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
//从session中获取SecurityContext对象
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
context = generateNewContext();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Created %s", context));
}
}
if (response != null) {
SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,
httpSession != null, context);
requestResponseHolder.setResponse(wrappedResponse);
requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));
}
return context;
}
SecurityContextPersistenceFilter:
会在每次请求处理之前从配置好的SecurityContextRepository中获取SecurityContext
安全上下文信息,然后加载到SecurityContextHolder中,然后在该次请求处理完成之后,将SecurityContextHolder
中关于这次请求的信息存储到一个“仓库”中,然后将SecurityContextHolder中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter
继承AbstractAuthenticationProcessingFilter
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
"POST");
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
在构造器中调用父类的构造器传入AntPathRequestMatcher对象
protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}
所以此时父类中requiresAuthenticationRequestMatcher
属性是AntPathRequestMatcher
对象,当请求进来的时候,会调用父类的doFilter
方法
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//是否需要验证,条件是请求路径为/login并且请求方法为post请求,则继续向下执行
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
//调用子类的方法,下面会讲
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
// return immediately as subclass has indicated that it hasn't completed
return;
}
//认证成功后session的处理策略,默认情况下什么也不做
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
//默认情况下为false
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//成功认证后的处理,下面会将
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
//如果认证过程中发生了异常,则调用认证失败后的处理,下面会讲
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
unsuccessfulAuthentication(request, response, ex);
}
}
认证成功后的逻辑处理
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
//创建SecurityContext对象
SecurityContext context = SecurityContextHolder.createEmptyContext();
//将认证成功的Authentication对象保存到SecurityContext安全上下文中
context.setAuthentication(authResult);
//将SecurityContext对象保存到SecurityContextHolder中
SecurityContextHolder.setContext(context);
//将SecurityContext安全上下文保存到仓库中,默认情况下什么也不做
this.securityContextRepository.saveContext(context, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
//调用rememberMeServices的loginSuccess方法,执行rememberMe的相关逻辑,这里就不展开看了,自行跟踪
this.rememberMeServices.loginSuccess(request, response, authResult);
//如果eventPublisher发布器不等于null,则发布一个事件,我们可以监听这个事件,进行自定义处理认证成功后的逻辑
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
//这里就会调用我们自己实现的AuthenticationSuccessHandler接口的方法
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
认证失败后的逻辑处理
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
this.rememberMeServices.loginFail(request, response);
//调用我们自己实现的AuthenticationFailureHandler接口的方法
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
重点看一下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());
}
//从请求中获取参数名username对应的参数值
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
//从请求中获取参数名password对应的参数值
String password = obtainPassword(request);
password = (password != null) ? password : "";
//通过username和password创建一个为经过身份认证的UsernamePasswordAuthenticationToken对象
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
//允许子类设置一些客户端的详细信息,例如ip、port等
setDetails(request, authRequest);
//调用AuthenticationManager对象的authenticate方法进行身份认证
return this.getAuthenticationManager().authenticate(authRequest);
}
AuthenticationManager
是一个接口,默认实现是ProviderManager
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();
//循环所有的AuthenticationProvider实现类
for (AuthenticationProvider provider : getProviders()) {
//如果支持当前Authentication类,则继续向下执行
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
//调用provider的authenticate方法,做认证,如果认证失败会被捕获并继续向外抛异常
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
if (result == null && this.parent != null) {
// Allow the parent to try.
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful then it
// will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent
// AuthenticationManager already published it
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed then it will
// publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
// parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
如果当前认证方式是用户名密码,那么AuthenticationProvider
的实现类AbstractUserDetailsAuthenticationProvider
支持当前Authentication类的子类
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
则会进入AbstractUserDetailsAuthenticationProvider
类中的authenticate
方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
//从Authentication中获取用户名
String username = determineUsername(authentication);
boolean cacheWasUsed = true;
//从缓存中获取,第一次,没有
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//retrieveUser是一个抽象方法,其子类DaoAuthenticationProvider实现了这个方法
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException ex) {
this.logger.debug("Failed to find user '" + username + "'");
if (!this.hideUserNotFoundExceptions) {
throw ex;
}
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
//如果获取UserDetails信息成功,则会进行一系列的检查,比如账号是否锁定、账号是否过期、密码是否过期登
this.preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException ex) {
if (!cacheWasUsed) {
throw ex;
}
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
this.preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//返回经过身份认证的Authentication对象
return createSuccessAuthentication(principalToReturn, authentication, user);
}
进入DaoAuthenticationProvider
的retrieveUser
方法
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//调用UserDetailsService接口的实现类,根据用户名获取UserDetails信息
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
UsernamePasswordAuthenticationFilter:
用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自/login的表单action。从表单中获取用户名和密码时,默认使用的表单name属性值为username和password,这俩个值也可以通过usernameParameter和passwordParameter在配置中自定义。
ExceptionTranslationFilter
此过滤器处于过滤器链倒数第二的位置
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
//放过直接调用下一个过滤器
chain.doFilter(request, response);
}
catch (IOException ex) {
//如果是IOException,直接向上抛出
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
//尝试从堆栈信息中提取Spring Security Exception
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
//先查找AuthenticationException类型的异常
RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
//如果没有查找到,接着查找AccessDeniedException类型的异常
if (securityException == null) {
securityException = (AccessDeniedException) this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
//如果都没有,则直接向上抛出
if (securityException == null) {
rethrow(ex);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception "
+ "because the response is already committed.", ex);
}
//处理spring security 相关异常
handleSpringSecurityException(request, response, chain, securityException);
}
}
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, RuntimeException exception) throws IOException, ServletException {
//如果异常类型是AuthenticationException
if (exception instanceof AuthenticationException) {
//处理认证异常
handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
//处理访问拒绝异常
handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
}
}
如果是AuthenticationException类型的异常
private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, AuthenticationException exception) throws ServletException, IOException {
this.logger.trace("Sending to authentication entry point since authentication failed", exception);
sendStartAuthentication(request, response, chain, exception);
}
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContext context = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(context);
//将当前请求保存到RequestCache中
this.requestCache.saveRequest(request, response);
//调用AuthenticationEntryPoint接口实现类的commence方法
this.authenticationEntryPoint.commence(request, response, reason);
}
看一下LoginUrlAuthenticationEntryPoint
的实现逻辑
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
//useForward默认false
if (!this.useForward) {
// redirect to login page. Use https if forceHttps true
//构建重定向url到登录页
String redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
//重定向到登录页
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
return;
}
String redirectUrl = null;
if (this.forceHttps && "http".equals(request.getScheme())) {
// First redirect the current request to HTTPS. When that request is received,
// the forward to the login page will be used.
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl != null) {
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
return;
}
String loginForm = determineUrlToUseForThisRequest(request, response, authException);
logger.debug(LogMessage.format("Server side forward to: %s", loginForm));
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
如果是AccessDeniedException类型的异常
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 (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
authentication), exception);
}
sendStartAuthentication(request, response, chain,
new InsufficientAuthenticationException(
this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
if (logger.isTraceEnabled()) {
logger.trace(
LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
exception);
}
//否则调用AccessDeniedHandler接口实现类的handle方法
this.accessDeniedHandler.handle(request, response, exception);
}
}
看一下AccessDeniedHandlerImpl
的处理逻辑
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
if (response.isCommitted()) {
logger.trace("Did not write to response since already committed");
return;
}
if (this.errorPage == null) {
logger.debug("Responding with 403 status code");
response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
return;
}
// Put exception into request scope (perhaps of use to a view)
request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
// Set the 403 status code.
response.setStatus(HttpStatus.FORBIDDEN.value());
// forward to error page.
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.format("Forwarding to %s with status code 403", this.errorPage));
}
request.getRequestDispatcher(this.errorPage).forward(request, response);
}
设置403状态码并转发到错误页面
ExceptionTranslationFilter:
捕获来自过滤器链的所有异常,并进行处理。但是只处理两类异常:AccessDeniedException和Authenti cationException 异常,其他的异常会继续抛出。
如果捕获到的AuthenticationException,那么将会使用其对应的AuthenticationEntryPoint的commence()方法处理。在处理之前,ExceptionTranslationFilter先使用RequestCache将当前的HTTPServletRequest的信息保存起来,方便用户登录成功后可以跳转到之前的页面。
可以自定义AuthenticationException的处理方法。需要实现AuthenticationEntryPoint接口,然后重写commence()方法。
如果捕获的AuthenticationDeniedException,那么将会根据当前访问的用户是否已经登录认证做不同的处理,如果未登录,则会使用关联的AuthenticationEntryPoint的commence()方法进行处理,否则将使用关联的AccessDeniedHandler的handle()方法进行处理。
可以进行自定义AuthenticationDeniedException的处理方法。需要实现AccessDeniedHandler接口,然后重写handle()方法。
FilterSecurityInterceptor
守门员
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
invoke(new FilterInvocation(request, response, chain));
}
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// first time this request being called, so perform security checking
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//在调用下游的服务之前进行最后的检查
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
//过滤器链的最后一个,调用下游服务
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
FilterSecurityInterceptor:
做为过滤器链中的最后一个过滤器,承担着最后的身份验证和鉴权,如果验证通过,就会调用到下游的服务。关于这个过滤器的执行逻辑,鉴于篇幅的原因,本篇暂不展开分析,后面会单独写一篇对这个过滤器的介绍。
总结
今天介绍了四类过滤器,其中绿色的过滤器,我们是可以根据需要进行扩展的,比如第二篇中,实现的手机短信认证方式。关于spring security过滤器链中的过滤器,远不止这些,这里只是介绍了几个关键的过滤器,其他更多过滤器请自行阅读,谢谢大家的阅读。