SpringSecurity
[[toc]]
版本配置:
- SpringBoot : 3.2.2
- SpringSecurity : 6.2.0
- JDK : 17
- MySQL : 8.0.35
SpringSecurity官方文档
https://spring.io/projects/spring-security
SpringSecurity官方示例
https://github.com/spring-projects/spring-sec
urity-samples
一. SpringSecurity简述
Spring Security 是一个提供身份验证
、授权
和针对常见攻击的保护
的框架。 凭借对保护命令式和反应式应用程序的一流支持,它成为保护基于 Spring 的应用程序的事实上的标准。
-
身份认证:是指确认用户身份是否有效的过程,确保用户是其声称的那个人,并且具有访问系统资源的权限。
Spring Security 提供了各种身份认证的方式和机制,包括但不限于:
- 基于表单的认证: 用户通过输入用户名和密码来进行身份验证,通常在 Web 应用程序中使用。
- HTTP 基本认证和摘要认证: 基于 HTTP 协议的基本身份认证和摘要身份认证。
- OAuth 认证: 支持 OAuth 协议,允许用户通过第三方身份提供者进行身份验证。
- JWT 认证: 使用 JSON Web Token(JWT)进行身份验证和授权。
- LDAP 认证: 通过 Lightweight Directory Access Protocol(LDAP)进行身份验证,常用于企业环境中集中管理用户身份。
- CAS 认证: 使用 Central Authentication Service(CAS)进行单点登录和身份验证。
-
授权:用户身份认证完成后,SpringSecurity能够控制用户所能够访问的资源
-
防御常见攻击:
- CSRF
二、第一个SpringSecurity程序
项目结构:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SpringSecurityLearn</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringSecurityLearn</name>
<description>SpringSecurityLearn</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
IndexController
package com.example.springsecuritylearn.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController {
@GetMapping("/")
public String index() {
return "index.html";
}
}
启动项目后会跳转到如下页面:
-
Username:user
-
Password:在控制台自动生成(很明显这是一个UUID格式)
正确输入两项之后便能够得到如下结果:
登录页面渲染异常或者无法页面卡死无法进入等页面,则是由于SpringSecurity自动为我们生成的登陆页面中的bootstra.min.css资源无法加载导致的,需要科学。
三、SpringSecurity底层原理
3.1 Filter复习
Spring Security 的 Servlet 支持是基于 Servlet Filter 的。下图显示了单个 HTTP 请求的处理程序的典型分层。
客户端向应用程序发送一个请求,容器创建一个 FilterChain
,其中包含 Filter
实例和 Servlet
,应该根据请求URI的路径来处理 HttpServletRequest
。在Spring MVC应用程序中,Servlet是 DispatcherServlet
的一个实例。一个 Servlet
最多可以处理一个 HttpServletRequest
和 HttpServletResponse
。然而,可以使用多个 Filter
来完成如下工作。
- 防止下游的
Filter
实例或Servlet
被调用。在这种情况下,Filter
通常会使用HttpServletResponse
对客户端写入响应。 - 修改下游的
Filter
实例和Servlet
所使用的HttpServletRequest
或HttpServletResponse
。
前面的IndexController底层实际上正是Servlet
一般情况下都是采用SpringBoot来集成SpringSecurity,而SpringBoot底层还是Spring那一套,因此我们很自然的想到可以将过滤器实例作为Bean交给Spring上下文管理。那么在对Filter的使用上就会更加灵活。
3.2 DelegatingFilterProxy
Spring 提供了一个名为 DelegatingFilterProxy
的 Filter
实现,允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext
之间建立桥梁。Servlet容器允许通过使用自己的标准来注册 Filter
实例,但它不知道 Spring 定义的 Bean。你可以通过标准的Servlet容器机制来注册 DelegatingFilterProxy
,但将所有工作委托给实现 Filter
的Spring Bean。
下面是 DelegatingFilterProxy
如何融入 Filter实例和FilterChain
例如上图,DelegatingFilterProxy从ApplicationContext中查找到Bean Filter0,然后待用Bean Filter0。
也就是说,它允许延迟查找Filter Bean实例。在容器启动之前,容器需要注册Filter实例。然而,Spring通常使用ContextLoaderListener来加载Spring Bean,这在需要注册Filter实例之后才会完成。
DelegatingFilterProxy的伪码
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain){
Filter delegate = getFilterBean(someBeanName);
delegate.doFilter(request,response);
}
DelegatingFilterProxy就是一个Filter可以被注册在Servlet的过滤器链中,然后注册在Spring容器中的Bean Filter就可以被DelegatinFilterProxy调用,从而工作在整个Servlet生命周期中。
这么做,对于Filter的启用等操作就会方便灵活很多。
3.3 FilterChainProxy
Spring Security 的 Servlet 支持包含在 FilterChainProxy
中。FilterChainProxy
是 Spring Security 提供的一个特殊的 Filter
,允许通过 SecurityFilterChain
委托给许多 Filter
实例。由于 FilterChainProxy是一个Bean,它通常被包裹在 DelegatingFilterProxy中。
3.4 SecurityFilterChain
实际应用情况
SecurityFilterChain被FilterChainProxt用来确定当前请求应该调用哪些Spring Security Filter实例
SecurityFilterChain
中的 Security Filter
通常是Bean
,但它们是用 FilterChainProxy
而不是 DelegatingFilterProxy
注册的。与直接向Servlet容器或 DelegatingFilterProxy
注册相比,FilterChainProxy
有很多优势。首先,它为 Spring Security 的所有 Servlet 支持提供了一个起点。由于这个原因,如果你试图对 Spring Security 的 Servlet 支持进行故障诊断,在 FilterChainProxy
中添加一个调试点是一个很好的开始。
其次,由于 FilterChainProxy
是 Spring Security 使用的核心,它可以执行一些不被视为可有可无的任务。 例如,它清除了 SecurityContext
以避免内存泄漏。它还应用Spring Security的 HttpFirewall
来保护应用程序免受某些类型的攻击。
此外,它在确定何时应该调用 SecurityFilterChain
方面提供了更大的灵活性。在Servlet容器中,Filter
实例仅基于URL被调用。 然而,FilterChainProxy
可以通过使用 RequestMatcher
接口,根据 HttpServletRequest
中的任何内容确定调用。
下图为多SecurityFilterChain实例。
FilterChainProxt会根据请求决定使用哪个SecurityFilterChain,而且最多调用一个。如果请求的URL是/api/message/,那么按顺序来它首先与SpringSecurityn0相匹配,尽管他还和最后一个SecurityFilterChain匹配,但是FilterChainProxy仍然只调用第一次模式匹配的SecurityFilterChain即n0。
四、DefaultSecurityFilterChain
4.1 简要分析
首先在IDEA中搜索DefaultSecurityFilterChain
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Standard implementation of {@code SecurityFilterChain}.
*
* @author Luke Taylor
* @since 3.1
*/
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
if (filters.isEmpty()) {
logger.info(LogMessage.format("Will not secure %s", requestMatcher));
}
else {
logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
}
this.requestMatcher = requestMatcher;
this.filters = new ArrayList<>(filters);
}
public RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
@Override
public List<Filter> getFilters() {
return this.filters;
}
@Override
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " [RequestMatcher=" + this.requestMatcher + ", Filters=" + this.filters
+ "]";
}
}
启动SpringBoot项目,发现在控制台打印了这样一条语句
后面的具体内容为:
Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@409a76d4, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@3e768587, org.springframework.security.web.context.SecurityContextHolderFilter@5c0bb8a, org.springframework.security.web.header.HeaderWriterFilter@82d2d7c, org.springframework.web.filter.CorsFilter@244d6082, org.springframework.security.web.csrf.CsrfFilter@7a39e757, org.springframework.security.web.authentication.logout.LogoutFilter@11a10c17, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@494c89b2, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@5c9f48fa, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@fae6ead, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@231bb139, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@9d27d01, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@7c212ae2, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1c91955a, org.springframework.security.web.access.ExceptionTranslationFilter@1b82d3af, org.springframework.security.web.access.intercept.AuthorizationFilter@5b7bcedf]
这些内容的格式似乎有点眼熟?
构造方法的调用顺序是显而易见的,默认过滤器链中也能清楚的看到16个Filter。
4.2 修改默认过滤器
默认过滤器当然不可能完全符合使用需求,因此有必要对其进行一定的修改。
org.springframework.security.web.access.ExceptionTranslationFilter@1b82d3af, org.springframework.security.web.access.intercept.AuthorizationFilter@5b7bcedf]
这些内容的格式似乎有点眼熟?
构造方法的调用顺序是显而易见的,默认过滤器链中也能清楚的看到16个Filter。
4.2 修改默认过滤器
默认过滤器当然不可能完全符合使用需求,因此有必要对其进行一定的修改。