对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

Spring Security 提供了许多与 Spring MVC 的可选集成。 本节将更详细地介绍集成。Spring中文文档

@EnableWebMvcSecurity

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

要启用 Spring Security 与 Spring MVC 的集成,请将注释添加到您的配置中。@EnableWebSecuritySpring中文文档

Spring Security 使用 Spring MVC 的 WebMvcConfigurer 提供配置。 这意味着,如果您使用更高级的选项,例如直接集成,则需要手动提供Spring Security配置。WebMvcConfigurationSupport
从 Spring Security 4.0 开始,已弃用。 替换将确定根据类路径添加 Spring MVC 功能。@EnableWebMvcSecurity@EnableWebSecurity
Spring Security 使用 Spring MVC 的 WebMvcConfigurer 提供配置。 这意味着,如果您使用更高级的选项,例如直接集成,则需要手动提供Spring Security配置。WebMvcConfigurationSupport

MvcRequestMatcher

Spring Security 提供了与 Spring MVC 在 URL 上的匹配方式的深度集成。 这有助于确保安全规则与用于处理请求的逻辑匹配。MvcRequestMatcherSpring中文文档

为了使用,您必须将 Spring Security 配置放在与 . 这是必要的,因为 Spring Security 需要由用于执行匹配的 Spring MVC 配置注册名称为 的 bean。MvcRequestMatcherApplicationContextDispatcherServletMvcRequestMatcherHandlerMappingIntrospectormvcHandlerMappingIntrospectorSpring中文文档

例如,这意味着您应该将配置放在 .web.xmlDispatcherServlet.xmlSpring中文文档

<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中文文档

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("/")
    }
}

始终建议通过匹配 and 方法安全性来提供授权规则。HttpServletRequestSpring中文文档

通过匹配 on 提供授权规则是很好的,因为它发生在代码路径的早期,有助于减少攻击面。 方法安全性可确保如果有人绕过了 Web 授权规则,您的应用程序仍然是安全的。 这就是所谓的纵深防御HttpServletRequestSpring中文文档

请考虑按如下方式映射的控制器:Spring中文文档

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

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

@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中文文档

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

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

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

幸运的是,当使用 DSL 方法时,Spring Security 会自动创建一个 if 它检测到 Spring MVC 在类路径中可用。 因此,它将通过使用 Spring MVC 匹配 URL 来保护 Spring MVC 将匹配的相同 URL。requestMatchersMvcRequestMatcherSpring中文文档

使用 Spring MVC 时的一个常见要求是指定 servlet path 属性,为此您可以使用 创建共享同一 servlet 路径的多个实例:MvcRequestMatcher.BuilderMvcRequestMatcherSpring中文文档

@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()
}

始终建议通过匹配 and 方法安全性来提供授权规则。HttpServletRequestSpring中文文档

通过匹配 on 提供授权规则是很好的,因为它发生在代码路径的早期,有助于减少攻击面。 方法安全性可确保如果有人绕过了 Web 授权规则,您的应用程序仍然是安全的。 这就是所谓的纵深防御HttpServletRequestSpring中文文档

@AuthenticationPrincipal

Spring Security 提供了可以自动解析 Spring MVC 参数的当前内容。 通过使用,您将自动将其添加到您的 Spring MVC 配置中。 如果使用基于 XML 的配置,则必须自行添加。 例如:AuthenticationPrincipalArgumentResolverAuthentication.getPrincipal()@EnableWebSecuritySpring中文文档

<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中文文档

考虑这样一种情况,即返回实现的自定义和您自己的 .可以使用以下代码访问当前经过身份验证的用户:UserDetailsServiceObjectUserDetailsCustomUserObjectCustomUserSpring中文文档

@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 开始,我们可以通过添加注解来更直接地解决参数。例如:Spring中文文档

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 ...
}

有时可能需要以某种方式转换主体。 例如,如果需要最终版本,则无法扩展。 在这种情况下,might 返回一个实现并提供名为 access 的方法。 例如,它可能如下所示:CustomUserUserDetailsServiceObjectUserDetailsgetCustomUserCustomUserSpring中文文档

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中文文档

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 来管理我们的用户,并且我们想要修改和保存当前用户的属性,则可以使用以下内容。Spring中文文档

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)

    // ...
}

我们可以通过对自己的注解进行元注解来进一步消除对 Spring Security 的依赖。 下面我们将演示如何在名为 的注释上执行此操作。@AuthenticationPrincipal@CurrentUserSpring中文文档

重要的是要认识到,为了消除对 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中文文档

@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 Security 的依赖,使用应用程序将创建 . 此步骤不是严格要求的,但有助于将对 Spring Security 的依赖关系隔离到更中心的位置。@CurrentUser

Spring MVC 异步集成

Spring Web MVC 3.2+ 对异步请求处理有很好的支持。 无需其他配置,Spring Security 将自动设置调用控制器返回的 to。 例如,以下方法将自动调用其与创建时可用的 :SecurityContextThreadCallableCallableSecurityContextCallableSpring中文文档

@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 关联到可调用对象的

从技术上讲,Spring Security 集成了 . 用于处理 的 是 当时存在的 被调用。WebAsyncManagerSecurityContextCallableSecurityContextSecurityContextHolderstartCallableProcessingSpring中文文档

没有与控制器返回的自动集成。 这是因为是由用户处理的,因此无法自动与之集成。 但是,您仍然可以使用并发支持来提供与 Spring Security 的透明集成。DeferredResultDeferredResultSpring中文文档

将 SecurityContext 关联到可调用对象的

从技术上讲,Spring Security 集成了 . 用于处理 的 是 当时存在的 被调用。WebAsyncManagerSecurityContextCallableSecurityContextSecurityContextHolderstartCallableProcessingSpring中文文档

Spring MVC 和 CSRF 集成

自动包含令牌

Spring Security 将自动将 CSRF 令牌包含在使用 Spring MVC 表单标记的表单中。 例如,以下 JSP:Spring中文文档

<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中文文档

<!-- ... -->

<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中文文档

正确配置后,您可以将其公开给基于静态 HTML 的应用程序。CsrfTokenArgumentResolverCsrfTokenSpring中文文档

@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中文文档