Spring Security 使用教程:从入门到实战

在Web应用开发中,安全永远是不可忽视的环节。Spring Security作为Spring生态中成熟的安全框架,提供了全面的认证(Authentication)和授权(Authorization)解决方案。本文将从基础到进阶,带你一步步掌握Spring Security的核心用法,构建安全可靠的Java Web应用。(对的,这是Spring Security的入门篇)

一、Spring Security 核心概念

在开始实战前,先理解几个核心概念,有助于后续学习:

  • 认证(Authentication):验证用户身份的过程,比如登录时校验用户名和密码是否正确。

  • 授权(Authorization):在认证通过后,判断用户是否有权限执行某个操作(如访问特定接口、修改数据)。

  • SecurityContext:存储当前认证用户的信息,通过SecurityContextHolder可以随时获取。

  • UserDetails:封装用户信息的接口,包含用户名、密码、权限等核心字段。

  • AuthenticationManager:认证的核心管理器,负责调度AuthenticationProvider完成认证。

二、环境搭建:快速集成Spring Security

本文基于Spring Boot 3.x进行演示,Spring Boot已为Spring Security提供了自动配置,集成过程非常简单。

2.1 引入依赖

pom.xml(Maven)或build.gradle(Gradle)中添加Spring Security依赖:

查看代码
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>7.0.0-M2</version>
</dependency>

2.2 启动应用,体验默认安全配置

添加依赖后,直接启动Spring Boot应用,Spring Security会自动生效,默认做了以下安全配置:

  1. 所有接口默认需要认证才能访问。

  2. 生成一个默认用户,用户名是user,密码会在控制台输出(格式如:Using generated security password: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。

  3. 提供一个默认的登录页面(访问任意接口会自动跳转)。

测试:访问http://localhost:8080/hello(需先编写一个简单的Hello接口),会跳转至登录页,输入默认用户名和密码即可访问。

三、自定义认证:从默认到数据库

默认配置仅用于测试,实际项目中需要对接数据库用户,下面实现自定义认证逻辑。

3.1 编写配置类

创建SecurityConfig类,继承WebSecurityConfigurerAdapter(Spring Boot 3.x中已 deprecated,推荐使用函数式风格配置):

查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

// 注入自定义的UserDetailsService(后续实现)
private final UserDetailsService userDetailsService;

public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}

// 配置密码加密器(Spring Security 5+要求必须指定密码加密方式)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

// 配置SecurityFilterChain(核心配置)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 关闭CSRF(测试环境可关闭,生产环境需开启)
.csrf(csrf -> csrf.disable())
// 配置授权规则
.authorizeHttpRequests(auth -> auth
// 放行登录接口、静态资源等
.requestMatchers("/login").permitAll()
// 其他所有接口需要认证
.anyRequest().authenticated()
)
// 配置表单登录
.formLogin(form -> form
// 自定义登录页面路径(可选,默认是/spring-security-login)
.loginPage("/custom-login")
// 登录请求的接口路径(默认是/login)
.loginProcessingUrl("/do-login")
// 登录成功后的跳转路径
.defaultSuccessUrl("/index", true)
// 登录失败后的跳转路径
.failureUrl("/custom-login?error=true")
)
// 配置退出登录
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/custom-login?logout=true")
.invalidateHttpSession(true) // 销毁session
);

return http.build();
}
}

3.2 实现UserDetailsService

UserDetailsService负责从数据库加载用户信息,我们需要实现它来对接自己的用户表:

查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {

// 注入用户DAO(实际项目中替换为MyBatis或JPA的Repository)
private final UserDao userDao;

public CustomUserDetailsService(UserDao userDao) {
this.userDao = userDao;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户
UserEntity user = userDao.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}

// 将数据库用户转换为UserDetails对象(包含权限信息)
return User.withUsername(user.getUsername())
.password(user.getPassword()) // 数据库中存储的密码必须是加密后的
.roles(user.getRoles().split(",")) // 假设roles字段是逗号分隔的角色字符串
.build();
}
}
注意:数据库中存储的密码必须是通过`BCryptPasswordEncoder`加密后的字符串,不能存储明文。可以通过`passwordEncoder.encode("123456")`生成加密密码。

四、授权控制:基于角色和权限

认证通过后,需要对不同用户授予不同权限,Spring Security支持多种授权方式。

4.1 基于角色的URL授权

securityFilterChain中通过hasRolehasAnyRole配置URL的角色权限:

查看代码
1
2
3
4
5
6
7

.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN") // 只有ADMIN角色可访问/admin下的接口
.requestMatchers("/user/**").hasAnyRole("ADMIN", "USER") // ADMIN或USER角色可访问
.requestMatchers("/public/**").permitAll() // 公开接口,无需认证
.anyRequest().authenticated()
)

4.2 基于注解的方法级授权

在Controller方法上使用注解控制权限,需先开启全局方法安全:

查看代码
1
2
3
4
5

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启PreAuthorize注解
public class MethodSecurityConfig {
}

然后在Controller方法上使用注解:

查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

@RestController
@RequestMapping("/admin")
public class AdminController {

// 只有拥有ADMIN角色的用户可访问
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/user-manage")
public String manageUser() {
return "用户管理页面";
}

// 只有拥有DELETE权限的用户可访问
@PreAuthorize("hasAuthority('DELETE')")
@DeleteMapping("/delete/{id}")
public String deleteUser(@PathVariable Long id) {
return "删除用户:" + id;
}
}

五、进阶功能:记住我与JWT

除了基础的认证授权,Spring Security还支持“记住我”、JWT令牌等高级功能。

5.1 实现“记住我”功能

只需在securityFilterChain中添加rememberMe配置:

查看代码
1
2
3
4
5
6

.rememberMe(remember -> remember
.rememberMeParameter("remember-me") // 登录表单中的记住我参数名
.tokenValiditySeconds(60 * 60 * 24 * 7) // 记住我有效期(7天)
.userDetailsService(userDetailsService) // 加载用户信息
)

然后在登录表单中添加一个名为remember-me的复选框即可。

5.2 集成JWT实现无状态认证

对于前后端分离项目,通常使用JWT(JSON Web Token)实现无状态认证,核心步骤如下:

  1. 引入JWT依赖:添加jjwt-apijjwt-impljjwt-jackson依赖。

  2. 编写JWT工具类:实现Token的生成、解析、验证逻辑。

  3. 自定义JwtAuthenticationFilter:从请求头中获取Token,解析后完成认证。

  4. 配置SecurityFilterChain:关闭session,添加JWT过滤器,放行登录接口等。

由于JWT集成步骤较多,后续会单独写一篇实战文章,感兴趣的可以关注。

六、常见问题与排查

  • 密码加密失败:确保数据库中存储的是加密后的密码,且配置了正确的PasswordEncoder

  • 接口授权不生效:检查URL匹配规则的顺序(具体规则要放在通用规则前面),以及角色/权限名称是否正确(hasRole会自动添加ROLE_前缀,hasAuthority需要完整名称)。

  • CSRF问题:前后端分离项目中,若关闭CSRF,需确保请求头中没有携带CSRF Token;若开启,需在前端请求中添加Token。

七、总结

本文介绍了Spring Security的核心概念、环境搭建、自定义认证、授权控制及进阶功能,涵盖了大部分实际项目中的常用场景。Spring Security功能强大且灵活,掌握它能有效提升应用的安全性。建议结合实际项目多动手实践,深入理解其核心原理(如过滤器链、认证流程等),以便应对更复杂的安全需求。