此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1spring-doc.cn

Spring MVC 集成

Spring Security 提供了许多与 Spring MVC 的可选集成。 本节将更详细地介绍集成。spring-doc.cn

@EnableWebMvcSecurity

从 Spring Security 4.0 开始,已弃用。 替换将决定根据 Classpath 添加 Spring MVC 功能。@EnableWebMvcSecurity@EnableWebSecurity

要启用 Spring Security 与 Spring MVC 的集成,请将注释添加到您的配置中。@EnableWebSecurityspring-doc.cn

Spring Security 使用 Spring MVC 的WebMvcConfigurer提供配置。 这意味着,如果您使用的是更高级的选项,例如直接与集成,则需要手动提供 Spring Security 配置。WebMvcConfigurationSupport

MvcRequestMatcher

Spring Security 提供了与 Spring MVC 在 URL 上的匹配方式的深度集成。 这有助于确保您的 Security 规则与用于处理请求的逻辑匹配。MvcRequestMatcherspring-doc.cn

为了使用,您必须将 Spring Security 配置放在与您的 . 这是必要的,因为 Spring Security 希望 name 为 的 bean 由用于执行匹配的 Spring MVC 配置注册。MvcRequestMatcherApplicationContextDispatcherServletMvcRequestMatcherHandlerMappingIntrospectormvcHandlerMappingIntrospectorspring-doc.cn

对于 a,这意味着您应该将配置放在 .web.xmlDispatcherServlet.xmlspring-doc.cn

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ -->
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring/*.xml</param-value>
</context-param>

<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <!-- Load from the ContextLoaderListener -->
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value></param-value>
  </init-param>
</servlet>

<servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

下面放在 s .WebSecurityConfigurationDispatcherServletApplicationContextspring-doc.cn

public class SecurityInitializer extends
    AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return null;
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[] { RootConfiguration.class,
        WebMvcConfiguration.class };
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }
}
class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
    override fun getRootConfigClasses(): Array<Class<*>>? {
        return null
    }

    override fun getServletConfigClasses(): Array<Class<*>> {
        return arrayOf(
            RootConfiguration::class.java,
            WebMvcConfiguration::class.java
        )
    }

    override fun getServletMappings(): Array<String> {
        return arrayOf("/")
    }
}

始终建议通过匹配 和 方法安全性来提供授权规则。HttpServletRequestspring-doc.cn

通过匹配来提供授权规则是很好的,因为它发生在代码路径的早期,有助于减少攻击面。 方法安全性可确保在有人绕过 Web 授权规则的情况下,您的应用程序仍然受到保护。 这就是所谓的深度防御HttpServletRequestspring-doc.cn

考虑一个映射如下的控制器:spring-doc.cn

@RequestMapping("/admin")
public String admin() {
@RequestMapping("/admin")
fun admin(): String {

如果我们想将对此控制器方法的访问限制为管理员用户,开发人员可以通过将 与以下内容匹配来提供授权规则:HttpServletRequestspring-doc.cn

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	http
		.authorizeHttpRequests((authorize) -> authorize
			.requestMatchers("/admin").hasRole("ADMIN")
		);
	return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        authorizeHttpRequests {
            authorize("/admin", hasRole("ADMIN"))
        }
    }
    return http.build()
}

或 XML 格式spring-doc.cn

<http>
	<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
</http>

无论使用哪种配置,URL 都将要求经过身份验证的用户是管理员用户。 但是,根据我们的 Spring MVC 配置,URL 也将映射到我们的方法。 此外,根据我们的 Spring MVC 配置,URL 也将映射到我们的方法。/admin/admin.htmladmin()/admin/admin()spring-doc.cn

问题在于,我们的安全规则只是保护 . 我们可以为 Spring MVC 的所有排列添加额外的规则,但这将非常冗长和乏味。/adminspring-doc.cn

幸运的是,当使用 DSL 方法时,如果 Spring Security 检测到 Spring MVC 在 Classpath 中可用,它会自动创建一个。 因此,它将通过使用 Spring MVC 匹配 URL 来保护 Spring MVC 将匹配的相同 URL。requestMatchersMvcRequestMatcherspring-doc.cn

使用 Spring MVC 时的一个常见要求是指定 servlet path 属性,为此,你可以使用 来创建共享同一 servlet 路径的多个实例:MvcRequestMatcher.BuilderMvcRequestMatcherspring-doc.cn

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
	MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector).servletPath("/path");
	http
		.authorizeHttpRequests((authorize) -> authorize
			.requestMatchers(mvcMatcherBuilder.pattern("/admin")).hasRole("ADMIN")
			.requestMatchers(mvcMatcherBuilder.pattern("/user")).hasRole("USER")
		);
	return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain {
    val mvcMatcherBuilder = MvcRequestMatcher.Builder(introspector)
    http {
        authorizeHttpRequests {
            authorize(mvcMatcherBuilder.pattern("/admin"), hasRole("ADMIN"))
            authorize(mvcMatcherBuilder.pattern("/user"), hasRole("USER"))
        }
    }
    return http.build()
}

@AuthenticationPrincipal

Spring Security 提供了可以自动解析当前 Spring MVC 参数的功能。 通过使用,你会自动将其添加到你的 Spring MVC 配置中。 如果使用基于 XML 的配置,则必须自行添加此配置。 例如:AuthenticationPrincipalArgumentResolverAuthentication.getPrincipal()@EnableWebSecurityspring-doc.cn

<mvc:annotation-driven>
		<mvc:argument-resolvers>
				<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
		</mvc:argument-resolvers>
</mvc:annotation-driven>

正确配置后,您可以在 Spring MVC 层中与 Spring Security 完全解耦。AuthenticationPrincipalArgumentResolverspring-doc.cn

考虑这样一种情况:返回 that implements 和您自己的 .可以使用以下代码访问当前经过身份验证的用户的 :UserDetailsServiceObjectUserDetailsCustomUserObjectCustomUserspring-doc.cn

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser() {
	Authentication authentication =
	SecurityContextHolder.getContext().getAuthentication();
	CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal();

	// .. find messages for this user and return them ...
}
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(): ModelAndView {
    val authentication: Authentication = SecurityContextHolder.getContext().authentication
    val custom: CustomUser? = if (authentication as CustomUser == null) null else authentication.principal

    // .. find messages for this user and return them ...
}

从 Spring Security 3.2 开始,我们可以通过添加 Comments 来更直接地解决参数。例如:spring-doc.cn

import org.springframework.security.core.annotation.AuthenticationPrincipal;

// ...

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

	// .. find messages for this user and return them ...
}
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal customUser: CustomUser?): ModelAndView {

    // .. find messages for this user and return them ...
}

有时可能需要以某种方式转换主体。 例如,如果需要是 final,则不能扩展。 在这种情况下,might 返回一个实现并提供名为 access 的方法。 例如,它可能看起来像:CustomUserUserDetailsServiceObjectUserDetailsgetCustomUserCustomUserspring-doc.cn

public class CustomUserUserDetails extends User {
		// ...
		public CustomUser getCustomUser() {
				return customUser;
		}
}
class CustomUserUserDetails(
    username: String?,
    password: String?,
    authorities: MutableCollection<out GrantedAuthority>?
) : User(username, password, authorities) {
    // ...
    val customUser: CustomUser? = null
}

然后,我们可以使用用作根对象的 SpEL 表达式来访问它:CustomUserAuthentication.getPrincipal()spring-doc.cn

import org.springframework.security.core.annotation.AuthenticationPrincipal;

// ...

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") CustomUser customUser) {

	// .. find messages for this user and return them ...
}
import org.springframework.security.core.annotation.AuthenticationPrincipal

// ...

@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") customUser: CustomUser?): ModelAndView {

    // .. find messages for this user and return them ...
}

我们也可以在 SPEL 表达式中引用 Beans。 例如,如果我们使用 JPA 来管理我们的 User,并且我们想修改并保存当前 User 的属性,则可以使用以下内容。spring-doc.cn

import org.springframework.security.core.annotation.AuthenticationPrincipal;

// ...

@PutMapping("/users/self")
public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") CustomUser attachedCustomUser,
		@RequestParam String firstName) {

	// change the firstName on an attached instance which will be persisted to the database
	attachedCustomUser.setFirstName(firstName);

	// ...
}
import org.springframework.security.core.annotation.AuthenticationPrincipal

// ...

@PutMapping("/users/self")
open fun updateName(
    @AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") attachedCustomUser: CustomUser,
    @RequestParam firstName: String?
): ModelAndView {

    // change the firstName on an attached instance which will be persisted to the database
    attachedCustomUser.setFirstName(firstName)

    // ...
}

我们可以通过在自己的 Comments 上添加 meta Comments 来进一步消除对 Spring Security 的依赖。 下面我们将演示如何对名为 .@AuthenticationPrincipal@CurrentUserspring-doc.cn

重要的是要认识到,为了消除对 Spring Security 的依赖,消费应用程序将创建 . 此步骤并非严格要求,但有助于将您对 Spring Security 的依赖关系隔离到更中心的位置。@CurrentUser
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {}
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@AuthenticationPrincipal
annotation class CurrentUser

现在已经指定了,我们可以使用它来发出信号来解析当前经过身份验证的用户。 我们还将对 Spring Security 的依赖隔离到单个文件中。@CurrentUserCustomUserspring-doc.cn

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {

	// .. find messages for this user and return them ...
}
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@CurrentUser customUser: CustomUser?): ModelAndView {

    // .. find messages for this user and return them ...
}

Spring MVC 异步集成

Spring Web MVC 3.2+ 对异步请求处理有很好的支持。 无需额外配置,Spring Security 将自动设置调用控制器返回的 to。 例如,以下方法将自动使用创建时可用的 来调用它:SecurityContextThreadCallableCallableSecurityContextCallablespring-doc.cn

@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {

return new Callable<String>() {
	public Object call() throws Exception {
	// ...
	return "someView";
	}
};
}
@RequestMapping(method = [RequestMethod.POST])
open fun processUpload(file: MultipartFile?): Callable<String> {
    return Callable {
        // ...
        "someView"
    }
}
将 SecurityContext 与 Callable 的

从技术上讲,Spring Security 与 集成。 用于处理的 是 在调用时存在的 。WebAsyncManagerSecurityContextCallableSecurityContextSecurityContextHolderstartCallableProcessingspring-doc.cn

没有与 controller 返回的 a 的自动集成。 这是因为它由用户处理,因此无法自动与它集成。 但是,您仍然可以使用并发支持来提供与 Spring Security 的透明集成。DeferredResultDeferredResultspring-doc.cn

Spring MVC 和 CSRF 集成

自动 Token 包含

Spring Security 将自动将 CSRF 令牌包含在使用 Spring MVC 表单标签的表单中。 例如,以下 JSP:spring-doc.cn

<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
	xmlns:c="http://java.sun.com/jsp/jstl/core"
	xmlns:form="http://www.springframework.org/tags/form" version="2.0">
	<jsp:directive.page language="java" contentType="text/html" />
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
	<!-- ... -->

	<c:url var="logoutUrl" value="/logout"/>
	<form:form action="${logoutUrl}"
		method="post">
	<input type="submit"
		value="Log out" />
	<input type="hidden"
		name="${_csrf.parameterName}"
		value="${_csrf.token}"/>
	</form:form>

	<!-- ... -->
</html>
</jsp:root>

将输出类似于以下内容的 HTML:spring-doc.cn

<!-- ... -->

<form action="/context/logout" method="post">
<input type="submit" value="Log out"/>
<input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/>
</form>

<!-- ... -->

解析 CsrfToken

Spring Security 提供了可以自动解析当前 Spring MVC 参数的功能。 通过使用 @EnableWebSecurity 你会自动将其添加到你的 Spring MVC 配置中。 如果使用基于 XML 的配置,则必须自行添加此配置。CsrfTokenArgumentResolverCsrfTokenspring-doc.cn

正确配置后,您可以向基于 HTML 的静态应用程序公开 。CsrfTokenArgumentResolverCsrfTokenspring-doc.cn

@RestController
public class CsrfController {

	@RequestMapping("/csrf")
	public CsrfToken csrf(CsrfToken token) {
		return token;
	}
}
@RestController
class CsrfController {
    @RequestMapping("/csrf")
    fun csrf(token: CsrfToken): CsrfToken {
        return token
    }
}

对其他域保密很重要。 这意味着,如果您使用的是跨域共享 (CORS),则不应将其暴露给任何外部域。CsrfTokenCsrfTokenspring-doc.cn