# Spring MVC集成
Spring 安全性提供了与 Spring MVC的许多可选集成。本节将进一步详细介绍集成。
## @enableWebMVCSecurity
| |在 Spring Security4.0中,`@EnableWebMvcSecurity`已被弃用。
替换为`@EnableWebSecurity`,这将决定在 Classpath 的基础上添加 Spring MVC特性。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
要启用 Spring 与 Spring MVC的安全集成,请在配置中添加`@EnableWebSecurity`注释。
| |Spring 安全性提供了使用 Spring MVC的的配置。这意味着,如果你正在使用更高级的选项,比如直接与集成,那么你将需要手动提供 Spring 安全性配置。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## MVCrequestMatcher
Spring 安全性提供了与 Spring MVC如何在具有`MvcRequestMatcher`的URL上匹配的深度集成。这有助于确保你的安全规则与用于处理请求的逻辑相匹配。
为了使用`MvcRequestMatcher`,你必须将 Spring 安全配置放在与你的`DispatcherServlet`相同的`ApplicationContext`中。这是必要的,因为 Spring Security的`MvcRequestMatcher`期望名称为`HandlerMappingIntrospector` Bean 的`mvcHandlerMappingIntrospector`被用于执行匹配的 Spring MVC配置注册。
对于`web.xml`,这意味着你应该将配置放在`DispatcherServlet.xml`中。
```
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
/WEB-INF/spring/*.xml
spring
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
spring
/
```
下面`WebSecurityConfiguration`中置于`DispatcherServlet`s`ApplicationContext`中。
Java
```
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[] { "/" };
}
}
```
Kotlin
```
class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array>? {
return null
}
override fun getServletConfigClasses(): Array> {
return arrayOf(
RootConfiguration::class.java,
WebMvcConfiguration::class.java
)
}
override fun getServletMappings(): Array {
return arrayOf("/")
}
}
```
| |始终建议通过匹配`HttpServletRequest`和方法安全性来提供授权规则。
通过匹配`HttpServletRequest`来提供授权规则是很好的,因为它在代码路径中很早就发生了,并且有助于减少
方法安全性确保如果有人绕过了Web授权规则,那么你的应用程序仍然是安全的。
这就是所谓的[纵深防御](https://en.wikipedia.org/wiki/Defense_in_depth_(computing))|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
考虑一个映射如下的控制器:
Java
```
@RequestMapping("/admin")
public String admin() {
```
Kotlin
```
@RequestMapping("/admin")
fun admin(): String {
```
如果我们希望将对此控制器方法的访问限制为管理用户,那么开发人员可以通过在`HttpServletRequest`上匹配以下内容来提供授权规则:
Java
```
protected configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers("/admin").hasRole("ADMIN")
);
}
```
Kotlin
```
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(AntPathRequestMatcher("/admin"), hasRole("ADMIN"))
}
}
}
```
或在XML中
```
```
对于任一种配置,URL`/admin`将要求经过身份验证的用户是管理用户。然而,根据我们的 Spring MVC配置,URL`/admin.html`也将映射到我们的`admin()`方法。此外,根据我们的 Spring MVC配置,URL`/admin/`也将映射到我们的`admin()`方法。
问题在于,我们的安全规则仅保护`/admin`。我们可以为 Spring MVC的所有排列添加额外的规则,但这将是非常冗长和乏味的。
相反,我们可以利用 Spring security的`MvcRequestMatcher`。下面的配置将通过使用 Spring MVC在URL上进行匹配来保护与 Spring MVC匹配的相同的URL。
Java
```
protected configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.mvcMatchers("/admin").hasRole("ADMIN")
);
}
```
Kotlin
```
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize("/admin", hasRole("ADMIN"))
}
}
}
```
或在XML中
```
```
## @AuthenticationPrincipal
Spring 安全性提供了`AuthenticationPrincipalArgumentResolver`,它可以自动解析 Spring MVC参数的当前`Authentication.getPrincipal()`。通过使用`@EnableWebSecurity`,你将自动将其添加到 Spring MVC配置中。如果使用基于XML的配置,则必须自己添加该配置。例如:
```
```
一旦`AuthenticationPrincipalArgumentResolver`被正确配置,你就可以在 Spring MVC层中与 Spring 安全性完全解耦。
考虑一种情况,其中一个自定义`UserDetailsService`返回一个`Object`,它实现`UserDetails`和你自己的`CustomUser``Object`。可以使用以下代码访问当前经过身份验证的用户的`CustomUser`:
Java
```
@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 ...
}
```
Kotlin
```
@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 Security3.2开始,我们可以通过添加一个注释来更直接地解决该参数。例如:
Java
```
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {
// .. find messages for this user and return them ...
}
```
Kotlin
```
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
```
有时可能有必要以某种方式转换本金。例如,如果`CustomUser`需要是最终的,则不能对其进行扩展。在这种情况下,`UserDetailsService`可能返回一个`Object`,它实现`UserDetails`并提供一个名为`getCustomUser`的方法来访问`CustomUser`。例如,它可能看起来像:
Java
```
public class CustomUserUserDetails extends User {
// ...
public CustomUser getCustomUser() {
return customUser;
}
}
```
Kotlin
```
class CustomUserUserDetails(
username: String?,
password: String?,
authorities: MutableCollection?
) : User(username, password, authorities) {
// ...
val customUser: CustomUser? = null
}
```
然后,我们可以使用一个[Spel表达式](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html)访问`CustomUser`,它使用`Authentication.getPrincipal()`作为根对象:
Java
```
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 ...
}
```
Kotlin
```
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
```
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);
// ...
}
```
Kotlin
```
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)
// ...
}
```
通过在我们自己的注释上使用`@AuthenticationPrincipal`元注释,我们可以进一步消除对 Spring 安全性的依赖。下面我们将演示如何在名为`@CurrentUser`的注释上实现这一点。
| |重要的是要认识到,为了消除对 Spring 安全性的依赖关系,将创建`@CurrentUser`的是消耗应用程序。
这一步骤并不是严格必需的,但有助于将你对 Spring 安全性的依赖关系隔离到一个更中心的位置。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Java
```
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {}
```
Kotlin
```
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@AuthenticationPrincipal
annotation class CurrentUser
```
现在已经指定了`@CurrentUser`,我们可以使用它来发送信号,以解析当前已验证用户的`CustomUser`。我们还将对 Spring 安全性的依赖隔离到一个文件中。
Java
```
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {
// .. find messages for this user and return them ...
}
```
Kotlin
```
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@CurrentUser customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
```
## Spring MVC异步集成
Spring Web MVC3.2+对[异步请求处理](https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-ann-async)具有出色的支持。 Spring 在没有额外配置的情况下,Security将自动将`SecurityContext`设置为调用控制器返回的`Thread`的`Callable`。例如,下面的方法将自动使用创建`Callable`时可用的`SecurityContext`调用其`Callable`:
Java
```
@RequestMapping(method=RequestMethod.POST)
public Callable processUpload(final MultipartFile file) {
return new Callable() {
public Object call() throws Exception {
// ...
return "someView";
}
};
}
```
Kotlin
```
@RequestMapping(method = [RequestMethod.POST])
open fun processUpload(file: MultipartFile?): Callable {
return Callable {
// ...
"someView"
}
}
```
| |将SecurityContext与Callable的
更严格地说, Spring Security与`WebAsyncManager`集成。
用于处理`Callable`的`SecurityContext`是在调用`SecurityContextHolder`时存在于`SecurityContextHolder`上的`SecurityContext`。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
没有控制器返回的`DeferredResult`的自动集成。这是因为`DeferredResult`是由用户处理的,因此无法自动与其集成。然而,你仍然可以使用[并发支持](../../features/integrations/concurrency.html#concurrency)来提供具有 Spring 安全性的透明集成。
## Spring MVC和CSRF集成
### 自动令牌包含
Spring 在使用[Spring MVC form tag](https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/view.html#view-jsp-formtaglib-formtag)的窗体中,安全性将自动[包括CSRF令牌](../exploits/csrf.html#servlet-csrf-include)。例如,下面的JSP:
```
```
将输出类似于以下内容的HTML:
```
```
### 解析CSRFToken
Spring Security提供了`CsrfTokenArgumentResolver`,它可以自动解析 Spring MVC参数的当前`CsrfToken`。通过使用[@enableWebSecurity](../configuration/java.html#jc-hello-wsca),你将自动将其添加到 Spring MVC配置中。如果使用基于XML的配置,则必须自己添加该配置。
一旦正确配置了`CsrfTokenArgumentResolver`,就可以将`CsrfToken`公开到基于HTML的静态应用程序中。
Java
```
@RestController
public class CsrfController {
@RequestMapping("/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
```
Kotlin
```
@RestController
class CsrfController {
@RequestMapping("/csrf")
fun csrf(token: CsrfToken): CsrfToken {
return token
}
}
```
保持`CsrfToken`是其他域的秘密是很重要的。这意味着如果你使用[跨源共享](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS),那么你应该**NOT**将`CsrfToken`公开到任何外部域。
[Spring Data](data.html)[WebSocket](websocket.html)