# Java 配置

在 Spring 3.1 中，对[Java 配置](https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/beans.html#beans-java)的一般支持被添加到 Spring 框架中。自 Spring Security3.2 以来，已经有了 Spring Security Java 配置支持，它使用户能够轻松地配置 Spring 安全性，而无需使用任何 XML。

如果你熟悉[安全名称空间配置](xml-namespace.html#ns-config)，那么你应该会发现它与安全 Java 配置支持之间有很多相似之处。

|   |Spring 安全性提供了[大量的示例应用程序](https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration)，其中演示了 Spring 安全性 Java 配置的使用。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## Hello Web 安全 Java 配置

第一步是创建我们的 Spring 安全 Java 配置。该配置创建了一个名为`springSecurityFilterChain`的 Servlet 过滤器，该过滤器负责应用程序中的所有安全性（保护应用程序的 URL，验证提交的用户名和密码，重定向到表单中的日志，等等）。你可以在下面找到 Spring 安全性 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.*;

@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 进行身份验证

* 为你生成一个登录表单

* 允许使用**用户 Name***User*和**密码***密码*的用户使用基于表单的身份验证进行身份验证

* 允许用户注销

* [CSRF 攻击](https://en.wikipedia.org/wiki/Cross-site_request_forgery)预防

* [Session Fixation](https://en.wikipedia.org/wiki/Session_fixation)保护

* 安全报头集成

  * [HTTP 严格的传输安全](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)用于安全请求

  * [X-Content-Type-Options](https://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).ASPX）集成

  * 缓存控制（可以稍后由应用程序重写，以允许缓存静态资源）

  * [X-XSS-保护](https://msdn.microsoft.com/en-us/library/dd565647(v=vs.85).ASPX）积分

  * x-frame-options 集成，帮助防止[点击劫持](https://en.wikipedia.org/wiki/Clickjacking)

* 与以下 Servlet API 方法集成

  * [HttpServletRequest#getRemoteUser（）](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser())

  * [HttpServletRequest#getUserPrincipal（）](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal())

  * [HttpServletRequest#isUserInRole](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#isUserInRole(java.lang.String))

  * [HttpServletRequest#login（java.lang.string，java.lang.string）](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login(java.lang.String,%20java.lang.String))

  * [HttpServletRequest#logout（）](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#logout())

### WebApplicationInitializer

下一步是在 WAR 中注册`springSecurityFilterChain`。这可以在 Servlet 3.0+ 环境中使用[Spring’s WebApplicationInitializer support](https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-container-config)在 Java 配置中完成。 Spring Security 提供了一个基类`AbstractSecurityWebApplicationInitializer`，它将确保`springSecurityFilterChain`为你注册。我们使用`AbstractSecurityWebApplicationInitializer`的方式有所不同，这取决于我们是否已经在使用 Spring 或者 Spring 安全性是我们应用程序中唯一的 Spring 组件。

* [不存在的安全 WebApplicationInitializer Spring](#abstractsecuritywebapplicationinitializer-without-existing-spring)-如果你尚未使用 Spring，请使用以下说明

* [带有 Spring MVC 的安全 WebApplicationInitializer](#abstractsecuritywebapplicationinitializer-with-spring-mvc)-如果你已经在使用这些指令 Spring

### AbstractSecurityWebApplicationInitializer without Existing Spring

如果不使用 Spring 或 Spring MVC，则需要将`WebSecurityConfig`传递到超类中，以确保拾取配置。你可以在下面找到一个例子：

```
import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
	extends AbstractSecurityWebApplicationInitializer {

	public SecurityWebApplicationInitializer() {
		super(WebSecurityConfig.class);
	}
}
```

`SecurityWebApplicationInitializer`将执行以下操作：

* 为应用程序中的每个 URL 自动注册 SpringSecurityFilterchain 过滤器

* 添加一个加载[WebSecurityConfig](#jc-hello-wsca)的 ContextLoaderListener。

### AbstractSecurityWebApplicationInitializer with Spring MVC

如果我们在应用程序的其他地方使用 Spring，我们可能已经有一个`WebApplicationInitializer`正在加载我们的 Spring 配置。如果我们使用以前的配置，我们将得到一个错误。相反，我们应该使用现有的`ApplicationContext`注册 Spring 安全性。例如，如果我们使用 Spring MVC，我们的`SecurityWebApplicationInitializer`将如下所示：

```
import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
	extends AbstractSecurityWebApplicationInitializer {

}
```

这只会为应用程序中的每个 URL 注册 SpringSecurityFilterchain 过滤器。在此之后，我们将确保`WebSecurityConfig`已加载到我们现有的应用程序初始化器中。例如，如果我们使用 Spring MVC，它将被添加到`getRootConfigClasses()`中

```
public class MvcWebApplicationInitializer extends
		AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[] { WebSecurityConfig.class };
	}

	// ... other overrides ...
}
```

## HttpSecurity

到目前为止，我们的[WebSecurityConfig](#jc-hello-wsca)仅包含有关如何对用户进行身份验证的信息。 Spring 安全性如何知道我们希望要求对所有用户进行身份验证？ Spring 安全性如何知道我们希望支持基于表单的身份验证？实际上，有一个正在幕后调用的配置类叫做`WebSecurityConfigurerAdapter`。它有一个名为`configure`的方法，其默认实现如下：

```
protected void configure(HttpSecurity http) throws Exception {
	http
		.authorizeRequests(authorize -> authorize
			.anyRequest().authenticated()
		)
		.formLogin(withDefaults())
		.httpBasic(withDefaults());
}
```

上面的默认配置：

* 确保对我们的应用程序的任何请求都需要对用户进行身份验证。

* 允许用户通过基于表单的登录进行身份验证

* 允许用户使用 HTTP Basic 身份验证进行身份验证

你将注意到此配置与 XML 名称空间配置非常相似：

```
<http>
	<intercept-url pattern="/**" access="authenticated"/>
	<form-login />
	<http-basic />
</http>
```

## 多重 HttpSecurity

我们可以配置多个 HttpSecurity 实例，就像我们可以配置多个`<http>`块一样。关键是将`WebSecurityConfigurerAdapter`多次扩展。例如，下面是一个以`/api/`开头的不同 URL 配置的示例。

```
@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;
	}

	@Configuration
	@Order(1)                                                        (2)
	public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
		protected void configure(HttpSecurity http) throws Exception {
			http
				.antMatcher("/api/**")                               (3)
				.authorizeHttpRequests(authorize -> authorize
					.anyRequest().hasRole("ADMIN")
			    )
				.httpBasic(withDefaults());
		}
	}

	@Configuration                                                   (4)
	public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

		@Override
		protected void configure(HttpSecurity http) throws Exception {
			http
				.authorizeHttpRequests(authorize -> authorize
					.anyRequest().authenticated()
				)
				.formLogin(withDefaults());
		}
	}
}
```

|**1**|将身份验证配置为常规身份验证|
|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|创建一个包含`@Order`的`WebSecurityConfigurerAdapter`实例，以指定应该首先考虑哪个`WebSecurityConfigurerAdapter`。|
|**3**|`http.antMatcher`声明此`HttpSecurity`将仅适用于以`/api/`开头的 URL|
|**4**|创建`WebSecurityConfigurerAdapter`的另一个实例。<br/>如果 URL 不以`/api/`开始，将使用此配置。<br/>此配置在`ApiWebSecurityConfigurationAdapter`之后被考虑，因为它在`1`之后有一个`@Order`值（不`@Order`默认为后）。|

## 定制 DSL

你可以在 Spring Security 中提供自己的自定义 DSL。例如，你可能有这样的东西：

```
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();
	}
}
```

|   |这实际上是`HttpSecurity.authorizeRequests()`之类的方法的实现方式。|
|---|-------------------------------------------------------------------------------------|

然后可以这样使用定制的 DSL:

```
@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.apply(customDsl())
				.flag(true)
				.and()
			...;
	}
}
```

该代码按以下顺序调用：

* 调用`Config`s configure 方法中的代码

* 调用`MyCustomDsl`s init 方法中的代码

* 调用`MyCustomDsl`s configure 方法中的代码

如果需要，可以使用`SpringFactories`，在默认情况下将`WebSecurityConfigurerAdapter`添加到`MyCustomDsl`。例如，你将在 Classpath 上创建一个名为`META-INF/ Spring.工厂`的资源，其内容如下：

META-INF/spring.factories

```
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
```

希望禁用默认值的用户可以显式地这样做。

```
@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.apply(customDsl()).disable()
			...;
	}
}
```

## 已配置对象的后处理

Spring Security 的 Java 配置不会公开它配置的每个对象的每个属性。这简化了大多数用户的配置。毕竟，如果每个属性都被公开，那么用户可以使用标准的 Bean 配置。

尽管有充分的理由不直接公开每个属性，但用户可能仍然需要更高级的配置选项。 Spring 为了解决这一安全问题，引入了`ObjectPostProcessor`的概念，该概念可用于修改或替换由 Java 配置创建的许多对象实例。例如，如果要在`FilterSecurityInterceptor`上配置`filterSecurityPublishAuthorizationSuccess`属性，可以使用以下方法：

```
@Override
protected void configure(HttpSecurity http) throws Exception {
	http
		.authorizeRequests(authorize -> authorize
			.anyRequest().authenticated()
			.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
				public <O extends FilterSecurityInterceptor> O postProcess(
						O fsi) {
					fsi.setPublishAuthorizationSuccess(true);
					return fsi;
				}
			})
		);
}
```

[JSP Taglib](../integrations/jsp-taglibs.html)[Kotlin Configuration](kotlin.html)

