@[TOC]
前言
分析Spring Security的核心原理,可以从以下几个方面进行:
- 系统启动的时候Spring Security做了哪些事情?
- 发起一次请求后Spring Security做了哪些事情?
系统启动
当我们的Web服务启动的时候,SpringSecurity做了哪些事情?当系统启动的时候,首先会加载配置的web.xml文件
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app version="2.5" id="WebApp_ID" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>Archetype Created Web Application</display-name>
<!-- 初始化spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- post乱码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 前端控制器 -->
<servlet>
<servlet-name>dispatcherServletb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServletb</servlet-name>
<!-- 拦截所有请求jsp除外 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置过滤器链 springSecurityFilterChain 名称固定 -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
web.xml中与SpringSecurity相关的配置信息:
- Spring的初始化(会加载解析SpringSecurity的配置文件)
- 加载DelegatingFilterProxy过滤器
Spring的初始化操作会加载SpringSecurity的配置文件,将相关的数据添加到Spring容器中
DelegatingFilterProxy过滤器:这个过滤器本身是和SpringSecurity没有关系的,会拦截所有的请求,包括SpringSecurity相关的过滤器。
Spring Security 对请求的处理
客户发送一个请求会经过很多个Web Filter拦截,web.xml中 定义的 DelegatingFilterProxy 过滤器会拦截客户端的所有的请求。
当用户请求进来的时候会被doFilter方法拦截
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
// 如果 delegateToUse 为空 那么完成init中的初始化操作
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
invokeDelegate
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// delegate.doFilter() FilterChainProxy
delegate.doFilter(request, response, filterChain);
}
在此处可以发现DelegatingFilterProxy最终是调用的委托代理对象的doFilter方法
FilterChainProxy
过滤器链的代理对象:增强过滤器链(具体处理请求的过滤器还不是FilterChainProxy ) 根据客户端的请求匹配合适的过滤器链链来处理请求,如下
// 处理用户请求
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);
this.doFilterInternal(request, response, chain);
} finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
} else {
this.doFilterInternal(request, response, chain);
}
}
doFilterInternal 获取对应处理请求的过滤器链,然后一一处理。
CSRFFilter:是Spring Security中用于防止跨站请求伪造(CSRF)攻击的过滤器。CSRFFilter通过在请求到达目标资源之前,验证请求的来源和身份认证信息是否匹配来防止CSRF攻击。它会在处理请求之前检查请求是否符合以下条件:
- 请求是否来自已认证的用户。
- 请求是否与目标资源的身份认证信息匹配。
如果以上两个条件都满足,则请求被允许继续处理,否则将被拒绝或者重定向到登录页面。
UsernamePasswordAuthenticationFilter:是Spring Security用来验证登陆用户密码的过滤器,在[认证流程]中有详细说明。
DefaultLoginPageGeneratingFilter:是Spring Security中负责生成登录页面的过滤器。当开发人员在安全配置中没有配置登录页面时,Spring Security Web会自动构造一个登录页面给用户。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
boolean loginError = this.isErrorPage(request);
boolean logoutSuccess = this.isLogoutSuccess(request);
if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
// 正常的业务请求就直接放过
chain.doFilter(request, response);
} else {
// 需要跳转到登录页面的请求
String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
// 直接响应登录页面
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
response.getWriter().write(loginPageHtml);
}
}
generateLoginPageHtml
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("SPRING_SECURITY_LAST_EXCEPTION");
errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
}
}
StringBuilder sb = new StringBuilder();
sb.append("<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n <meta name=\"description\" content=\"\">\n <meta name=\"author\" content=\"\">\n <title>Please sign in</title>\n <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 <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n </head>\n <body>\n <div class=\"container\">\n");
String contextPath = request.getContextPath();
if (this.formLoginEnabled) {
sb.append(" <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n <h2 class=\"form-signin-heading\">Please sign in</h2>\n" + createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + " <p>\n <label for=\"username\" class=\"sr-only\">Username</label>\n <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n </p>\n <p>\n <label for=\"password\" class=\"sr-only\">Password</label>\n <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n </p>\n" + this.createRememberMe(this.rememberMeParameter) + this.renderHiddenInputs(request) + " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n </form>\n");
}
if (this.openIdEnabled) {
sb.append(" <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n" + createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + " <p>\n <label for=\"username\" class=\"sr-only\">Identity</label>\n <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n </p>\n" + this.createRememberMe(this.openIDrememberMeParameter) + this.renderHiddenInputs(request) + " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n </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");
Iterator var7 = this.oauth2AuthenticationUrlToClientName.entrySet().iterator();
while(var7.hasNext()) {
Entry<String, String> clientAuthenticationUrlToClientName = (Entry)var7.next();
sb.append(" <tr><td>");
String url = (String)clientAuthenticationUrlToClientName.getKey();
sb.append("<a href=\"").append(contextPath).append(url).append("\">");
String clientName = HtmlUtils.htmlEscape((String)clientAuthenticationUrlToClientName.getValue());
sb.append(clientName);
sb.append("</a>");
sb.append("</td></tr>\n");
}
sb.append("</table>\n");
}
sb.append("</div>\n");
sb.append("</body></html>");
return sb.toString();
}
ExceptionTranslationFilter:是滤器链中的倒数第二个,如果没有登录时进行请求,该过滤器会重定向到配置的登录页面。
总结
通过以上的分析,可以知道Spring Security使用一系列的过滤器来提供其安全功能。包括CsrfFilter、UsernamePasswordAuthenticationFilter、DefaultLoginPageGeneratingFilter、ExceptionTranslationFilter等等,它们在Spring Security中扮演着各自的角色,协同工作以提供完善的安全保障。