1.何为spring Security
Spring Security is a framework that provides authentication, authorization, and protection against common attacks. With first class support for both imperative and reactive applications, it is the de-facto standard for securing Spring-based applications.
Spring Security是一个提供认证,授权,保护应用应对常见的攻击的框架,对命令式和响应式的应用提供一流的支持,其是保护基于Spring应用的约定俗成的标准。
首先引用Spring Security官方文档的一段介绍文字,
该系列文章主要介绍Servlet应用中的Spring Security运用。
2.Spring Boot + spring Security 自动配置
- 开启Spring Security的自动配置后将创建一个名为springSecurityFilterChain的Filter,该bean负责应用中的所有安全职责(保护应用的路径,验证提交的账号秒,重定向到登录表单等)
- 创建一个UserDetailsService生成名为user,随机生成的密码(打印在控制台中)
- 在Servlet容器中注册名为springSecurityFilterChain的Filter来过滤所有的请求
3.Spring Boot + spring Security 简单的启动不需要很多的配置,但是做了很多事情
- 经过身份验证的用户才能与应用进行交互
- 生成一个默认的登录表单
- 在控制台生成用户密码供通过认证
- 使用BCrypt保护密码存储
- 提供注销接口
- 防止CSRF攻击
- 回话保护
- 强制安全传输技术
- X-Content-Type-Options头部集成
- 缓存控制
- XSS攻击防护(X-XSS-Protection)
- X-Frame-Options防止点击劫持
- 提供以下Servlet API方法
- getRemoteUser()
- isUserInRole(java.lang.String)
- isUserInRole(java.lang.String)
- login(java.lang.String, java.lang.String)
- logout()
4.Servlet Security 中的Filter
s
4.1 Filter
s一览
Spring Security 的Servlet支持是基于Servlet的Filter
s,如下图所示:
客户端发送请求到应用,容器创建一个过滤链(FilterChain),其中包含Filter
s和Servlet,病根据请求URI的路径处理HttpServletRequest,在Spring MVC的应用中该Servlet的实例为DispatcherServlet,通常情况一个Servlet处理一个单独的HttpServletRequest和HttpServletResponse,但是,多个
Filter可以这样使用:
- 防止下游的
Filters
或Servlet被调用。在当前处理的Filter中
,过滤器可以直接写回HttpServletResponse - 放过请求,由下游的
Filter
s和Servlet来处理HttpServletRequest和HttpServletResponse
FilterChain
使用例子:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 在此之前做些事情
chain.doFilter(request, response); // 逻辑部分
// 在此之后做些事情
}
4.2 DelegatingFilterProxy
Spring提供了一个Filter的实现类
DelegatingFilterProxy,其在Servlet容器的生命周期和Spring的ApplicationContext之间建立桥梁,Servlet容器允许所以注册的Filter
s使用自己的标准,但是这些Filter
s无法获取到Spring定义的对象,DelegatingFilterProxy
可以通过标准Servlet容器机制进行注册,但是将所有工作委托给实现了Filter的Spring对象。
DelegatingFilterProxy从ApplicationContext中获取到第一个Filter并且调用其过滤方法,以下是DelegatingFilterProxy伪代码:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 惰性获取注册到Spring Bean的过滤器
// 以下delegate为elegatingFilterProxy中的一个实例
Filter delegate = getFilterBean(someBeanName);
// 将工作委托给Spring Bean
delegate.doFilter(request, response);
}
DelegatingFilterProxy
的另一个好处是,它允许延迟查找Filter
bean实例。这点很重要,因为容器需要Filter
在容器启动之前注册实例。但是,Spring通常使用ContextLoaderListener
来加载Spring Bean,直到Filter
需要注册实例之后才能完成。使用DelegatingFilterProxy可以在调用的时候再去
ApplicationContext获取需要的Filter
DelegatingFilterProxy由
DelegatingFilterProxyRegistrationBean初始化
public DelegatingFilterProxy getFilter() {
return new DelegatingFilterProxy(this.targetBeanName, this.getWebApplicationContext()) {
protected void initFilterBean() throws ServletException {
}
};
}
DelegatingFilterProxyRegistrationBean中targetBeanName的值为’springSecurityFilterChain‘
DelegatingFilterProxy中核心代码:
private String targetBeanName;
private volatile Filter delegate;
public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
this.targetFilterLifecycle = false;
this.delegateMonitor = new Object();
Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
this.setTargetBeanName(targetBeanName);
this.webApplicationContext = wac;
if (wac != null) {
this.setEnvironment(wac.getEnvironment());
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
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);
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = this.getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
if (this.isTargetFilterLifecycle()) {
delegate.init(this.getFilterConfig());
}
return delegate;
}
- 首先DelegatingFilterProxyRegistrationBean新建
DelegatingFilterProxy
对象,并设置targetBeanName为springSecurityFilterChain - 当执行到
DelegatingFilterProxy中的
doFilter方法时delegate对象为null - 调用this.initDelegate(wac),从WebApplicationContext中获取到spring管理的delegate(springSecurityFilterChain)
- 调用springSecurityFilterChain的doFilter方法
4.3 FilterChainProxy
Spring Security的Servlet支持包含在FilterChainProxy中,FilterChainProxy是由Spring Security提供的一个特殊的Filter,其允许通过SecurityFilterChain委托多个过滤器实例,因为FilterChainProxy是一个Bean,所以它通常封装在DelegatingFilterProxy中。
4.4 SecurityFilterChain
SecurityFilterChain由FilterChainProxy用来确定应该为这个请求调用哪个Spring Security过滤器