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

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

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

@EnableWebMvcSecurity

从 Spring Security 4.0 开始,已弃用。 替换是 ,它基于类路径添加了 Spring MVC 功能。@EnableWebMvcSecurity@EnableWebSecuritySpring中文文档

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

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

从 Spring Security 4.0 开始,已弃用。 替换是 ,它基于类路径添加了 Spring MVC 功能。@EnableWebMvcSecurity@EnableWebSecuritySpring中文文档

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

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>

以下放在 .WebSecurityConfigurationApplicationContextDispatcherServletSpring中文文档

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

以下 XML 具有相同的效果:Spring中文文档

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

我们始终建议您通过匹配 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中文文档

考虑这样一种情况:自定义返回 an that implements 和 your own .可以使用以下代码访问当前经过身份验证的用户: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 ...
}

有时,您可能需要以某种方式转换主体。 例如,如果需要成为最终版本,则无法扩展。 在这种情况下,可能会返回一个实现并提供名为 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 表达式中引用 bean。 例如,如果我们使用 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 的依赖关系隔离到更中心的位置。@CurrentUserSpring中文文档

@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 的依赖关系隔离到更中心的位置。@CurrentUserSpring中文文档

Spring MVC 异步集成

Spring Web MVC 3.2+ 对异步请求处理有很好的支持。 无需其他配置,Spring Security 会自动设置调用控制器返回的 to。 例如,以下方法会自动调用创建时可用的 that: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 与 Spring MVC 集成以添加 CSRF 保护。Spring中文文档

自动包含令牌

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