1.概述

本文将讲解如何使用Spring Security 完成登录,如果你对Spring MVC不熟悉,不妨先学习我们上一篇文章Spring MVC教程

2. Maven依赖

使用Spring Boot时, spring-boot-starter-security 会自动引入所有依赖,例如 spring-security-corespring-security-webspring-security-config 以及其他依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

如果我们不想使用Spring Boot,请参阅 spring security与maven文章,描述了如何添加所需的依赖。

3. Spring Security Java配置方式

我们首先创建一个SecSecurityConfig配置类,继承自WebSecurityConfigurerAdapter类。

并添加@EnableWebSecurity注解,获得Spring SecurityMVC集成支持:

    @Configuration
    @EnableWebSecurity
    public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
            // authentication manager 认证管理器 (见下文)
        }
    
        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            // 访问权限配置 (见下文)
        }
    }

下面我们来实现具体的配置。

首先我们从认证管理器开始。

3.1 身份认证管理器(Authentication Manager)

为了简单起见,我们将使用内存版的认证管理器,用户数据存储在内存中,不走数据库查询。用到InMemoryUserDetailsManager:

protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("user1").password(passwordEncoder().encode("user1Pass")).roles("USER")
        .and()
        .withUser("user2").password(passwordEncoder().encode("user2Pass")).roles("USER")
        .and()
        .withUser("admin").password(passwordEncoder().encode("adminPass")).roles("ADMIN");
}

我们配置了3个用户,账号密码和角色信息都是硬编码的。

从Spring 5开始,我们也必须定义密码编码器。本来中,我们使用了 BCryptPasswordEncoder:

@Bean 
public PasswordEncoder passwordEncoder() { 
    return new BCryptPasswordEncoder(); 
}

接下来,让我们配置 HttpSecurity

3.2 权限配置

下面进行必要的认证配置。

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
      .csrf().disable()
      .authorizeRequests()
      // 仅有admin角色的用户可以访问`/admin`页面
      .antMatchers("/admin/**").hasRole("ADMIN")
      .antMatchers("/anonymous*").anonymous()
      // 允许所有人访问`/login`登录页面
      .antMatchers("/login*").permitAll()
      .anyRequest().authenticated()
      .and()
      // ...
}

请注意,antMatchers()的顺序很重要 —— 越具体的规则越放在前面,然后是更宽泛的规则。

3.3. 登录配置

继续扩展上面的配置,添加登录和注销相关配置项。

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
      // ...
      .and()
      .formLogin()
      .loginPage("/login.html")
      .loginProcessingUrl("/perform_login")
      .defaultSuccessUrl("/homepage.html", true)
      .failureUrl("/login.html?error=true")
      .failureHandler(authenticationFailureHandler())
      .and()
      .logout()
      .logoutUrl("/perform_logout")
      .deleteCookies("JSESSIONID")
      .logoutSuccessHandler(logoutSuccessHandler());
}
  • loginPage() - 自定义登录页面
  • loginProcessingUrl() - 处理登录验证的URL
  • defaultSuccessUrl() - 成功登录后的着陆页
  • failureUrl() - 登录不成功后的着陆页
  • logoutUrl() - 自定义注销URL

4.应用Spring Security配置

要使上述Spring Security配置生效,我们需要将其附加到我的Web应用中。

因为我们将使用 webapplicationInitializer,所以不需要提供 Web.xml

    public class AppInitializer implements WebApplicationInitializer {

        @Override
        public void onStartup(ServletContext sc) {
    
            AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
            root.register(SecSecurityConfig.class);
    
            sc.addListener(new ContextLoaderListener(root));
    
            sc.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain"))
              .addMappingForUrlPatterns(null, false, "/*");
        }
    }

请注意,如果是基于Spring Boot的应用程序,则此initializer不是必须的。我们的文章 Spring Boot security自动配置描述了Spring Security配置在Spring Boot中是如何加载的

5. Spring Security XML配置方式

前面我们通过Java代码实现Spring Security配置,下面介绍XML配置方式。

整个项目使用Java配置,因此我们需要通过Java @Configuration 类导入XML配置文件:

@Configuration
@ImportResource({ "classpath:webSecurityConfig.xml" })
public class SecSecurityConfig {
   public SecSecurityConfig() {
      super();
   }
}

Spring Security XML配置 - WebSeecurityConfig.xml

    <http use-expressions="true">
        <intercept-url pattern="/login*" access="isAnonymous()" />
        <intercept-url pattern="/**" access="isAuthenticated()"/>
    
        <form-login login-page='/login.html' 
          default-target-url="/homepage.html" 
          authentication-failure-url="/login.html?error=true" />
        <logout logout-success-url="/login.html" />
    </http>
    
    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="user1" password="user1Pass" authorities="ROLE_USER" />
            </user-service>
            <password-encoder ref="encoder" />
        </authentication-provider>
    </authentication-manager>
    
    <beans:bean id="encoder" 
      class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
    </beans:bean>

6.web.xml

在引入Spring 4之前,我们习惯在web.xml中配置Spring Security ; 仅一个额外的filter - DelegatingFilterProxy 需要添加到web.xml中

    <display-name>Spring Secured Application</display-name>

    <!-- Spring MVC -->
    <!-- ... -->
    
    <!-- Spring Security -->
    <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>
    </filter-mapping>

7.登录页面

下面使用Spring MVC来注册一个登录页面。

因为不涉及业务逻辑代码,没必要编写一个Controller,我们直接利用addViewController()将一个http请求直接映射为view视图。

registry.addViewController("/login.html");

对应的 login.jsp:

<html>
<head></head>
<body>
   <h1>Login</h1>
   <form name='f' action="login" method='POST'>
      <table>
         <tr>
            <td>User:</td>
            <td><input type='text' name='username' value=''></td>
         </tr>
         <tr>
            <td>Password:</td>
            <td><input type='password' name='password' /></td>
         </tr>
         <tr>
            <td><input name="submit" type="submit" value="submit" /></td>
         </tr>
      </table>
  </form>
</body>
</html>

登录表单中包含下面几个字段:

  • login - 表单完成提交的URL
  • username - 用户名
  • password - 密码

8.进一步配置登录

在上面Spring Security配置中,我们已经介绍了一些配置方法。下面我们进一步看下配置细节。

覆盖Spring Security默认配置,主要原因是防止别人知道我们正在使用Spring Security,能最大限度地减少了潜在的攻击者知道的有关该应用程序的信息。

完成配置后,登录项像下面这样:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
      .loginPage("/login.html")
      .loginProcessingUrl("/perform_login")
      .defaultSuccessUrl("/homepage.html",true)
      .failureUrl("/login.html?error=true")
}

或对应的XML配置:

<form-login 
  login-page='/login.html' 
  login-processing-url="/perform_login" 
  default-target-url="/homepage.html"
  authentication-failure-url="/login.html?error=true" 
  always-use-default-target="true"/>

8.1 登录页面

接下来,让我们看看我们如何使用 loginPage() 方法来配置自定义登录页面:

http.formLogin()
  .loginPage("/login.html")

或者,通过XML配置:

login-page='/login.html'

如果我们不指定,Spring Security会为我们生成一个默认的登录页面,URL为/login

8.2 登录提交

表单提交后会发送POST请求到默认/login URL处理身份验证,Spring Security 4中默认URL为/j_spring_security_check

我们可以使用loginProcessingUrl()方法来覆盖配置:

http.formLogin()
  .loginProcessingUrl("/perform_login")

或者,通过XML配置:

login-processing-url="/perform_login"

修改此配置一个理由是,避免被攻击者发现我们正在使用Spring Security

8.3 登录成功后的着陆页

登录成功之后,默认会重定向到根目录。

我们可以通过defaultSuccessUrl()方法来覆盖:

http.formLogin()
  .defaultSuccessUrl("/homepage.html")

或使用XML配置:

default-target-url="/homepage.html"

如果always-use-default-target设置为true,则始终将用户重定向到此页面。 如果该属性设置为false,则用户被重定向到登录之前的页面。

8.4 登录失败后的着陆页

与登录页面相同,默认情况下Spring会自动生成登录失败页面,/login?error

要覆盖,使用failureUrl()方法:

http.formLogin()
  .failureUrl("/login.html?error=true")

或使用XML:

authentication-failure-url="/login.html?error=true"

9.总结

在这个spring登录示例中,我们实现了一个简单的登录功能 - 我们讨论了Spring Security登录表单,安全配置和一些自定义配置。

本文项目完整源代码存放在GitHub。本地运行后,访问登录页面:

http://localhost:8080/spring-security-mvc-login/login.html