Spring Security Java 配置预览:Web 安全

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

更新

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

原始博客文章

在我之前的文章中,我介绍了 Spring Security Java 配置,并讨论了一些项目的后勤工作。在这篇文章中,我们将首先逐步完成一个非常简单的 Web 安全配置。然后,我们将通过一些自定义配置来增强它。

Hello Web 安全

在本节中,我们将介绍 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
  • 允许任何人(包括未经身份验证的用户)访问“/signup”和“/about” URL
  • * 允许任何人(包括未经身份验证的用户)访问“/login”和“/login?error” URL。在这种情况下,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 社区中所有即将举行的活动。

    查看全部