|
对于最新的稳定版本,请使用 Spring Security 7.0.4! |
Java 配置
Spring 框架 Java 开发安全配置
首先,我们需要创建我们的Spring Security Java配置。
该配置创建一个名为springSecurityFilterChain的Servlet过滤器,它负责处理您应用程序中的所有安全问题(保护应用程序URL、验证提交的用户名和密码、重定向到登录表单等)。
以下示例展示了最基础的Spring Security Java配置示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
return manager;
}
}
这配置并不复杂或繁多,但它做了很多事情:
-
要求应用程序中的每个URL进行身份验证
-
生成一个登录表单给您的
-
让用户名为user、密码为
user的用户通过表单认证进行身份验证 -
让用户登出
-
CSRF攻击 防护
-
会话固定 保护
-
安全头部集成:
-
HTTP严格传输安全 用于安全请求
-
缓存控制(您可以在应用程序中稍后覆盖此设置以允许缓存您的静态资源)
-
X-Frame-Options 集成以帮助防止
点击劫持
-
-
与以下Servlet API方法进行集成:
抽象安全 Web 应用初始化器
下一步是将 springSecurityFilterChain 注册到 WAR 文件中。
在 Servlet 3.0+ 环境中,您可以在 Java 配置中通过 Spring 的 WebApplicationInitializer 支持 完成此操作。
毫不奇怪,Spring Security 提供了一个基类(AbstractSecurityWebApplicationInitializer),以确保 springSecurityFilterChain 为您自动注册。
我们使用 AbstractSecurityWebApplicationInitializer 的方式取决于是否已经在应用中使用 Spring,或者 Spring Security 是否是应用中唯一的 Spring 组件。
-
不使用现有Spring的AbstractSecurityWebApplicationInitializer - 如果您尚未使用Spring,请按照这些说明操作
-
AbstractSecurityWebApplicationInitializer与Spring MVC - 如果您已经使用了Spring,请按照这些说明操作。
在没有现有 Spring 的情况下初始化 AbstractSecurityWebApplication
如果您未使用Spring或Spring MVC,则需要将WebSecurityConfig传递给超类,以确保配置被拾取:
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
The SecurityWebApplicationInitializer:
-
自动为应用程序中的每个URL注册
springSecurityFilterChain过滤器。 -
添加一个
ContextLoaderListener来加载WebSecurityConfig。
使用 Spring MVC 的 AbstractSecurityWebApplicationInitializer
如果我们在应用程序的其他地方使用了Spring,我们可能已经有一个WebApplicationInitializer在加载我们的Spring配置。
如果我们使用之前的配置,将会出现错误。
相反,我们应该将Spring Security注册到现有的ApplicationContext中。
例如,如果我们使用Spring MVC,那么我们的SecurityWebApplicationInitializer可以看起来像下面这样:
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
这只会为应用程序中的每个URL注册springSecurityFilterChain。
之后,我们需要确保在现有的WebSecurityConfig中加载了ApplicationInitializer。
例如,在使用Spring MVC时,它会添加到getServletConfigClasses()中:
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
这是原因,因为Spring Security需要能够检查一些Spring MVC配置,以便适当配置底层请求匹配器,因此它们需要在同一应用程序上下文中。
将Spring Security放置在getRootConfigClasses中会将其置于可能无法找到Spring MVC的HandlerMappingIntrospector的应用程序父上下文之中。
为多个 Spring MVC Dispatcher 进行配置
如果需要,任何与Spring MVC无关的Spring Security配置可以放在另一个配置类中,例如:
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { NonWebSecurityConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
这在您有多个AbstractAnnotationConfigDispatcherServletInitializer实例且不希望在这两个中重复通用的安全配置时可能会很有帮助。
HttpSecurity
到目前为止,我们的 WebSecurityConfig 仅包含有关如何对用户进行身份验证的信息。
Spring Security 如何知道我们需要要求所有用户进行身份验证?
Spring Security 如何知道我们需要支持基于表单的身份验证?
实际上,有一个配置类(称为 SecurityFilterChain)在后台被调用。
它使用以下默认实现进行配置:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
return http.build();
}
默认配置(如上例所示):
-
确保任何对我们应用程序的请求都需要用户进行身份验证
-
允许用户通过表单登录进行身份验证
-
允许用户通过HTTP基本身份验证进行身份验证
注意,此配置与XML命名空间配置相对应:
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
多个 HttpSecurity 实例
要有效地管理应用程序中的安全问题,某些区域需要不同的保护级别时,我们可以在 securityMatcher DSL 方法之外使用多个过滤器链。
这种方法允许我们为应用程序的特定部分定义独特的安全配置,从而增强整体应用的安全性和控制能力。
我们可以像在XML中可以有多个HttpSecurity块一样,配置多个<http>实例。
关键在于注册多个SecurityFilterChain @Bean。
以下示例对以/api/开头的URL有不同的配置:
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() throws Exception {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") (3)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean (4)
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
| 1 | 按常规配置身份验证。 |
| 2 | 创建一个包含SecurityFilterChain来指定哪个@Order应该被首先考虑的SecurityFilterChain实例。 |
| 3 | http.securityMatcher() 表示该 HttpSecurity 仅适用于以 /api/ 开头的 URL。 |
| 4 | 创建另一个SecurityFilterChain的实例。
如果URL不以/api/开头,将使用此配置。
此配置会在apiFilterChain之后被考虑,因为它具有比@Order大的1值(如果没有@Order则默认为最后)。 |
选择securityMatcher or requestMatchers
常见问题有:
http.securityMatcher()方法和requestMatchers()在请求授权(即在http.authorizeHttpRequests()中)方面有什么区别?
要回答这个问题,有助于理解每个用于构建HttpSecurity的SecurityFilterChain实例包含一个RequestMatcher来匹配传入请求。
如果一个请求没有匹配具有更高优先级的SecurityFilterChain(例如@Order(1)),该请求可以尝试匹配较低优先级的过滤链(例如无@Order)。
|
多个过滤器链的匹配逻辑由 |
The default RequestMatcher 匹配 所有请求 以确保 Spring Security 默认保护 所有请求。
|
指定一个 |
|
若没有过滤器链匹配某个请求,则该请求将不受Spring Security保护。 |
以下示例演示了一个单一的过滤器链,仅保护以/secured/开头的请求:
@Configuration
@EnableWebSecurity
public class PartialSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/secured/user").hasRole("USER") (2)
.requestMatchers("/secured/admin").hasRole("ADMIN") (3)
.anyRequest().authenticated() (4)
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
| 1 | 以/secured/开头的请求将受到保护,但其他任何请求都不会受到保护。 |
| 2 | 对 /secured/user 的请求需要具有 ROLE_USER 权限。 |
| 3 | 对 /secured/admin 的请求需要具有 ROLE_ADMIN 权限。 |
| 4 | 任何其他请求(例如/secured/other)只需一个已认证的用户。 |
|
建议提供一个未指定任何 |
注意,requestMatchers 方法仅适用于个别授权规则。
每个在此处列出的请求还必须匹配此特定 securityMatcher 实例用于创建 HttpSecurity 的整体 SecurityFilterChain。
在这个例子中使用 anyRequest() 匹配的是该特定 SecurityFilterChain 中的所有其他请求(这些请求必须以 /secured/ 开头)。
|
见授权 HttpServletRequest 请求 以获取更多关于 |
SecurityFilterChain端点
SecurityFilterChain 中的多个过滤器直接提供端点,例如由 http.formLogin() 设置并暴露 POST /login 端点的 UsernamePasswordAuthenticationFilter。
在 上述示例 中,/login 端点未被 http.securityMatcher("/secured/**") 匹配,因此该应用程序不会具有任何 GET /login 或 POST /login 端点。
此类请求将返回 404 Not Found。
这常常令用户感到意外。
指定http.securityMatcher()会影响该SecurityFilterChain匹配哪些请求。
然而,这并不会自动影响过滤器链提供的端点。
在这种情况下,您可能需要自定义任何希望过滤器链提供的端点的URL。
以下示例演示了如何配置以保护所有以/secured/开头的请求,并拒绝其他所有请求,同时自定义由SecurityFilterChain提供的端点:
@Configuration
@EnableWebSecurity
public class SecuredSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
@Order(1)
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated() (2)
)
.formLogin(formLogin -> formLogin (3)
.loginPage("/secured/login")
.loginProcessingUrl("/secured/login")
.permitAll()
)
.logout(logout -> logout (4)
.logoutUrl("/secured/logout")
.logoutSuccessUrl("/secured/login?logout")
.permitAll()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().denyAll() (5)
);
return http.build();
}
}
| 1 | 以/secured/开头的请求将由该过滤器链保护。 |
| 2 | 以/secured/开头的请求需要经过身份验证的用户。 |
| 3 | 自定义表单登录以在URL前缀添加/secured/。 |
| 4 | 自定义登出以在 URL 前缀添加 /secured/。 |
| 5 | 所有其他请求将被拒绝。 |
|
此示例自定义了登录和注销页面,这会禁用Spring Security生成的页面。
您必须为 |
真实世界示例
以下示例展示了将这些元素综合在一起的更接近实际世界配置的情况:
@Configuration
@EnableWebSecurity
public class BankingSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
manager.createUser(users.username("user2").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain approvalsSecurityFilterChain(HttpSecurity http) throws Exception {
String[] approvalsPaths = { "/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**" };
http
.securityMatcher(approvalsPaths)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
@Order(2) (3)
public SecurityFilterChain bankingSecurityFilterChain(HttpSecurity http) throws Exception {
String[] bankingPaths = { "/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**" };
String[] viewBalancePaths = { "/balances/**" };
http
.securityMatcher(bankingPaths)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(viewBalancePaths).hasRole("VIEW_BALANCE")
.anyRequest().hasRole("USER")
);
return http.build();
}
@Bean (4)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
String[] allowedPaths = { "/", "/user-login", "/user-logout", "/notices", "/contact", "/register" };
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(allowedPaths).permitAll()
.anyRequest().authenticated()
)
.formLogin(formLogin -> formLogin
.loginPage("/user-login")
.loginProcessingUrl("/user-login")
)
.logout(logout -> logout
.logoutUrl("/user-logout")
.logoutSuccessUrl("/?logout")
);
return http.build();
}
}
| 1 | 按步骤配置认证设置。 |
| 2 | 定义一个带有SecurityFilterChain的@Order(1)实例,这意味着此过滤器链将具有最高的优先级。
此过滤器链仅适用于以/accounts/approvals/、/loans/approvals/或/credit-cards/approvals/开头的请求。
对于此过滤器链的请求需要具有ROLE_ADMIN权限,并允许使用HTTP Basic认证。 |
| 3 | 接下来,创建另一个SecurityFilterChain实例,并使用@Order(2),这将被视为第二个过滤链。
此过滤链仅适用于以/accounts/、/loans/、/credit-cards/或/balances/开头的请求。
注意,由于此过滤链是第二个,任何包含/approvals/的请求将匹配之前的过滤链,并且不会被此过滤链匹配。
此过滤链中的请求需要具有ROLE_USER权限。
此过滤链未定义任何认证,因为下一个(默认)过滤链包含了该配置。 |
| 4 | 最后,创建一个额外的 SecurityFilterChain 实例,且不带 @Order 注解。
此配置将处理未被其他过滤器链覆盖的请求,并将最后进行处理(未指定 @Order 默认为最后)。
匹配 /、/user-login、/user-logout、/notices、/contact 和 /register 的请求允许无需身份验证即可访问。
任何其他请求都要求用户经过身份验证,才能访问未明确允许或未被其他过滤器链保护的 URL。 |
自定义领域特定语言
您可以在Spring Security中提供自己的自定义DSL:
-
Java
-
Kotlin
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
private boolean flag;
@Override
public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable();
}
@Override
public void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// here we lookup from the ApplicationContext. You can also just create a new instance.
MyFilter myFilter = context.getBean(MyFilter.class);
myFilter.setFlag(flag);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
public MyCustomDsl flag(boolean value) {
this.flag = value;
return this;
}
public static MyCustomDsl customDsl() {
return new MyCustomDsl();
}
}
class MyCustomDsl : AbstractHttpConfigurer<MyCustomDsl, HttpSecurity>() {
var flag: Boolean = false
override fun init(http: HttpSecurity) {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable()
}
override fun configure(http: HttpSecurity) {
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
// here we lookup from the ApplicationContext. You can also just create a new instance.
val myFilter: MyFilter = context.getBean(MyFilter::class.java)
myFilter.setFlag(flag)
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter::class.java)
}
companion object {
@JvmStatic
fun customDsl(): MyCustomDsl {
return MyCustomDsl()
}
}
}
|
这实际上是如何实现像 |
然后您可以使用自定义DSL:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.flag(true)
)
// ...
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
flag = true
}
// ...
return http.build()
}
}
代码按照以下顺序被调用:
-
Config.filterChain方法中的代码会被调用 -
MyCustomDsl.init方法中的代码会被调用 -
MyCustomDsl.configure方法中的代码会被调用
如果您想让HttpSecurity默认添加MyCustomDsl,可以通过使用SpringFactories来实现。
例如,您可以在类路径上创建一个名为META-INF/spring.factories的资源文件,并包含以下内容:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
您也可以显式禁用默认设置:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.disable()
)
...;
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
disable()
}
// ...
return http.build()
}
}
已配置对象的后期处理
Spring Security的Java配置并不暴露它所配置的每个对象的所有属性。 这简化了大多数用户的配置过程。 毕竟,如果每个属性都暴露出来,用户就可以使用标准的bean配置了。
在直接暴露每个属性方面虽然有很好的理由,但用户仍然可能需要更高级的配置选项。
为此,Spring Security 引入了 ObjectPostProcessor 的概念,可以使用它来修改或替换 Java 配置创建的许多 Object 实例。
例如,要配置 filterSecurityPublishAuthorizationSuccess 的 FilterSecurityInterceptor 属性,可以使用以下代码:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(
O fsi) {
fsi.setPublishAuthorizationSuccess(true);
return fsi;
}
})
);
return http.build();
}