对于最新的稳定版本,请使用 Spring Security 6.3.1! |
对于最新的稳定版本,请使用 Spring Security 6.3.1! |
Spring Security 提供了许多与 Spring MVC 的可选集成。 本节将更详细地介绍集成。
@EnableWebMvcSecurity
从 Spring Security 4.0 开始,已弃用。
替换将确定根据类路径添加 Spring MVC 功能。@EnableWebMvcSecurity @EnableWebSecurity |
要启用 Spring Security 与 Spring MVC 的集成,请将注释添加到您的配置中。@EnableWebSecurity
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 上的匹配方式的深度集成。
这有助于确保安全规则与用于处理请求的逻辑匹配。MvcRequestMatcher
为了使用,您必须将 Spring Security 配置放在与 .
这是必要的,因为 Spring Security 需要由用于执行匹配的 Spring MVC 配置注册名称为 的 bean。MvcRequestMatcher
ApplicationContext
DispatcherServlet
MvcRequestMatcher
HandlerMappingIntrospector
mvcHandlerMappingIntrospector
例如,这意味着您应该将配置放在 .web.xml
DispatcherServlet.xml
<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 .WebSecurityConfiguration
DispatcherServlet
ApplicationContext
-
Java
-
Kotlin
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 方法安全性来提供授权规则。 |
请考虑按如下方式映射的控制器:
-
Java
-
Kotlin
@RequestMapping("/admin")
public String admin() {
@RequestMapping("/admin")
fun admin(): String {
如果我们想将对此控制器方法的访问限制为管理员用户,开发人员可以通过匹配以下内容来提供授权规则:HttpServletRequest
-
Java
-
Kotlin
@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 格式
<http>
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
</http>
无论使用哪种配置,URL 都要求经过身份验证的用户是管理员用户。
但是,根据我们的 Spring MVC 配置,URL 也将映射到我们的方法。
此外,根据我们的 Spring MVC 配置,URL 也将映射到我们的方法。/admin
/admin.html
admin()
/admin/
admin()
问题是我们的安全规则只是在保护.
我们可以为 Spring MVC 的所有排列添加额外的规则,但这将非常冗长和乏味。/admin
幸运的是,当使用 DSL 方法时,Spring Security 会自动创建一个 if 它检测到 Spring MVC 在类路径中可用。
因此,它将通过使用 Spring MVC 匹配 URL 来保护 Spring MVC 将匹配的相同 URL。requestMatchers
MvcRequestMatcher
使用 Spring MVC 时的一个常见要求是指定 servlet path 属性,为此您可以使用 创建共享同一 servlet 路径的多个实例:MvcRequestMatcher.Builder
MvcRequestMatcher
-
Java
-
Kotlin
@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 方法安全性来提供授权规则。 |
@AuthenticationPrincipal
Spring Security 提供了可以自动解析 Spring MVC 参数的当前内容。
通过使用,您将自动将其添加到您的 Spring MVC 配置中。
如果使用基于 XML 的配置,则必须自行添加。
例如:AuthenticationPrincipalArgumentResolver
Authentication.getPrincipal()
@EnableWebSecurity
<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 完全解耦。AuthenticationPrincipalArgumentResolver
考虑这样一种情况,即返回实现的自定义和您自己的 .可以使用以下代码访问当前经过身份验证的用户:UserDetailsService
Object
UserDetails
CustomUser
Object
CustomUser
-
Java
-
Kotlin
@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 开始,我们可以通过添加注解来更直接地解决参数。例如:
-
Java
-
Kotlin
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 的方法。
例如,它可能如下所示:CustomUser
UserDetailsService
Object
UserDetails
getCustomUser
CustomUser
-
Java
-
Kotlin
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 表达式来访问:CustomUser
Authentication.getPrincipal()
-
Java
-
Kotlin
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 来管理我们的用户,并且我们想要修改和保存当前用户的属性,则可以使用以下内容。
-
Java
-
Kotlin
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
@CurrentUser
重要的是要认识到,为了消除对 Spring Security 的依赖,使用应用程序将创建 .
此步骤不是严格要求的,但有助于将对 Spring Security 的依赖关系隔离到更中心的位置。@CurrentUser |
-
Java
-
Kotlin
@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 的依赖关系隔离到单个文件中。@CurrentUser
CustomUser
-
Java
-
Kotlin
@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。
例如,以下方法将自动调用其与创建时可用的 :SecurityContext
Thread
Callable
Callable
SecurityContext
Callable
-
Java
-
Kotlin
@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 集成了 .
用于处理 的 是 当时存在的 被调用。 |
没有与控制器返回的自动集成。
这是因为是由用户处理的,因此无法自动与之集成。
但是,您仍然可以使用并发支持来提供与 Spring Security 的透明集成。DeferredResult
DeferredResult
将 SecurityContext 关联到可调用对象的
从技术上讲,Spring Security 集成了 .
用于处理 的 是 当时存在的 被调用。 |
Spring MVC 和 CSRF 集成
自动包含令牌
Spring Security 将自动将 CSRF 令牌包含在使用 Spring MVC 表单标记的表单中。 例如,以下 JSP:
<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:
<!-- ... -->
<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 的配置,则必须自行添加。CsrfTokenArgumentResolver
CsrfToken
正确配置后,您可以将其公开给基于静态 HTML 的应用程序。CsrfTokenArgumentResolver
CsrfToken
-
Java
-
Kotlin
@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),则不应将其公开给任何外部域。CsrfToken
CsrfToken