赞
踩
对于这15个过滤器,针对他们的功能,可以做一个简单的划分
• 0-4 这几个过滤器是 功能性的前置过滤器,提供了SpringSecurity的基础必要能力。
• 5-14 则与认证和授权过程相关
从字面上可以看出,这个过滤器是用来禁用URL重新编码的;
现在看他的具体实现
public class DisableEncodeUrlFilter extends OncePerRequestFilter { public DisableEncodeUrlFilter() { } protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 对response进行包装后直接放行 filterChain.doFilter(request, new DisableEncodeUrlResponseWrapper(response)); } private static final class DisableEncodeUrlResponseWrapper extends HttpServletResponseWrapper { private DisableEncodeUrlResponseWrapper(HttpServletResponse response) { super(response); } public String encodeRedirectUrl(String url) { // 不重新url 直接返回 return url; } public String encodeRedirectURL(String url) { // 不重新url 直接返回 return url; } public String encodeUrl(String url) { // 不重新url 直接返回 return url; } public String encodeURL(String url) { // 不重新url 直接返回 return url; } } }
为什么要这样处理?
Session的会话持有在客户端是通过cookies来保存SessionId来实现的,每次客户端的请求都携带sessionId.
如果禁用了cookie,后端的默认响应会重写url将sessionId拼接到url后面,传递给页面,sessionId就在http访问日志中暴露了。
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/session/DisableEncodeUrlFilter.html
从字面上理解的话,这个过滤器就是 Web异步处理整合过滤器。
看他的具体实现
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter { private static final Object CALLABLE_INTERCEPTOR_KEY = new Object(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 尝试获取一个 Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE); // asyncManagerAttr 如果已存在,返回已存在的对象, // asyncManagerAttr 为空,并将新对方放到servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); // 尝试从asyncManager 中获取已经存储的拦截器 SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager .getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY); // 如果没有获取到 if (securityProcessingInterceptor == null) { // 新建一个拦截器 将SecurityContextHolderStrategy存储到拦截器,供子线程可获取;注册拦截器放到asyncManager中 asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, new SecurityContextCallableProcessingInterceptor()); } filterChain.doFilter(request, response); } }
默认情况下securityContextHolderStrategy的存储策略为ThreadLocal,在ThreadLocal的存储策略下,只有当前线程可以获取到securityContextHolder。
WebAsyncManagerIntegrationFilter 通过创建拦截器的形式,将securityContextHolderStrategy传递给子线程,后续子线程可以通过该拦截器获取到用户认证信息。
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/context/request/async/WebAsyncManagerIntegrationFilter.html
从字面上理解就是 持有SecurityContext的过滤器。
该Filter是用于存储用户认证信息的,他有两个重要的属性SecurityContextRepository和securityContextHolderStrategy。
public class SecurityContextHolderFilter extends OncePerRequestFilter { // SecurityContextRepository接口 提供一种在整个请求上下文存储SecurityContext的能力 // SecurityContextRepository接口有两个重要方法: loadContext - 获取SecurityContext loadDeferredContext-延期获取SecurityContext saveContext - 保存SecurityContext // 该属性默认为 DelegatingSecurityContextRepository,DelegatingSecurityContextRepository也是实现SecurityContextRepository接口的一个代理类 // DelegatingSecurityContextRepository允许代理多个SecurityContextRepository来实现SecurityContext的存储 // DelegatingSecurityContextRepository对 loadContext 和 saveContext 实现 // 默认被代理的SecurityContextRepository为:HttpSessionSecurityContextRepository 和 RequestAttributeSecurityContextRepository // 当调用 DelegatingSecurityContextRepository 时,他会遍历被代理的SecurityContextRepository // saveContext时:遍历被代理的SecurityContextRepository 都调用saveContext SecurityContext进行存储 // loadContext时: 调用自身loadDeferredContext 获取SecurityContext // loadDeferredContext时:遍历被代理的SecurityContextRepository 调用loadDeferredContext 获取 SecurityContext private final SecurityContextRepository securityContextRepository; private boolean shouldNotFilterErrorDispatch; /** * Creates a new instance. * @param securityContextRepository the repository to use. Cannot be null. */ public SecurityContextHolderFilter(SecurityContextRepository securityContextRepository) { Assert.notNull(securityContextRepository, "securityContextRepository cannot be null"); this.securityContextRepository = securityContextRepository; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { SecurityContext securityContext = this.securityContextRepository.loadContext(request).get(); try { SecurityContextHolder.setContext(securityContext); filterChain.doFilter(request, response); } finally { SecurityContextHolder.clearContext(); } } @Override protected boolean shouldNotFilterErrorDispatch() { return this.shouldNotFilterErrorDispatch; } /** * Disables {@link SecurityContextHolderFilter} for error dispatch. * @param shouldNotFilterErrorDispatch if the Filter should be disabled for error * dispatch. Default is false. */ public void setShouldNotFilterErrorDispatch(boolean shouldNotFilterErrorDispatch) { this.shouldNotFilterErrorDispatch = shouldNotFilterErrorDispatch; } }
SecurityContextHolderFilter是第三个要执行的Filter,但是他是直接继承于GenericFilterBean。
看他的doFilter逻辑
public class SecurityContextHolderFilter extends GenericFilterBean { @Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); } private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 如果已经执行过 该过滤器 if (request.getAttribute(FILTER_APPLIED) != null) { // 跳过 直接执行下一个过滤器 chain.doFilter(request, response); return; } // 标记改过滤器在本次请求过程中已经执行过 request.setAttribute(FILTER_APPLIED, Boolean.TRUE);// 从SecurityContextRepository获取到SecurityContext Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request); try { // 将SecurityContext存储到securityContextHolderStrategy中,也就是存储到线程中。 this.securityContextHolderStrategy.setDeferredContext(deferredContext);// 继续执行Filter chain.doFilter(request, response); } finally { // 这时应该 后续所有的Filter都已执行完后,有回到当前Filter中 // 请求执行完成后,清除SecurityContext this.securityContextHolderStrategy.clearContext();// 移除已执行标记 request.removeAttribute(FILTER_APPLIED); } } }
从源码中可以看出,该过滤器最主要的作用是
如果请求上下文中存在 SecurityContext 则 SecurityContext存储到securityContextHolderStrategy默认是ThreadLocal
并在整个过滤器链执行完成后清除SecurityContext
存储到securityContextHolderStrategy可以保证后续的过滤器都可以从securityContextHolderStrategy中获取到SecurityContext。
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/context/SecurityContextHolderFilter.html
从字面上理解,头信息写入过滤器
看他的具体实现
public class HeaderWriterFilter extends OncePerRequestFilter {// 要写人的头信息 private final List<HeaderWriter> headerWriters;// 默认是false 也就是在过滤器都执行完成后,回到该过滤器时向response中写入 private boolean shouldWriteHeadersEagerly = false;// 构造方法,需要在构造该过滤器时就传入要写入ResponseHeader的头信息 // 具体确定要写入那些头信息是由HeadersConfigurer来决定的 // 默认是以下几个头信息 // 0 = {XContentTypeOptionsHeaderWriter} X-Content-Type-Options: nosniff // 1 = {XXssProtectionHeaderWriter} X-XSS-Protection: 0 // 2 = {CacheControlHeadersWriter} // Header [name: Cache-Control, values: [no-cache, no-store, max-age=0, must-revalidate]] // Header [name: Pragma, values: [no-cache]] // Header [name: Expires, values: [0]] // 3 = {HstsHeaderWriter} Strict-Transport-Security: max-age=31536000 ; includeSubDomains // 4 = {XFrameOptionsHeaderWriter} X-Frame-Options: DENY public HeaderWriterFilter(List<HeaderWriter> headerWriters) { Assert.notEmpty(headerWriters, "headerWriters cannot be null or empty"); this.headerWriters = headerWriters; } // 具体的过滤器执行方法 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (this.shouldWriteHeadersEagerly) { // 如果是提前写入响应头,则是直接调用了writeHeaders 方法,并继续执行过滤器 doHeadersBefore(request, response, filterChain); } else { // 默认走该方法 // 再过滤器执行完成后,再写入头信息 doHeadersAfter(request, response, filterChain); } } private void doHeadersBefore(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {// 写入头信息 writeHeaders(request, response);// 执行过滤器 filterChain.doFilter(request, response); } private void doHeadersAfter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {// 对Req 和 Resp进行一个包装 HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request, response); HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request, headerWriterResponse); try { // 先执行后续过滤器 filterChain.doFilter(headerWriterRequest, headerWriterResponse); } finally { // 执行完后续过滤器,回到该过滤器后,写入响应头信息 headerWriterResponse.writeHeaders(); } } void writeHeaders(HttpServletRequest request, HttpServletResponse response) { for (HeaderWriter writer : this.headerWriters) { // 将头信息写入HttpServletResponse writer.writeHeaders(request, response); } } }
该过滤器的类描述信息:
为当前响应添加报头的过滤器实现。可以添加某些头,启用浏览器保护。像X-Frame-Options, X-XSS-Protection和X-Content-Type-Options。
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/header/HeaderWriterFilter.html
该过滤器用于保护应用不受Csrf攻击,是比较重要的一个过滤器。
CsrfFilter是第五个要执行的过滤器,他的主要作用是:
使用同步令牌模式应用CSRF保护。开发人员需要确保任何允许状态改变的请求都会调用CsrfFilter。 通常这只是意味着他们应该确保他们的web应用程序遵循适当的REST语义(即不使用HTTP方法GET、HEAD、TRACE、OPTIONS改变状态)。
通常,CsrfTokenRepository实现选择将CsrfToken存储在HttpSession中,HttpSessionCsrfTokenRepository由LazyCsrfTokenRepository包装。 这比将令牌存储在可由客户端应用程序修改的cookie中更可取。
这个过滤器的主要作用是防范跨站请求伪造(英语:Cross-site request forgery)攻击。
CSRF攻击的原理是,当用户已经在某个网站认证完成后浏览器已存储用户的有效信息,攻击者诱导用户登录一个其他的网站,利用用户在原网站未过期的认证信息发起一个非用户本意的请求。
CSRF利用了用户对本地浏览器的信任。
CsrfFilter处理逻辑
public final class CsrfFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 尝试从存储组件中加载 CsrfToken DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response);// 在Request中设置CsrfToken // request.setAttribute(CsrfToken.class.getName(), csrfToken); this.requestHandler.handle(request, response, deferredCsrfToken::get);// 判断请求是否需要防止Csrf // 默认忽略了("GET", "HEAD", "TRACE", "OPTIONS") 这种不会改变数据 的请求方式 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; } // 获取CsrfToken ,有就返回 没有初始化一个token CsrfToken csrfToken = deferredCsrfToken.get();// 获取请求中携带的token String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken);// 对比 token if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {// 是否是未携带token boolean missingToken = deferredCsrfToken.isGenerated(); 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); } }
代码中有this.requireCsrfProtectionMatcher.matches()这个方法,来判断当前请求是否要进行CSRFToken的验证。
这里的匹配规则是 排除 (“GET”, “HEAD”, “TRACE”, “OPTIONS”) 这几种,然后
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfFilter.html
从字面上理解,这个过滤器的是处理用户登出请求的。他的逻辑代码比较简单。
public class LogoutFilter extends GenericFilterBean {
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {// 判断请求是否是注销登录 默认匹配/logout请求路径
if (requiresLogout(request, response)) {// 获取认证信息
Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication();
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Logging out [%s]", auth));
}// 调用登出方法
this.handler.logout(request, response, auth);// 跳转到登出成功后指定的页面
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
}
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/logout/LogoutFilter.html
从名称上看,这个过滤器时 用户名密码认证的过滤器。
他是AbstractAuthenticationProcessingFilter的子类,AbstractAuthenticationProcessingFilter的作用是,用于提供针对某种类型AbstractAuthenticationToken的用户认证的具体实现。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {// 判断请求是否是需要请求用户认证的 默认是 POST /login if (!requiresAuthentication(request, response)) {// 如果不是请求认证的请求,则继续执行后面的filter // 本次案例中执行 /test/hello 不是login 所以会继续往后执行 chain.doFilter(request, response); return; }// 如果是请求用户认证的请求 try {// 调用子类的attemptAuthentication实现,尝试去认证,这里调用的是UsernamePasswordAuthenticationFilter.attemptAuthentication方法 // attemptAuthentication有可能会抛出认证相关的异常 AuthenticationException Authentication authenticationResult = attemptAuthentication(request, response);// 如果此时返回的结果是null 表示认证尚未完成 if (authenticationResult == null) { // 暂时停止后续的过滤器 // return immediately as subclass has indicated that it hasn't completed return; }// 如果返回认证后信息 this.sessionStrategy.onAuthentication(authenticationResult, request, response);// 认证成功后是否继续执行过滤器链 默认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); } } }
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.html
这个过滤器 提供在默认配置下生成一个登录页面的能力
其具体的实现如下
public class DefaultLoginPageGeneratingFilter extends GenericFilterBean { @Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); } private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {// 判断是不是登录失败跳转页面 boolean loginError = isErrorPage(request);// 判断是不是退出登录成功跳转页面 boolean logoutSuccess = isLogoutSuccess(request);// 判断是否需要需要生成登录页面 if (isLoginUrlRequest(request) || loginError || logoutSuccess) {// 生产页面HTML代码 String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);// 返回到浏览器 response.setContentType("text/html;charset=UTF-8"); response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length); response.getWriter().write(loginPageHtml); return; }// 继续执行过滤器 chain.doFilter(request, response); }// 生成页面的HTML代码 private String generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess) { String errorMsg = "Invalid credentials"; if (loginError) { HttpSession session = request.getSession(false); if (session != null) { AuthenticationException ex = (AuthenticationException) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); errorMsg = (ex != null) ? ex.getMessage() : "Invalid credentials"; } } String contextPath = request.getContextPath(); StringBuilder sb = new StringBuilder(); sb.append("<!DOCTYPE html>\n"); sb.append("<html lang=\"en\">\n"); sb.append(" <head>\n"); sb.append(" <meta charset=\"utf-8\">\n"); sb.append(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"); sb.append(" <meta name=\"description\" content=\"\">\n"); sb.append(" <meta name=\"author\" content=\"\">\n"); sb.append(" <title>Please sign in</title>\n"); sb.append(" <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" " + "rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"); sb.append(" <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" " + "rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"); sb.append(" </head>\n"); sb.append(" <body>\n"); sb.append(" <div class=\"container\">\n"); if (this.formLoginEnabled) { sb.append(" <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"); sb.append(" <h2 class=\"form-signin-heading\">Please sign in</h2>\n"); sb.append(createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + " <p>\n"); sb.append(" <label for=\"username\" class=\"sr-only\">Username</label>\n"); sb.append(" <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"); sb.append(" </p>\n"); sb.append(" <p>\n"); sb.append(" <label for=\"password\" class=\"sr-only\">Password</label>\n"); sb.append(" <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"); sb.append(" </p>\n"); sb.append(createRememberMe(this.rememberMeParameter) + renderHiddenInputs(request)); sb.append(" <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"); sb.append(" </form>\n"); } if (this.oauth2LoginEnabled) { sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>"); sb.append(createError(loginError, errorMsg)); sb.append(createLogoutSuccess(logoutSuccess)); sb.append("<table class=\"table table-striped\">\n"); for (Map.Entry<String, String> clientAuthenticationUrlToClientName : this.oauth2AuthenticationUrlToClientName.entrySet()) { sb.append(" <tr><td>"); String url = clientAuthenticationUrlToClientName.getKey(); sb.append("<a href=\"").append(contextPath).append(url).append("\">"); String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue()); sb.append(clientName); sb.append("</a>"); sb.append("</td></tr>\n"); } sb.append("</table>\n"); } if (this.saml2LoginEnabled) { sb.append("<h2 class=\"form-signin-heading\">Login with SAML 2.0</h2>"); sb.append(createError(loginError, errorMsg)); sb.append(createLogoutSuccess(logoutSuccess)); sb.append("<table class=\"table table-striped\">\n"); for (Map.Entry<String, String> relyingPartyUrlToName : this.saml2AuthenticationUrlToProviderName.entrySet()) { sb.append(" <tr><td>"); String url = relyingPartyUrlToName.getKey(); sb.append("<a href=\"").append(contextPath).append(url).append("\">"); String partyName = HtmlUtils.htmlEscape(relyingPartyUrlToName.getValue()); sb.append(partyName); sb.append("</a>"); sb.append("</td></tr>\n"); } sb.append("</table>\n"); } sb.append("</div>\n"); sb.append("</body></html>"); return sb.toString(); } }
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.html
与DefaultLoginPageGeneratingFilter类似,这个过滤器 提供在默认配置下生成一个登出页面的能力
public class DefaultLogoutPageGeneratingFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 判断是否是 /logout 页面 if (this.matcher.matches(request)) {// 生成页面返回到浏览器 renderLogout(request, response); }// 不是 /logout 继续执行后续的过滤器 else { if (logger.isTraceEnabled()) { logger.trace(LogMessage.format("Did not render default logout page since request did not match [%s]", this.matcher)); } filterChain.doFilter(request, response); } } private void renderLogout(HttpServletRequest request, HttpServletResponse response) throws IOException { StringBuilder sb = new StringBuilder(); sb.append("<!DOCTYPE html>\n"); sb.append("<html lang=\"en\">\n"); sb.append(" <head>\n"); sb.append(" <meta charset=\"utf-8\">\n"); sb.append(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"); sb.append(" <meta name=\"description\" content=\"\">\n"); sb.append(" <meta name=\"author\" content=\"\">\n"); sb.append(" <title>Confirm Log Out?</title>\n"); sb.append(" <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" " + "rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" " + "crossorigin=\"anonymous\">\n"); sb.append(" <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" " + "rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"); sb.append(" </head>\n"); sb.append(" <body>\n"); sb.append(" <div class=\"container\">\n"); sb.append(" <form class=\"form-signin\" method=\"post\" action=\"" + request.getContextPath() + "/logout\">\n"); sb.append(" <h2 class=\"form-signin-heading\">Are you sure you want to log out?</h2>\n"); sb.append(renderHiddenInputs(request) + " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Log Out</button>\n"); sb.append(" </form>\n"); sb.append(" </div>\n"); sb.append(" </body>\n"); sb.append("</html>"); response.setContentType("text/html;charset=UTF-8"); response.getWriter().write(sb.toString()); } }
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/ui/DefaultLogoutPageGeneratingFilter.html
这个过滤器时用来做BASIC认证的。
与表单认证的作用一样,都是一种用户认证的方式。
这种认证方式用的很少,大致过一下他的核心代码既可以了。
public class BasicAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { try {// 从Request中解析到 凭证信息 UsernamePasswordAuthenticationToken authRequest = this.authenticationConverter.convert(request);// 未获取到 凭证信息 if (authRequest == null) { this.logger.trace("Did not process authentication request since failed to find " + "username and password in Basic Authorization header"); chain.doFilter(request, response); return; }// 获取到用户名 String username = authRequest.getName(); this.logger.trace(LogMessage.format("Found username '%s' in Basic Authorization header", username));// 从SecurityContextHolder中获取认证信息,判断用户名是否需要认证 如果已经认证过 就不再认证 if (authenticationIsRequired(username)) {// 调用认证接口 Authentication authResult = this.authenticationManager.authenticate(authRequest);// 创建一个空的SecurityContext SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();// 将认证结果存放到SecurityContext context.setAuthentication(authResult);// securityContextHolder存储SecurityContext this.securityContextHolderStrategy.setContext(context); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult)); }// 调用rememberMeServices 存储用户登录成功方法 this.rememberMeServices.loginSuccess(request, response, authResult);// 在本次请求上下文中保存认证结果 this.securityContextRepository.saveContext(context, request, response);// 调用认证成功后的处理方法 onSuccessfulAuthentication(request, response, authResult); } }// 异常处理 catch (AuthenticationException ex) { this.securityContextHolderStrategy.clearContext(); this.logger.debug("Failed to process authentication request", ex); this.rememberMeServices.loginFail(request, response); onUnsuccessfulAuthentication(request, response, ex); if (this.ignoreFailure) { chain.doFilter(request, response); } else { this.authenticationEntryPoint.commence(request, response, ex); } return; }// 继续执行过滤器 chain.doFilter(request, response); } }
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.html
这个过滤器是一个比较重要的,当客户端访问资源时,RequestCacheAwareFilter尝试冲缓存中查找已经保存的Request,默认是存储到Session的Attitude种的,默认的key为SPRING_SECURITY_SAVED_REQUEST。
public class RequestCacheAwareFilter extends GenericFilterBean {// 默认是:HttpSessionRequestCache 也就是将请求缓存在session
private RequestCache requestCache;
@Overridepublic
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 尝试从缓存中获取已缓存的请求
HttpServletRequest wrappedSavedRequest = this.requestCache.getMatchingRequest((HttpServletRequest) request, (HttpServletResponse) response);// 如果有缓存的请求,则用缓存的请求继续执行,否则用本次请求继续执行。
chain.doFilter((wrappedSavedRequest != null) ? wrappedSavedRequest : request, response);
}
}
从代码中可以看出,如果从缓存中找到了已经存储的请求,则继续原请求,如果没找到,则继续当前请求。
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/savedrequest/RequestCacheAwareFilter.html
该过滤器的主要作用是将ServletRequest包装成SecurityContextHolderAwareRequestWrapper
SecurityContextHolderAwareRequestWrapper 包装了SpringContext定义的 Authentication 对象。
SecurityContextHolderAwareRequestWrapper包含securityContextHolderStrategy属性,所以从SecurityContextHolderAwareRequestWrapper中可以直接获取到SecurityContext。
public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {// 默认是 HttpServlet3RequestFactory
private HttpServletRequestFactory requestFactory;
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {// 将请求包装成 SecurityContextHolderAwareRequestWrapper 这里默认是 Servlet3SecurityContextHolderAwareRequestWrapper
chain.doFilter(this.requestFactory.create((HttpServletRequest) req, (HttpServletResponse) res), res);
}
}
final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
public HttpServletRequest create(HttpServletRequest request, HttpServletResponse response) {
Servlet3SecurityContextHolderAwareRequestWrapper wrapper = new Servlet3SecurityContextHolderAwareRequestWrapper(request, this.rolePrefix, response);
wrapper.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return wrapper;
}
}
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.html
该过滤器的作用:如果在经过该过滤器时,依然没有获取到用户的认证信息,则创建一个匿名用户。
大致的执行过程见下方源码。
public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {// 从SecurityContextHolderStrategy中获取SecurityContext Supplier<SecurityContext> deferredContext = this.securityContextHolderStrategy.getDeferredContext();// 再给SecurityContextHolderStrategy从新设置一次SecurityContext // 该方法根据请求和 从SecurityContextHolderStrategy中获取的SecurityContext 再次生产一个Supplier<SecurityContext> this.securityContextHolderStrategy.setDeferredContext(defaultWithAnonymous((HttpServletRequest) req, deferredContext));// 继续执行 chain.doFilter(req, res); } private Supplier<SecurityContext> defaultWithAnonymous(HttpServletRequest request, Supplier<SecurityContext> currentDeferredContext) { return SingletonSupplier.of(() -> {// 获取SecurityContext SecurityContext currentContext = currentDeferredContext.get();// 创建一个默认的Anonymous SecurityContext return defaultWithAnonymous(request, currentContext); }); } private SecurityContext defaultWithAnonymous(HttpServletRequest request, SecurityContext currentContext) {// 从currentContext中获取 Authentication Authentication currentAuthentication = currentContext.getAuthentication();// 如果凭证信息为空 if (currentAuthentication == null) {// 创建一个匿名的Authentication信息 Authentication anonymous = createAuthentication(request); if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.of(() -> "Set SecurityContextHolder to " + anonymous)); } else { this.logger.debug("Set SecurityContextHolder to anonymous SecurityContext"); }// 将anonymous Authentication 设置到SecurityContext 中 SecurityContext anonymousContext = this.securityContextHolderStrategy.createEmptyContext(); anonymousContext.setAuthentication(anonymous);// 返回 匿名的 SecurityContext return anonymousContext; } else { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.of(() -> "Did not set SecurityContextHolder since already authenticated " + currentAuthentication)); } }// 如果不为空 直接返回原SecurityContext return currentContext; } }
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/AnonymousAuthenticationFilter.html
ExceptionTranslationFilter是第14个过滤器,主要作用是用来处理 过滤器链中抛出来的权限校验异常。
如果检测到AuthenticationException这种认证异常,过滤器将启动authenticationEntryPoint 跳转到登录页面去认证。
如果检测到AccessDeniedException,过滤器将确定用户是否是匿名用户。如果是匿名用户,authenticationEntryPoint将启动,跳转到登录页面去,如果他们不是匿名用户,过滤器将委托给AccessDeniedHandler。默认情况下,过滤器将使用AccessDeniedHandlerImpl。
public class ExceptionTranslationFilter extends GenericFilterBean implements MessageSourceAware { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); } private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { try {// 执行doFilter 继续执行后续过滤器 如果后续的过滤器抛出异常,也会被catch到 chain.doFilter(request, response); } catch (IOException ex) {// 不处理IO异常 throw ex; } catch (Exception ex) {// Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);// 先尝试获取AuthenticationException异常 RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (securityException == null) {// 在尝试获取AccessDeniedException异常 securityException = (AccessDeniedException) this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); }// 如果不是 AuthenticationException 和 AccessDeniedException异常 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); }// 处理异常 handleSpringSecurityException(request, response, chain, securityException); } }// 抛出异常 private void rethrow(Exception ex) throws ServletException {// Rethrow ServletExceptions and RuntimeExceptions as-is if (ex instanceof ServletException) { throw (ServletException) ex; } if (ex instanceof RuntimeException) { throw (RuntimeException) ex; }// Wrap other Exceptions. This shouldn't actually happen // as we've already covered all the possibilities for doFilter throw new RuntimeException(ex); }// 处理 AccessDeniedException和AuthenticationException。 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); }// 如果是AccessDeniedException异常 else if (exception instanceof AccessDeniedException) { handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception); } } }
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/access/ExceptionTranslationFilter.html
他的主要作用是判断当前的用户用没有权限可以访问目标资源。
如果用户没有权限访问当前资源 会抛出 AccessDeniedException。异常会被ExceptionTranslationFilter处理。
public class AuthorizationFilter extends GenericFilterBean { private final AuthorizationManager<HttpServletRequest> authorizationManager; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {// 获取到请求和响应 HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse;// 判断是否 监控每一次请求 并且 当前请求已经执行过该过滤器 则跳过认证 if (this.observeOncePerRequest && isApplied(request)) { chain.doFilter(request, response); return; }// 当请求的转发方式 为ERROR 和 ASYNC时 跳过认证 // DispatcherType.ERROR 当容器将处理过程传递给错误处理机制(如定义的错误页)时。 // DispatcherType.ASYNC 在以下几种情况下 AsyncContext.dispatch(), AsyncContext.dispatch(String) and AsyncContext.addListener(AsyncListener, ServletRequest, ServletResponse) if (skipDispatch(request)) { chain.doFilter(request, response); return; }// 获取记录已经执行过该过滤器的标记setAttribute KEY String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();// 设置为TRUE 表示已经执行了该过滤器 request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try {// 调用authorizationManager 判断当前用户是否有权限访问该资源 // this::getAuthentication -> 获取用户的认证信息 // request: 当前请求 // AuthorizationDecision: 授权描述 decision.isGranted() 表示是否有该资源的权限 AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);// 发布一个认证的消息 this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);// 如果未授权,抛出异常AccessDeniedException if (decision != null && !decision.isGranted()) { throw new AccessDeniedException("Access Denied"); }// 已授权 继续向后执行 chain.doFilter(request, response); } finally {// 请求执行完成后 移除掉 已执行标记 request.removeAttribute(alreadyFilteredAttributeName); } } private boolean skipDispatch(HttpServletRequest request) { if (DispatcherType.ERROR.equals(request.getDispatcherType()) && !this.filterErrorDispatch) { return true; } if (DispatcherType.ASYNC.equals(request.getDispatcherType()) && !this.filterAsyncDispatch) { return true; } return false; } private String getAlreadyFilteredAttributeName() { String name = getFilterName(); if (name == null) { name = getClass().getName(); } return name + ".APPLIED"; } }
官方文档地址:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/access/intercept/AuthorizationFilter.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。