Spring Security表单登录

2023/05/17

1. 概述

本教程将重点介绍使用Spring Security实现登录。

2. Maven依赖

使用Spring Boot时,spring-boot-starter-security包含所有Spring Security相关的依赖项, 例如spring-security-core、spring-security-web和spring-security-config等:


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

3. Spring Security Java配置

让我们首先创建一个继承WebSecurityConfigurerAdapter的Spring Security配置类,在该类中配置一个SecurityFilterChain bean。

通过添加@EnableWebSecurity,我们开启Spring Security和MVC集成支持:


@Configuration
@EnableWebSecurity
public class SecSecurityConfig {

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        // InMemoryUserDetailsManager (see below)
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // http builder configurations for authorize requests and form login (see below)
    }
}

在这个示例中,我们使用内存方式的身份认证并定义了三个用户。

接下来,我们将介绍用于创建表单登录配置的元素。

3.1 Authentication Manager

Authentication Provider由一个简单的内存实现InMemoryUserDetailsManager支持,这对于快速编写示例代码非常有用:

public class SecSecurityConfig {

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user1 = User.withUsername("user1")
                .password(passwordEncoder().encode("user1Pass"))
                .roles("USER")
                .build();
        UserDetails user2 = User.withUsername("user2")
                .password(passwordEncoder().encode("user2Pass"))
                .roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin")
                .password(passwordEncoder().encode("adminPass"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user1, user2, admin);
    }
}

在这里,我们使用硬编码的用户名、密码和角色配置三个了用户。

从Spring 5开始,我们还必须定义一个密码编码器。在我们的示例中,我们将使用BCryptPasswordEncoder:

public class SecSecurityConfig {

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

接下来让我们配置HttpSecurity。

3.2 授权请求的配置

我们首先对授权请求进行必要的配置。在这里,我们允许匿名访问/login,以便用户可以进行身份验证。 我们将/admin限制为仅ADMIN角色可访问并保护其他所有内容:


@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/admin/**")
            .hasRole("ADMIN")
            .antMatchers("/anonymous*")
            .anonymous()
            .antMatchers("/login*")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            // ...
}

请注意,antMatchers()调用的顺序很重要;更具体的规则需要首先定义,然后定义更一般的规则

3.3 表单登录配置

接下来我们将扩展上述配置以进行表单登录和注销:

@Bean
public SecurityFilterChain filterChain(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());
    return http.build();
}
  • loginPage() - 自定义登录页面
  • loginProcessingUrl() - 提交用户名和密码的URL
  • defaultSuccessUrl() - 登录成功后的页面
  • failureUrl() - 登录失败后的页面
  • logoutUrl() - 自定义注销URL

4. 将Spring Security添加到Web应用程序

要使用上面定义的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应用程序,则不需要此AppInitializer类。

5. Spring Security XML配置

整个项目使用的是Java配置,因此我们需要通过@ImportResource注解来导入XML配置文件:


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

下面是Spring Security XML配置文件webSecurityConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xsi:schemaLocation="
		http://www.springframework.org/schema/security 
        https://www.springframework.org/schema/security/spring-security.xsd
		http://www.springframework.org/schema/beans 
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <http use-expressions="true">
        <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/anonymous*" access="isAnonymous()"/>
        <intercept-url pattern="/login*" access="permitAll"/>
        <intercept-url pattern="/**" access="isAuthenticated()"/>

        <csrf disabled="true"/>

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

        <logout logout-url="/perform_logout" delete-cookies="JSESSIONID"
                success-handler-ref="customLogoutSuccessHandler"/>

        <!-- <access-denied-handler error-page="/accessDenied"/> -->
        <access-denied-handler ref="customAccessDeniedHandler"/>
    </http>

    <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="user1" password="user1Pass" authorities="ROLE_USER"/>
                <user name="user2" password="user2Pass" authorities="ROLE_USER"/>
                <user name="admin" password="adminPass" authorities="ROLE_ADMIN"/>
            </user-service>
        </authentication-provider>
    </authentication-manager>
</beans:beans>

6. web.xml

在Spring 4之前,我们习惯于在web.xml中配置Spring Security;仅向标准web.xml中添加了一个附加过滤器:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!-- 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>
</web-app>

过滤器DelegatingFilterProxy简单地委托给一个Spring管理的FilterChainProxy bean,它本身能够从Spring bean的全生命周期管理中受益。

7. 登录表单

登录表单页面将使用Spring MVC注册,使用简单的机制将视图名称映射到URL。这样,两者之间不需要显式的定义一个控制器:

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

当然,这对应于login.jsp:


<html>
<head></head>

<body>
<h1>Login</h1>
<form name='f' action="perform_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登录的更多配置

在上面介绍的Spring Security配置中,我们简要讨论了登录机制的一些配置,现在让我们更详细地介绍一下。

覆盖Spring Security中大多数默认值的一个原因是隐藏应用程序是由Spring Security保护的事实。 我们还希望尽可能减少潜在攻击者知道的有关应用程序的信息。

因此,HttpSecurity的最终配置如下所示:


@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.
            // ...
            .formLogin()
            .loginPage("/login.html")
            .loginProcessingUrl("/perform_login")
            .defaultSuccessUrl("/homepage.html", true)
            .failureUrl("/login.html?error=true");
    return http.build();
}

或相应的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 URL

Spring将触发登录身份验证过程的默认URL是/login,在Spring Security 4之前是/j_spring_security_check。

我们可以使用loginProcessingUrl方法来覆盖这个URL:

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

也可以使用XML配置:

login-processing-url="/perform_login"

通过覆盖这个默认URL,我们隐藏了应用程序实际上是由Spring Security保护的。

8.3 登录成功后的页面

成功登录后,我们将被重定向到默认为Web应用程序根路径的页面。

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

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

或者使用XML配置:

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

如果always-use-default-target属性设置为true,则用户始终被重定向到此页面。 如果该属性设置为false,那么在提示用户进行身份验证之前,用户将被重定向到他们想要访问的上一个页面。

8.4 登录失败后的页面

与登录页面类似,Spring Security给我们提供的默认登录失败页面为/login?error。

要覆盖它,我们可以使用failureUrl()方法:

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

或使用XML:

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

9. 总结

在这个Spring Security登录案例中,我们配置了一个简单的身份验证过程。我们还介绍了Spring Security登录表单、安全配置以及一些更高级的自定义方式。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章