在 Spring 3.1 中,Spring Framework 中添加了对 Java 配置的一般支持。 Spring Security 3.2 引入了 Java 配置,允许用户在不使用任何 XML 的情况下配置 Spring Security。
如果您熟悉 Security Namespace Configuration,您应该会发现它与 Spring Security Java 配置之间有很多相似之处。
Spring Security 提供了许多示例应用程序来演示 Spring Security Java Configuration 的使用。 |
Spring Security 提供了许多示例应用程序来演示 Spring Security Java Configuration 的使用。 |
Hello Web Security Java 配置
第一步是创建我们的 Spring Security Java 配置。
该配置将创建一个称为 的 Servlet 过滤器,它负责应用程序中的所有安全性(保护应用程序 URL、验证提交的用户名和密码、重定向到登录表单等)。
以下示例显示了 Spring Security Java 配置的最基本示例:springSecurityFilterChain
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 进行身份验证
-
为您生成登录表单
-
让 Username 为 和 Password 的用户使用基于表单的身份验证进行身份验证
user
password
-
允许用户注销
-
CSRF 攻击预防
-
安全标头集成:
-
用于安全请求的 HTTP 严格传输安全性
-
缓存控制(您可以稍后在应用程序中覆盖它以允许缓存静态资源)
-
X-Frame-Options 集成有助于防止点击劫持
-
-
与以下 Servlet API 方法集成:
AbstractSecurityWebApplicationInitializer
下一步是向 WAR 文件注册 。
您可以在 Servlet 3.0+ 环境中使用 Spring 的 WebApplicationInitializer
支持在 Java 配置中执行此操作。
毫不奇怪, Spring Security 提供了一个基类 () 来确保为您注册。
我们使用的方式会有所不同,具体取决于我们是否已经在使用 Spring,或者 Spring Security 是否是我们应用程序中唯一的 Spring 组件。springSecurityFilterChain
AbstractSecurityWebApplicationInitializer
springSecurityFilterChain
AbstractSecurityWebApplicationInitializer
-
没有现有 Spring 的 AbstractSecurityWebApplicationInitializer - 如果您尚未使用 Spring,请使用这些说明
-
AbstractSecurityWebApplicationInitializer 与 Spring MVC - 如果您已经在使用 Spring,请使用这些说明
没有现有 Spring 的 AbstractSecurityWebApplicationInitializer
如果你没有使用 Spring 或 Spring MVC,你需要将 传递给 超类 以确保配置被拾取:WebSecurityConfig
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
这:SecurityWebApplicationInitializer
-
自动为应用程序中的每个 URL 注册 Filter。
springSecurityFilterChain
-
添加一个加载 WebSecurityConfig 的 a 。
ContextLoaderListener
使用 Spring MVC 的 AbstractSecurityWebApplicationInitializer
如果我们在应用程序的其他地方使用 Spring,我们可能已经有一个正在加载我们的 Spring Configuration。
如果我们使用以前的配置,则会收到错误。
相反,我们应该使用现有的 .
例如,如果我们使用 Spring MVC,我们的可能如下所示:WebApplicationInitializer
ApplicationContext
SecurityWebApplicationInitializer
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
这只会在应用程序中的每个 URL 中注册 。
之后,我们需要确保 它已加载到我们现有的 .
例如,如果我们使用 Spring MVC,它会被添加到 :springSecurityFilterChain
WebSecurityConfig
ApplicationInitializer
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 放在 中,将其放入可能无法找到 Spring MVC 的父应用程序上下文中。getRootConfigClasses
HandlerMappingIntrospector
配置多个 Spring MVC 调度器
如果需要,任何与 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(withDefaults())
.httpBasic(withDefaults());
return http.build();
}
默认配置(如前面的示例所示):
-
确保对我们的应用程序的任何请求都需要对用户进行身份验证
-
允许用户使用基于表单的登录进行身份验证
-
允许用户使用 HTTP 基本身份验证进行身份验证
请注意,此配置与 XML Namespace 配置类似:
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
多个 HttpSecurity 实例
我们可以配置多个实例,就像我们可以在 XML 中拥有多个块一样。
关键是要注册多个 s。
以下示例对以 .HttpSecurity
<http>
SecurityFilterChain
@Bean
/api/
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
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(withDefaults());
return http.build();
}
@Bean (4)
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
}
1 | 照常配置 Authentication。 |
2 | 创建该 contains 的实例以指定应首先考虑哪个实例。SecurityFilterChain @Order SecurityFilterChain |
3 | 声明这仅适用于以 .http.securityMatcher HttpSecurity /api/ |
4 | 创建 的另一个实例。
如果 URL 不以 开头,则使用此配置。
此配置在 之后考虑,因为它在 之后 具有值(没有默认为 last)。SecurityFilterChain /api/ apiFilterChain @Order 1 @Order |
1 | 照常配置 Authentication。 |
2 | 创建该 contains 的实例以指定应首先考虑哪个实例。SecurityFilterChain @Order SecurityFilterChain |
3 | 声明这仅适用于以 .http.securityMatcher HttpSecurity /api/ |
4 | 创建 的另一个实例。
如果 URL 不以 开头,则使用此配置。
此配置在 之后考虑,因为它在 之后 具有值(没有默认为 last)。SecurityFilterChain /api/ apiFilterChain @Order 1 @Order |
自定义 DSL
您可以在 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()
}
}
}
这实际上是 method like 的实现方式。 |
然后,您可以使用自定义 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
如果需要,默认情况下,您可以使用 .
例如,您可以在以以下内容命名的 Classpath 上创建一个资源: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()
}
}
这实际上是 method like 的实现方式。 |
后处理已配置的对象
Spring Security 的 Java 配置不会公开它配置的每个对象的每个属性。 这简化了大多数用户的配置。 毕竟,如果每个属性都公开了,用户就可以使用标准的 bean 配置。
虽然有充分的理由不直接公开每个属性,但用户可能仍需要更高级的配置选项。
为了解决这个问题, Spring Security 引入了 an 的概念,它可用于修改或替换 Java 配置创建的许多实例。
例如,要在 上配置属性,可以使用以下内容:ObjectPostProcessor
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();
}