1.概述
本文将讲解如何使用Spring Security 完成登录,如果你对Spring MVC
不熟悉,不妨先学习我们上一篇文章Spring MVC教程。
2. Maven依赖
使用Spring Boot时, spring-boot-starter-security 会自动引入所有依赖,例如 spring-security-core
,spring-security-web
和 spring-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 Security
与MVC
集成支持:
@Configuration
@EnableWebSecurity
public class SecSecurityConfig {
@Bean
public InMemoryUserDetailsManager userDetailsService() {
// InMemoryUserDetailsManager (见下面)
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// http builder configurations for authorize requests and form login (见下面)
}
}
下面我们来实现具体的配置。
首先我们从认证管理器开始。
3.1 InMemoryUserDetailsManager
为了简单起见,我们将使用内存版的认证管理器,用户数据存储在内存中,不走数据库查询。用到InMemoryUserDetailsManager:
@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);
}
我们配置了3个用户,账号密码和角色信息都是硬编码的。
从Spring 5开始,我们也必须定义密码编码器。本来中,我们使用了 BCryptPasswordEncoder:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
接下来,让我们配置 HttpSecurity
。
3.2 权限配置
下面进行必要的认证配置。
@Bean
public SecurityFilterChain filterChain(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. 登录配置
继续扩展上面的配置,添加登录和注销相关配置项。
@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配置
要使上述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,能最大限度地减少了潜在的攻击者知道的有关该应用程序的信息。
完成配置后,登录项像下面这样:
@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请求到默认/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