此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1! |
Spring MVC 集成
Spring Security 提供了许多与 Spring MVC 的可选集成。 本节将更详细地介绍集成。
@EnableWebMvcSecurity
从 Spring Security 4.0 开始,已弃用。
替换是 ,它基于 Classpath 添加了 Spring MVC 功能。 |
要启用 Spring Security 与 Spring MVC 的集成,请将注释添加到您的配置中。@EnableWebSecurity
Spring Security 通过使用 Spring MVC 的 |
MvcRequestMatcher
Spring Security 提供了与 Spring MVC 在 URL 上的匹配方式的深度集成。
这有助于确保您的 Security 规则与用于处理请求的逻辑匹配。MvcRequestMatcher
要使用 ,必须将 Spring Security 配置放置在与 .
这是必要的,因为 Spring Security 希望 name 为 的 bean 由用于执行匹配的 Spring MVC 配置注册。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>
以下内容放置在 的 .WebSecurityConfiguration
ApplicationContext
DispatcherServlet
-
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("/")
}
}
我们始终建议您通过匹配 和 方法安全性来提供授权规则。 |
考虑一个映射如下的控制器:
-
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 检测到 Spring MVC 在 Classpath 中可用,它会自动创建一个。
因此,它将通过使用 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()
}
以下 XML 具有相同的效果:
<http request-matcher="mvc">
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
</http>
@AuthenticationPrincipal
Spring Security 提供了 ,它可以自动解析当前 for 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
考虑这样一种情况:自定义返回 implementation 和您自己的 .可以使用以下代码访问当前已验证用户的 :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 开始,我们可以通过添加 annotation 来更直接地解决参数:
-
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 ...
}
有时,您可能需要以某种方式转换主体。
例如,如果需要是 final,则无法扩展。
在这种情况下,可能会返回一个 ful,该 implementation 并提供了一个名为 to 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 表达式中引用 bean。 例如,如果我们使用 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)
// ...
}
我们可以通过在自己的 Comments 上进行元 Annotation 来进一步消除对 Spring Security 的依赖。
下一个示例演示了我们如何对名为 .@AuthenticationPrincipal
@CurrentUser
要消除对 Spring Security 的依赖,使用应用程序将创建 .
此步骤并非严格要求,但有助于将您对 Spring Security 的依赖隔离到更中心的位置。 |
-
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 ...
}
一旦它是元注释,您就可以使用参数化。
例如,假设您将 JWT 作为委托人,并且想要说出要检索的声明。 作为元注释,您可以这样做:
-
Java
-
Kotlin
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal(expression = "claims['sub']")
public @interface CurrentUser {}
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@AuthenticationPrincipal(expression = "claims['sub']")
annotation class CurrentUser
这已经相当强大了。
但是,它也仅限于检索索赔。sub
为了使其更灵活,首先发布 bean,如下所示:AnnotationTemplateExpressionDefaults
-
Java
-
Kotlin
-
Xml
@Bean
public AnnotationTemplateExpressionDefaults templateDefaults() {
return new AnnotationTemplateExpressionDeafults();
}
@Bean
fun templateDefaults(): AnnotationTemplateExpressionDefaults {
return AnnotationTemplateExpressionDeafults()
}
<b:bean name="annotationExpressionTemplateDefaults" class="org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults"/>
然后你可以提供一个参数,如下所示:@CurrentUser
-
Java
-
Kotlin
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal(expression = "claims['{claim}']")
public @interface CurrentUser {
String claim() default 'sub';
}
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@AuthenticationPrincipal(expression = "claims['{claim}']")
annotation class CurrentUser(val claim: String = "sub")
这将通过以下方式在应用程序集中提供更大的灵活性:
-
Java
-
Kotlin
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@CurrentUser("user_id") String userId) {
// .. find messages for this user and return them ...
}
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@CurrentUser("user_id") userId: String?): ModelAndView {
// .. find messages for this user and return them ...
}
Spring MVC 异步集成
Spring Web MVC 3.2+ 对异步请求处理有很好的支持。
无需额外配置,Spring Security 会自动设置调用控制器返回的 to。
例如,以下方法会自动使用创建时可用的 the 调用 its: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 与 Callable 的
从技术上讲,Spring Security 与 集成。
用于处理的 是 when 上存在的 。 |
没有与 controller 返回的 a 的自动集成。
这是因为它由用户处理,因此无法自动与它集成。
但是,您仍然可以使用并发支持来提供与 Spring Security 的透明集成。DeferredResult
DeferredResult
Spring MVC 和 CSRF 集成
Spring Security 与 Spring MVC 集成以添加 CSRF 保护。
自动 Token 包含
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 提供了 ,它可以自动解析当前 for 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