Spring Security Java Config 预览:Web 安全

工程 | Rob Winch | 2013年7月3日 | ...

更新

用户应参考包含最新信息的Spring Security 参考文档

原始博客文章

在我的上一篇文章中,我介绍了 Spring Security 的 Java 配置,并讨论了项目的一些细节。在本文中,我们将首先介绍一个非常简单的 Web 安全配置。然后,我们将通过一些定制配置来稍微丰富一下内容。

Hello Web Security

本节我们将介绍基于 Web 的安全性的最基本配置。它可以分为四个步骤:

WebSecurityConfigurerAdapter

@EnableWebSecurity 注解和 WebSecurityConfigurerAdapter 协同工作,提供基于 Web 的安全性。通过扩展 WebSecurityConfigurerAdapter 并只用几行代码,我们可以实现以下功能:

  • 要求用户在访问应用程序内的任何 URL 之前进行身份验证
  • 创建一个用户,其用户名为 "user",密码为 "password",角色为 "ROLE_USER"
  • 启用 HTTP Basic 和基于表单的身份验证
  • Spring Security 会自动为你渲染登录页面和注销成功页面

@Configuration
@EnableWebSecurity
public class HelloWebSecurityConfiguration
   extends WebSecurityConfigurerAdapter {

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) {
    auth
      .inMemoryAuthentication()
        .withUser("user").password("password").roles("USER");
  }
}

供你参考,这类似于以下 XML 配置,但有一些例外:

  • Spring Security 将渲染登录、身份验证失败 URL 和注销成功 URL
  • login-processing-url 只会处理 HTTP POST 请求
  • login-page 只会处理 HTTP GET 请求

<http use-expressions="true">
  <intercept-url pattern="/**" access="authenticated"/>
  <logout
    logout-success-url="/login?logout"
    logout-url="/logout"
  />
  <form-login
    authentication-failure-url="/login?error"
    login-page="/login"
    login-processing-url="/login"
    password-parameter="password"
    username-parameter="username"
  />
</http>
<authentication-manager>
  <authentication-provider>
    <user-service>
      <user name="user" 
          password="password" 
          authorities="ROLE_USER"/>
    </user-service>
  </authentication-provider>
</authentication-manager>

AbstractAnnotationConfigDispatcherServletInitializer

下一步是确保根 ApplicationContext 包含我们刚刚指定的 HelloWebSecurityConfiguration。实现这一点有许多不同的方法,但如果你使用的是 Spring 的 AbstractAnnotationConfigDispatcherServletInitializer,它可能看起来像这样:


public class SpringWebMvcInitializer extends
   AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class[] { HelloWebSecurityConfiguration.class };
  }
  ...
}

换句话说,传统上 Spring Security 是使用 web.xml 中类似于以下的代码行初始化的:


<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
  <listener-class>
    org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>

<!-- Load all Spring XML configuration including our security.xml file -->
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring/*.xml</param-value>
</context-param>

[callout title="WebApplicationInitializer 的顺序"]如果在调用 AbstractSecurityWebApplicationInitializer 后添加任何 Servlet Filter 映射,它们可能会意外地被添加到 springSecurityFilterChain 之前。除非应用程序包含不需要保护的 Filter 实例,否则 springSecurityFilterChain 应该在任何其他 Filter 映射之前。@Order 注解可以用来帮助确保任何 WebApplicationInitializer 都以确定的顺序加载。[/callout]

AbstractSecurity WebApplicationInitializer

最后一步是我们需要映射 springSecurityFilterChain。通过扩展 AbstractSecurityWebApplicationInitializer 并可选地覆盖方法来定制映射,我们可以轻松做到这一点。

下面最基本的示例接受默认映射,并添加具有以下特性的 springSecurityFilterChain:

  • springSecurityFilterChain 映射到 "/*"
  • springSecurityFilterChain 使用 ERRORREQUEST 的分发类型
  • springSecurityFilterChain 映射被插入到任何已经配置的 Servlet Filter 映射之前

public class SecurityWebApplicationInitializer 
   extends AbstractSecurityWebApplicationInitializer {
}

上面的代码等效于 web.xml 中的以下几行:


<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>
    org.springframework.web.filter.DelegatingFilterProxy
  </filter-class>
</filter>

<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>ERROR</dispatcher>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>

CustomWebSecurityConfigurerAdapter

我们的HelloWebSecurityConfiguration 示例展示了 Spring Security Java 配置可以为我们提供一些非常好的默认设置。让我们来看看一些基本的定制。


@EnableWebSecurity
@Configuration
public class CustomWebSecurityConfigurerAdapter extends
   WebSecurityConfigurerAdapter {
  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) {
    auth
      .inMemoryAuthentication()
        .withUser("user")  // #1
          .password("password")
          .roles("USER")
          .and()
        .withUser("admin") // #2
          .password("password")
          .roles("ADMIN","USER");
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    web
      .ignoring()
         .antMatchers("/resources/**"); // #3
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeUrls()
        .antMatchers("/signup","/about").permitAll() // #4
        .antMatchers("/admin/**").hasRole("ADMIN") // #6
        .anyRequest().authenticated() // 7
        .and()
    .formLogin()  // #8
        .loginUrl("/login") // #9
        .permitAll(); // #5
  }
}

假设我们调整 AbstractAnnotationConfigDispatcherServletInitializer 来加载我们的新配置,我们的 CustomWebSecurityConfigurerAdapter 将执行以下操作:

  • 允许内存身份验证,用户名为 "user"
  • 允许内存身份验证,管理员用户名为 "admin"
  • 忽略任何以 "/resources/" 开头的请求。这类似于使用 XML 命名空间配置时配置 http@security=none
  • 允许任何人(包括未经身份验证的用户)访问 URL "/signup" 和 "/about"
  • * 允许任何人(包括未经身份验证的用户)访问 URL "/login" 和 "/login?error"。在这种情况下,permitAll() 意味着允许访问 formLogin() 使用的任何 URL。* 任何以 "/admin/" 开头的 URL 都必须是管理员用户才能访问。在我们的示例中,这将是用户 "admin"。* 所有剩余的 URL 要求用户成功通过身份验证 * 使用 Java 配置默认设置来配置基于表单的身份验证。当向 URL "/login" 提交带参数 "username" 和 "password" 的 POST 请求时,执行身份验证。* 明确指定登录页面,这意味着当请求 GET /login 时,开发者需要负责渲染该登录页面。

    对于熟悉基于 XML 配置的人来说,上面的配置与以下 XML 配置非常相似:

    
    <http security="none" pattern="/resources/**"/>
    <http use-expressions="true">
      <intercept-url pattern="/logout" access="permitAll"/>
      <intercept-url pattern="/login" access="permitAll"/>
      <intercept-url pattern="/signup" access="permitAll"/>
      <intercept-url pattern="/about" access="permitAll"/>
      <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
      <logout
          logout-success-url="/login?logout"
          logout-url="/logout"
      />
      <form-login
          authentication-failure-url="/login?error"
          login-page="/login"
          login-processing-url="/login"
          password-parameter="password"
          username-parameter="username"
      />
    </http>
    <authentication-manager>
      <authentication-provider>
        <user-service>
          <user name="user" 
              password="password" 
              authorities="ROLE_USER"/>
          <user name="admin" 
              password="password" 
              authorities="ROLE_USER,ROLE_ADMIN"/>
        </user-service>
      </authentication-provider>
    </authentication-manager>
    

    与 XML 命名空间的相似之处

    在看了我们这个稍微复杂一点的示例后,你可能会发现 XML 命名空间和 Java 配置之间有一些相似之处。以下是一些更有用的要点:

    • HttpSecurity 与 http 命名空间元素非常相似。它允许为特定的选择(在本例中是所有)请求配置基于 Web 的安全性。
    • WebSecurity 与任何用于 Web 且不需要父元素的 Security 命名空间元素(例如 security=none, debug 等)非常相似。它允许配置影响整个 Web 安全性的内容。
    • WebSecurityConfigurerAdapter 是一个便利类,允许对 WebSecurity 和 HttpSecurity 进行定制。我们可以多次(在不同的对象中)扩展 WebSecurityConfigurerAdapter,以模拟拥有多个 http 元素的行为。
    • 通过格式化我们的 Java 配置代码,它更容易阅读。它可以像阅读等效的 XML 命名空间一样,其中 "and()" 表示可选地关闭一个 XML 元素。

    与 XML 命名空间的区别

    你也会注意到 XML 和 Java 配置之间有一些重要的区别。

    • #1 和 #2 中创建用户时,我们不像 XML 配置那样指定 "ROLE_" 前缀。由于这个约定非常常见,"roles" 方法会自动为你添加 "ROLE_"。如果你不想添加 "ROLE_",可以使用 authorities 方法代替。
    • Java 配置有不同的默认 URL 和参数。在创建自定义登录页面时请记住这一点。结果是我们的 URL 更符合 RESTful 风格。此外,使用 Spring Security 的事实也不是那么明显,这有助于防止信息泄露。例如:
    • GET /login 渲染登录页面,而不是 /spring_security_login
    • POST /login 验证用户身份,而不是 /j_spring_security_check
    • 用户名参数默认为 username,而不是 j_username
    • 密码参数默认为 password,而不是 j_password
    • Java 配置可以轻松地将多个请求匹配器映射到相同的角色。这在#4 中很明显,我们将两个 URL 映射为任何人都可以访问。
    • Java 配置试图移除冗余代码。例如,我们不再像 XML 那样在 form-login 元素和 intercept-url 元素中重复我们的 /login URL,而是可以直接声明用户应该可以访问与 formLogin() 相关的所有 URL,如#5 所示。
    • 当使用 hasRole 方法映射 HTTP 请求时,如我们在#6 中所做的那样,我们不需要像 XML 那样指定 "ROLE_"。同样,由于这是一个非常常见的约定,hasRole 方法会自动为你添加 "ROLE_"。如果你不想自动添加 "ROLE_" 前缀,可以使用 "access" 方法。

    更多 Web 示例

    示例兼容性 由于代码已在没有更改的情况下合并到 Spring Security 3.2 M2 中,因此示例将兼容独立模块或 spring-security-config-3.2.0.M2+

    我们提供了一些示例,展示了如何使用 Spring Security Java 配置来保护你的 Web 应用程序,希望能引起你的兴趣。下面你可以找到一些包含更多示例的资源。

    XML 命名空间转换为 Java 配置

    如果你在将 XML 命名空间转换为 Java 配置时遇到困难,可以参考测试。惯例是,给定 XML 元素的测试将以 "Namespace" 开头,包含 XML 元素名称,并以 "Tests" 结尾。例如,要了解 http 元素如何映射到 Java 配置,你可以参考 NamespaceHttpTests。另一个示例是,你可以在 NamespaceRememberMeTests 中了解 remember-me 命名空间如何映射到 Java 配置。

    请提供反馈

    如果你遇到错误、有改进想法等,请随时提出!我们很想听取你的意见,以确保在代码正式发布前一切正常。尽早试用新功能是回馈社区的一种良好而简单的方式。这也确保了你想要的功能存在并按你期望的方式工作。

    请将任何问题或功能请求记录在 Spring Security JIRA 的 "Java Config" 类别下。在提交 JIRA 后,我们鼓励(但不强制要求)你以拉取请求的形式提交你的更改。你可以在贡献者指南中阅读更多关于如何操作的信息。

    如果你对如何做某事有疑问,请使用Spring Security 论坛或在Stack Overflow 上使用 spring-security 标签提问(我会密切关注)。如果你对这篇博客有任何具体的评论或问题,请随时留言。使用适当的工具将有助于使每个人都更容易。

    总结

    你应该对如何使用 Spring Security 的 Java 配置实现基于 Web 的安全性有了相当好的了解。在下一篇文章中,我们将探讨如何使用 Java 配置设置基于方法的安全性。

    订阅 Spring 快讯

    保持与 Spring 快讯的连接

    订阅

    超越

    VMware 提供培训和认证,助你加速进步。

    了解更多

    获取支持

    Tanzu Spring 通过一个简单的订阅提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

    了解更多

    近期活动

    查看 Spring 社区的所有近期活动。

    查看全部