1. 简介
本教程将重点介绍使用 Spring Security 登录。我们将在前面的 Spring MVC 示例之上构建,因为这是设置 Web 应用程序以及登录机制的必要部分。
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 with Maven 文章,其中介绍了如何添加所有必需的依赖项。标准的 spring-security-web和spring-security-config都是必需的。
3. Spring Security Java配置
让我们从创建一个创建SecurityFilterChain
bean的 Spring Security 配置类开始*。*
通过添加@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. InMemoryUserDetailsManager
身份验证提供者(Authentication Provider) 由一个简单的内存中实现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);
}
在这里,我们将使用硬编码的用户名、密码和角色配置三个用户。
从 Spring 5 开始,我们还必须定义一个密码编码器。在我们的示例中,我们将使用BCryptPasswordEncoder:
@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() – 自定义注销
4. 为Web应用添加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 应用程序,则不需要此初始化程序。有关如何在 Spring Boot 中加载安全配置的更多详细信息,请查看我们关于Spring Boot 安全自动配置的文章。
5. Spring Security XML配置
让我们也看看相应的 XML 配置。
整个项目使用的是Java配置,所以我们需要通过Java的*@Configuration*类导入XML配置文件:
@Configuration
@ImportResource({ "classpath:webSecurityConfig.xml" })
public class SecSecurityConfig {
public SecSecurityConfig() {
super();
}
}
和 Spring Security XML 配置,webSecurityConfig.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;*只有一个额外的过滤器添加到标准的 Spring MVC 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>
过滤器 - DelegatingFilterProxy - 简单地委托给 Spring 管理的 bean - FilterChainProxy - 它本身能够从完整的 Spring bean 生命周期管理等中受益。
7.登录表单
登录表单页面将使用将视图名称映射到 URL的直接机制向 Spring MVC 注册。此外,两者之间不需要显式控制器:
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>
Spring 登录表单具有以下相关工件:
- 登录- 发布表单以触发身份验证过程的 URL
- 用户名——用户名
- 密码——密码
8. 进一步配置Spring登录
上面我们在介绍Spring Security Configuration的时候简单的讨论了登录机制的几个配置。现在让我们更详细地讨论一下。
覆盖 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 将在/login
URL 生成一个非常基本的登录表单。
8.2. 用于登录的 POST URL
Spring Login 将 POST 以触发身份验证过程的默认 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?
默认错误。
要覆盖它,我们可以使用 *failureUrl()*方法:
http.formLogin()
.failureUrl("/login.html?error=true")
或者使用 XML:
authentication-failure-url="/login.html?error=true"
9. 结论
在这个Spring Login Example中,我们配置了一个简单的身份验证过程。我们还讨论了 Spring Security 登录表单、安全配置和一些可用的更高级的定制。
本文的实现可以在GitHub 项目中找到——这是一个基于 Eclipse 的项目,因此应该很容易导入并按原样运行。
当项目在本地运行时,可以在以下位置访问示例 HTML:
http://localhost:8080/spring-security-mvc-login/login.html