# Java 配置

在 Spring 3.1 中,对Java 配置 (opens new window)的一般支持被添加到 Spring 框架中。自 Spring Security3.2 以来,已经有了 Spring Security Java 配置支持,它使用户能够轻松地配置 Spring 安全性,而无需使用任何 XML。

如果你熟悉安全名称空间配置,那么你应该会发现它与安全 Java 配置支持之间有很多相似之处。

Spring 安全性提供了大量的示例应用程序 (opens new window),其中演示了 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;
	}
}

这种配置实际上没有太多的东西,但它做了很多事情。你可以找到以下功能的摘要:

# WebApplicationInitializer

下一步是在 WAR 中注册springSecurityFilterChain。这可以在 Servlet 3.0+ 环境中使用Spring’s WebApplicationInitializer support (opens new window)在 Java 配置中完成。 Spring Security 提供了一个基类AbstractSecurityWebApplicationInitializer,它将确保springSecurityFilterChain为你注册。我们使用AbstractSecurityWebApplicationInitializer的方式有所不同,这取决于我们是否已经在使用 Spring 或者 Spring 安全性是我们应用程序中唯一的 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的 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仅包含有关如何对用户进行身份验证的信息。 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 创建一个包含@OrderWebSecurityConfigurerAdapter实例,以指定应该首先考虑哪个WebSecurityConfigurerAdapter
3 http.antMatcher声明此HttpSecurity将仅适用于以/api/开头的 URL
4 创建WebSecurityConfigurerAdapter的另一个实例。
如果 URL 不以/api/开始,将使用此配置。
此配置在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()
			...;
	}
}

该代码按以下顺序调用:

  • 调用Configs configure 方法中的代码

  • 调用MyCustomDsls init 方法中的代码

  • 调用MyCustomDsls 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 TaglibKotlin Configuration