Java Configuration
Spring Framework 在 Spring 3.1 中增加了对 Java configuration 的通用支持。Spring Security 3.2 引入了 Java 配置,使用户可以配置 Spring Security 而无需使用任何 XML。
General support for Java configuration was added to Spring Framework in Spring 3.1. Spring Security 3.2 introduced Java configuration to let users configure Spring Security without the use of any XML.
如果您熟悉 Security Namespace Configuration,您应该发现它与 Spring Security Java 配置之间有很多相似之处。
If you are familiar with the Security Namespace Configuration, you should find quite a few similarities between it and Spring Security Java configuration.
Spring Security 提供了 lots of sample applications 来演示 Spring Security Java 配置的使用。 Spring Security provides lots of sample applications to demonstrate the use of Spring Security Java Configuration. |
Hello Web Security Java Configuration
第一步是创建我们的 Spring Security Java 配置。此配置创建一个名为 springSecurityFilterChain
的 Servlet 过滤器,此过滤器负责应用程序中的所有安全性(保护应用程序 URL、验证提交的用户名和密码、重定向到登录表单等)。以下示例展示了 Spring Security Java 配置最基本的示例:
The first step is to create our Spring Security Java Configuration.
The configuration creates a Servlet Filter known as the springSecurityFilterChain
, which is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the log in form, and so on) within your application.
The following example shows the most basic example of a Spring Security Java Configuration:
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;
}
}
此配置不复杂或冗余,但它能完成大量工作:
This configuration is not complex or extensive, but it does a lot:
-
Require authentication to every URL in your application
-
Generate a login form for you
-
Let the user with a Username of
user
and a Password ofpassword
authenticate with form based authentication -
Let the user logout
-
CSRF attack prevention
-
Session Fixation protection
-
Security Header integration:
-
HTTP Strict Transport Security for secure requests
-
X-Content-Type-Options integration
-
Cache Control (which you can override later in your application to allow caching of your static resources)
-
X-XSS-Protection integration
-
X-Frame-Options integration to help prevent Clickjacking
-
-
Integration with the following Servlet API methods:
AbstractSecurityWebApplicationInitializer
下一步是将 springSecurityFilterChain
注册到 WAR 文件。你可以在 Servlet 3.0+ 环境中的 Java 配置中使用 Spring’s WebApplicationInitializer
support 来执行此操作。毫不奇怪,Spring Security 提供了一个基类 (AbstractSecurityWebApplicationInitializer
) 以确保 springSecurityFilterChain
已为你注册。根据我们是否已使用 Spring 或者 Spring Security 是否是我们应用程序中唯一的 Spring 组件,我们使用 AbstractSecurityWebApplicationInitializer
的方式有所不同。
The next step is to register the springSecurityFilterChain
with the WAR file.
You can do so in Java configuration with Spring’s WebApplicationInitializer
support in a Servlet 3.0+ environment.
Not surprisingly, Spring Security provides a base class (AbstractSecurityWebApplicationInitializer
) to ensure that the springSecurityFilterChain
gets registered for you.
The way in which we use AbstractSecurityWebApplicationInitializer
differs depending on if we are already using Spring or if Spring Security is the only Spring component in our application.
-
AbstractSecurityWebApplicationInitializer without Existing Spring - Use these instructions if you are not already using Spring
-
AbstractSecurityWebApplicationInitializer with Spring MVC - Use these instructions if you are already using Spring
AbstractSecurityWebApplicationInitializer without Existing Spring
如果你没有使用 Spring 或 Spring MVC,则需要将 WebSecurityConfig
传递给超类,以确保选取配置:
If you are not using Spring or Spring MVC, you need to pass the WebSecurityConfig
to the superclass to ensure the configuration is picked up:
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
SecurityWebApplicationInitializer
:
The SecurityWebApplicationInitializer
:
-
Automatically registers the
springSecurityFilterChain
Filter for every URL in your application. -
Add a
ContextLoaderListener
that loads the jc-hello-wsca.
AbstractSecurityWebApplicationInitializer with Spring MVC
如果我们在应用程序的其他位置使用 Spring,那么我们可能已经有了正在加载我们的 Spring 配置的 WebApplicationInitializer
。如果我们使用之前的配置,我们会收到错误。相反,我们应该使用现有的 ApplicationContext
注册 Spring Security。例如,如果我们使用 Spring MVC,则我们的 SecurityWebApplicationInitializer
可能如下所示:
If we use Spring elsewhere in our application, we probably already have a WebApplicationInitializer
that is loading our Spring Configuration.
If we use the previous configuration, we would get an error.
Instead, we should register Spring Security with the existing ApplicationContext
.
For example, if we use Spring MVC, our SecurityWebApplicationInitializer
could look something like the following:
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
这只会为应用程序中的每个 URL 注册 springSecurityFilterChain
。之后,我们需要确保在现有的 ApplicationInitializer
中加载了 WebSecurityConfig
。例如,如果我们使用 Spring MVC,则将其添加到 getServletConfigClasses()
中:
This only registers the springSecurityFilterChain
for every URL in your application.
After that, we need to ensure that WebSecurityConfig
was loaded in our existing ApplicationInitializer
.
For example, if we use Spring MVC it is added in the getServletConfigClasses()
:
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
这样做的原因是 Spring Security 需要能够检查一些 Spring MVC 配置才能适当地配置 underlying request matchers,因此它们需要位于同一应用程序上下文中。将 Spring Security 放在 getRootConfigClasses
中会将其置于父级应用程序上下文中,该环境可能无法找到 Spring MVC 的 HandlerMappingIntrospector
。
The reason for this is that Spring Security needs to be able to inspect some Spring MVC configuration in order to appropriately configure underlying request matchers, so they need to be in the same application context.
Placing Spring Security in getRootConfigClasses
places it into a parent application context that may not be able to find Spring MVC’s HandlerMappingIntrospector
.
Configuring for Multiple Spring MVC Dispatchers
如果需要,可以将与 Spring MVC 无关的任何 Spring Security 配置放在不同的配置类中,如下所示:
If desired, any Spring Security configuration that is unrelated to Spring MVC may be placed in a different configuration class like so:
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
实例,并且不想同时对这两个实例进行重复的安全配置,这会很有帮助。
This can be helpful if you have multiple instances of AbstractAnnotationConfigDispatcherServletInitializer
and don’t want to duplicate the general security configuration across both of them.
HttpSecurity
到目前为止,我们的 <<`WebSecurityConfig`,jc-hello-wsca>> 只包含有关如何对用户进行身份验证的信息。Spring Security 如何知道我们希望要求所有用户进行身份验证?Spring Security 如何知道我们希望支持基于表单的身份验证?实际上,有一个配置类(称为 SecurityFilterChain
)正在幕后调用。它使用以下默认实现进行配置:
Thus far, our <<`WebSecurityConfig`,jc-hello-wsca>> contains only information about how to authenticate our users.
How does Spring Security know that we want to require all users to be authenticated?
How does Spring Security know we want to support form-based authentication?
Actually, there is a configuration class (called SecurityFilterChain
) that is being invoked behind the scenes.
It is configured with the following default implementation:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults())
.httpBasic(withDefaults());
return http.build();
}
默认配置(在前面的示例中显示):
The default configuration (shown in the preceding example):
-
Ensures that any request to our application requires the user to be authenticated
-
Lets users authenticate with form based login
-
Lets users authenticate with HTTP Basic authentication
请注意,此配置与 XML 命名空间配置并行:
Note that this configuration is parallels the XML Namespace configuration:
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
Multiple HttpSecurity Instances
我们可以配置多个 HttpSecurity
实例,就像我们在 XML 中可以有多个 <http>
块一样。关键是要注册多个 SecurityFilterChain
@Bean
。以下示例对以 /api/
开头的 URL 有不同的配置。
We can configure multiple HttpSecurity
instances just as we can have multiple <http>
blocks in XML.
The key is to register multiple SecurityFilterChain
@Bean`s.
The following example has a different configuration for URLs that start with `/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 | Configure Authentication as usual. |
2 | Create an instance of SecurityFilterChain that contains @Order to specify which SecurityFilterChain should be considered first. |
3 | The http.securityMatcher states that this HttpSecurity is applicable only to URLs that start with /api/ . |
4 | Create another instance of SecurityFilterChain .
If the URL does not start with /api/ , this configuration is used.
This configuration is considered after apiFilterChain , since it has an @Order value after 1 (no @Order defaults to last). |
Custom DSLs
你可以在 Spring Security 中提供你自己的自定义 DSL:
You can provide your own custom DSLs in Spring Security:
-
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()
}
}
}
这实际上是像 This is actually how methods like |
然后,你可以使用自定义 DSL:
You can then use the custom 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()
}
}
代码按照以下顺序调用:
The code is invoked in the following order:
-
Code in the
Config.filterChain
method is invoked -
Code in the
MyCustomDsl.init
method is invoked -
Code in the
MyCustomDsl.configure
method is invoked
如果你愿意,你可以使用 SpringFactories
让 HttpSecurity
默认添加 MyCustomDsl
。例如,你可以在类路径上创建一个名为 META-INF/spring.factories
的资源,内容如下:
If you want, you can have HttpSecurity
add MyCustomDsl
by default by using SpringFactories
.
For example, you can create a resource on the classpath named META-INF/spring.factories
with the following contents:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
你还可以显式禁用默认值:
You can also explicit disable the default:
-
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()
}
}
Post Processing Configured Objects
Spring Security 的 Java 配置不会公开所配置每个对象的每个属性。这会为大多数用户简化配置。毕竟,如果公开每个属性,用户可以使用标准 Bean 配置。
Spring Security’s Java configuration does not expose every property of every object that it configures. This simplifies the configuration for a majority of users. After all, if every property were exposed, users could use standard bean configuration.
虽然有很多理由不直接公开每个属性,但用户仍然可能需要更高级的配置选项。为了解决此问题,Spring Security 引入了 ObjectPostProcessor
的概念,它可用于修改或替换 Java 配置创建的许多 Object
实例。例如,要配置 FilterSecurityInterceptor
上的 filterSecurityPublishAuthorizationSuccess
属性,可以使用以下内容:
While there are good reasons to not directly expose every property, users may still need more advanced configuration options.
To address this issue, Spring Security introduces the concept of an ObjectPostProcessor
, which can be used to modify or replace many of the Object
instances created by the Java Configuration.
For example, to configure the filterSecurityPublishAuthorizationSuccess
property on FilterSecurityInterceptor
, you can use the following:
@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();
}