Spring Security—Spring MVC 整合

简介: Spring Security—Spring MVC 整合

 目录

一、@EnableWebMvcSecurity

二、MvcRequestMatcher

三、@AuthenticationPrincipal

四、异步 Spring MVC 整合

五、Spring MVC 和 CSRF 整合

1、自动包含 Token

2、解析 CsrfToken


Spring Security提供了一些与Spring MVC的可选整合。本节将进一步详细介绍这种整合。

一、@EnableWebMvcSecurity

从Spring Security 4.0开始,@EnableWebMvcSecurity 已被弃用。取而代之的是 @EnableWebSecurity,它根据classpath增加了Spring MVC功能。

要启用Spring Security与Spring MVC的整合,请在配置中添加 @EnableWebSecurity 注解。

Spring Security通过使用Spring MVC的 WebMvcConfigurer 提供配置。这意味着,如果你使用更高级的选项,如直接与 WebMvcConfigurationSupport 集成,你需要手动提供Spring Security的配置。

二、MvcRequestMatcher

Spring Security提供了与Spring MVC如何通过 MvcRequestMatcher 匹配URL的深度集成。这有助于确保你的安全规则与用于处理请求的逻辑相匹配。

要使用 MvcRequestMatcher,你必须将Spring Security配置放在与 DispatcherServlet 相同的 ApplicationContext 中。这是必要的,因为Spring Security的 MvcRequestMatcher 期望一个名称为 mvcHandlerMappingIntrospector 的 HandlerMappingIntrospector Bean被你的Spring MVC配置注册,用于执行匹配。

对于 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>

image.gif

以下 WebSecurityConfiguration 被放置在 DispatcherServlet 的 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[] { "/" };
      }
    }

    image.gif

    我们总是建议你通过对 HttpServletRequest 和安全方法(method security)进行匹配来提供授权规则。

    通过对 HttpServletRequest 的匹配来提供授权规则是很好的,因为它发生在代码路径的早期,有助于减少 attack surface(攻击面)。安全方法确保,如果有人绕过了网络授权规则,你的应用程序仍然是安全的。这就是所谓的 Defense in Depth(深度防御)

    考虑一个Controller,它有如下mapping。

      • Java
      @RequestMapping("/admin")
      public String admin() {
        // ...
      }

      image.gif

      为了限制管理员用户对这个Controller方法的访问,你可以通过在 HttpServletRequest 上匹配以下内容来提供授权规则。

        • Java
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
          http
            .authorizeHttpRequests((authorize) -> authorize
              .requestMatchers("/admin").hasRole("ADMIN")
            );
          return http.build();
        }

        image.gif

        在xml中实现相同功能。

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

        image.gif

        无论是哪种配置,/admin URL都要求被认证的用户是一个管理员用户。然而,根据我们的Spring MVC配置,/admin.html URL也映射到我们的 admin() 方法。此外,根据我们的Spring MVC配置,/admin URL也映射到我们的 admin() 方法。

        问题是,我们的安全规则只保护 /admin。我们可以为Spring MVC的所有排列组合添加额外的规则,但这将是相当冗长和乏味的。

        幸运的是,当使用 requestMatchers DSL方法时,如果Spring Security检测到Spring MVC在classpath中可用,它会自动创建一个 MvcRequestMatcher。因此,它将通过使用Spring MVC对URL进行匹配来保护Spring MVC的相同URL。

        在使用 Spring MVC 时,一个常见的要求是指定servlet路径属性,为此你可以使用 MvcRequestMatcher.Builder 来创建多个共享相同servlet路径的 MvcRequestMatcher 实例。

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

          image.gif

          下面的XML具有相同的效果。

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

          image.gif

          三、@AuthenticationPrincipal

          Spring Security提供了 AuthenticationPrincipalArgumentResolver,它可以自动解析Spring MVC参数的当前 Authentication.getPrincipal()。通过使用 @EnableWebSecurity,你会自动将其添加到你的Spring MVC配置中。如果你使用基于XML的配置,你必须自己添加这个。

          <mvc:annotation-driven>
              <mvc:argument-resolvers>
                  <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
              </mvc:argument-resolvers>
          </mvc:annotation-driven>

          image.gif

          一旦你正确配置了 AuthenticationPrincipalArgumentResolver,你就可以在Spring MVC层中完全与Spring Security脱钩。

          考虑这样一种情况:一个自定义的 UserDetailsService 返回一个实现 UserDetails 的 Object 和你自己的 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 ...
            }

            image.gif

            从 Spring Security 3.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 ...
              }

              image.gif

              有时,你可能需要以某种方式转变 principal。例如,如果 CustomUser 需要是 final 的,它就不能被继承。在这种情况下,UserDetailsService 可能会返回一个实现 UserDetails 的 Object,并提供一个名为 getCustomUser 的方法来访问 CustomUser。

                • Java
                public class CustomUserUserDetails extends User {
                    // ...
                    public CustomUser getCustomUser() {
                        return customUser;
                    }
                }

                image.gif

                然后我们可以通过使用 SpEL 表达式 访问 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 ...
                  }

                  image.gif

                  我们也可以在我们的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);
                      // ...
                    }

                    image.gif

                    我们可以通过让 @AuthenticationPrincipal 成为我们自己注解的元注解来进一步消除对 Spring Security 的依赖。下一个例子演示了我们如何在一个名为 @CurrentUser 的注解上这样做。

                    为了消除对Spring Security的依赖,消费应用程序将创建 @CurrentUser。这一步不是严格要求的,但有助于将你对Spring Security的依赖性隔离到一个更集中的位置。

                      • Java
                      @Target({ElementType.PARAMETER, ElementType.TYPE})
                      @Retention(RetentionPolicy.RUNTIME)
                      @Documented
                      @AuthenticationPrincipal
                      public @interface CurrentUser {}

                      image.gif

                      我们已经将对Spring Security的依赖性隔离到一个文件中。现在 @CurrentUser 已经被指定,我们可以用它来发出信号(signal)来解决我们的当前认证用户的 CustomUser。

                        • Java
                        @RequestMapping("/messages/inbox")
                        public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {
                          // .. find messages for this user and return them ...
                        }

                        image.gif

                        四、异步 Spring MVC 整合

                        Spring Web MVC 3.2+对 异步请求处理有很好的支持。不需要额外的配置,Spring Security会自动将 SecurityContext 设置为调用 Controller 返回的 Callable 的 Thread。例如,下面的方法会自动用创建 Callable 时可用的 SecurityContext 来调用它的 Callable。

                          • Java
                          @RequestMapping(method=RequestMethod.POST)
                          public Callable<String> processUpload(final MultipartFile file) {
                          return new Callable<String>() {
                            public Object call() throws Exception {
                            // ...
                            return "someView";
                            }
                          };
                          }

                          image.gif

                          Associating SecurityContext to Callable’s

                          将 SecurityContext 与 Callable 关联起来从技术上讲,Spring Security与 WebAsyncManager 集成。用于处理 Callable 的 SecurityContext 是 startCallableProcessing 被调用时存在于 SecurityContextHolder 上的 SecurityContext。

                          没有与 Controller 返回的 DeferredResult 的自动整合。这是因为 DeferredResult 是由用户处理的,因此,没有办法与它自动整合。然而,你仍然可以使用 并发支持来提供与Spring Security的透明整合。

                          五、Spring MVC 和 CSRF 整合

                          Spring Security与Spring MVC集成,增加CSRF保护。

                          1、自动包含 Token

                          Spring Security 在使用 Spring MVC表单标签 的表单中自动 [包含CSRF Token。考虑一下下面的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>

                          image.gif

                          前面的例子输出的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>
                          <!-- ... -->

                          image.gif

                          2、解析 CsrfToken

                          Spring Security提供了 CsrfTokenArgumentResolver,它可以自动解析Spring MVC参数的当前 CsrfToken。通过使用 @EnableWebSecurity,你会自动将其添加到你的Spring MVC配置中。如果你使用基于XML的配置,你必须自己添加这个。

                          一旦 CsrfTokenArgumentResolver 被正确配置,你就可以将 CsrfToken 暴露给你基于HTML的静态应用程序。

                            • Java
                            @RestController
                            public class CsrfController {
                              @RequestMapping("/csrf")
                              public CsrfToken csrf(CsrfToken token) {
                                return token;
                              }
                            }

                            image.gif

                            对其他域保持 CsrfToken 的 secret 是很重要的。这意味着,如果你使用 跨源资源共享(CORS),你不应该将 CsrfToken 暴露给任何外部域。



                            Doker 技术人的数码品牌!!!

                            文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群!!!

                            你的支持和鼓励是我创作的动力❗❗❗

                            官网:Doker 多克; 官方旗舰店首页-Doker 多克创新官方店-淘宝网 全品8.5折优惠,购前请和店小二说明来自《阿里云》!!!

                            目录
                            相关文章
                            |
                            19天前
                            |
                            XML 安全 前端开发
                            Spring Security 重点解析(下)
                            Spring Security 重点解析
                            39 1
                            |
                            19天前
                            |
                            安全 NoSQL Java
                            Spring Security 重点解析(上)
                            Spring Security 重点解析
                            34 1
                            |
                            20天前
                            |
                            前端开发 Java 测试技术
                            Java一分钟之Spring MVC:构建Web应用
                            【5月更文挑战第15天】Spring MVC是Spring框架的Web应用模块,基于MVC模式实现业务、数据和UI解耦。常见问题包括:配置DispatcherServlet、Controller映射错误、视图解析未设置、Model数据传递遗漏、异常处理未配置、依赖注入缺失和忽视单元测试。解决这些问题可提升代码质量和应用性能。注意配置`web.xml`、`@RequestMapping`、`ViewResolver`、`Model`、`@ExceptionHandler`、`@Autowired`,并编写测试用例。
                            307 3
                            |
                            6天前
                            |
                            存储 JSON 前端开发
                            利用Spring MVC开发程序2
                            利用Spring MVC开发程序
                            15 1
                            |
                            6天前
                            |
                            设计模式 JSON 前端开发
                            利用Spring MVC开发程序1
                            利用Spring MVC开发程序
                            18 0
                            |
                            6天前
                            |
                            存储 前端开发 Java
                            Spring MVC
                            Spring MVC
                            18 2
                            |
                            17天前
                            |
                            前端开发 Java 关系型数据库
                            使用IDEA搭建一个Spring + AOP (权限管理 ) + Spring MVC
                            使用IDEA搭建一个Spring + AOP (权限管理 ) + Spring MVC
                            |
                            20天前
                            |
                            JSON 前端开发 Java
                            【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解(下)
                            【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解
                            16 0
                            |
                            19天前
                            |
                            JSON 前端开发 Java
                            【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解(上)
                            【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解
                            24 0
                            |
                            20天前
                            |
                            设计模式 前端开发 Java
                            初识Spring MVC
                            初识Spring MVC
                            16 0