1. 前言

又是新的一周,博主继续来给大家更新Spring Security实战教程。在上一个章节中我们详细介绍了Spring Security的底层原理,本章节博主将带着大家介绍如何在Spring Security中实现基于内存的用户认证。

虽然Spring Security基于内存的用户认证在实际开发中相对来说用得比较少,但在某些场景下(如:开发阶段、原型验证、演示环境搭建、单元测试/集成测试,或甚至不需要数据库的简单系统),基于内存的用户认证方式就足以满足需求。为了应对这样的需求,博主觉得还是有必要聊一聊基于内存的用户认证。

2. 为何选择内存认证?

就如前面说的场景,总结内存认证主要有以下几个优点:

  • 简单快捷:配置简单,不需要依赖数据库或外部存储,适用于快速构建和测试。
  • 易于调试:所有用户信息存储在代码中,方便开发过程中快速定位问题。
  • 适用于小型应用:对于用户数量较少的应用或者临时验证原型,内存认证是个不错的选择。

当然,内存认证也有局限性:用户数据不持久化、无法扩展到分布式系统等。因此,在生产环境中,通常会采用基于数据库或其他外部认证机制的方式。

与数据库认证对比

特性 内存认证 数据库认证
用户存储位置 应用内存 持久化存储
用户管理灵活性 配置硬编码 动态增删改查
生产环境适用性 不推荐 推荐

3. 基础配置实战

接下来在之前的Maven项目中创建第三个子模块memory-spring-security,完整的Maven项目结构如下:

项目结构

❶ 创建Spring Security配置文件

现在我们来创建一个Spring Security配置文件InMemorySecurityConfig

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
@Configuration
public class InMemorySecurityConfig {

// 手动配置用户信息
@Bean
public UserDetailsService users() {
UserDetails user = User.withUsername("user")
.password("{noop}user") // {noop}表示不加密
.roles("USER")
.build();

UserDetails admin = User.withUsername("admin")
.password("{noop}admin")
.roles("ADMIN")
.build();
// 可以继续追加其它用户...
UserDetails anonymous = User.withUsername("anonymous")
.password("{noop}anonymous")
.roles("ANONYMOUS")
.build();

return new InMemoryUserDetailsManager(user, admin, anonymous);
}

// 配置安全策略,并配置/admin/**只允许ADMIN角色用户访问
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults())
.logout(withDefaults());
return http.build();
}
}

配置说明

  • 构建UserDetailsService
    创建三个用户信息:admin、user、anonymous,并由InMemoryUserDetailsManager在内存中保存用户数据。
  • SecurityFilterChain
    SecurityFilterChain中,默认采用了Spring Security表单登录登出方式,并配置/admin/**请求路径下需要管理员角色方可访问。

❷ 创建Controller测试

接下来创建用以测试的Controller:DemoMemoryController

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
@Controller
public class DemoMemoryController {

// 返回用户信息及角色权限
@GetMapping("/")
public ResponseEntity<Map<String, Object>> index(Authentication authentication) {

String username = authentication.getName(); // 用户名
Object principal = authentication.getPrincipal(); // 身份

// 获取用户拥有的权限列表
List<String> roles = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());

// 返回用户信息
return ResponseEntity.ok(Map.of(
"username", username,
"principal", principal,
"roles", roles));
}

// 测试管理员权限
@GetMapping("/admin/view")
public ResponseEntity<String> admin() {
return ResponseEntity.ok("管理员ADMIN角色访问ok");
}
}

代码说明

  • 注入Authentication对象
    index方法中,通过方法参数直接注入Authentication对象,Spring Security会自动传入当前认证信息。
  • 提取用户信息
    通过authentication.getName()获取当前登录用户的用户名;通过authentication.getAuthorities()获取用户的权限列表,并将每个权限的getAuthority()值收集成一个字符串列表。

❸ 运行测试

启动项目访问登录页,分别测试两个用户登录查看信息,如user用户:

user用户登录

接下来尝试使用user用户访问/admin/view路径,会出现403访问错误提示:即您无权访问此地址:

403权限不足

切换admin用户登录,继续访问/admin/view路径,出现管理员ADMIN角色访问ok文字显示,即代表管理员角色允许访问:

admin用户访问成功

4. 追加密码编码器

在上述代码中,我们使用了password("{noop}admin")声明密码明文存储。如果我们需要对密码加密,如何操作?实际上Spring Security为我们提供了非常方便的密码编码器。

密码编码器配置

只需要创建PasswordEncoder Bean,并返回加密类型,如下代码样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public UserDetailsService users(PasswordEncoder encoder) {
UserDetails user = User.builder()
.username("user")
.password(encoder.encode("secret"))
.roles("USER")
.build();

return new InMemoryUserDetailsManager(user);
}

支持的编码格式

1
2
3
4
5
6
# 不同前缀对应不同编码器
{noop} → NoOpPasswordEncoder (明文)
{bcrypt} → BCryptPasswordEncoder
{pbkdf2} → Pbkdf2PasswordEncoder
{scrypt} → SCryptPasswordEncoder
{sha256} → StandardPasswordEncoder

5. 总结

通过本章节的配置示例,相信你已经可以使用Spring Security的基于内存认证方式来快速搭建安全认证体系。

注意本章节中提到的基于内存的用户认证适用的场景,更多情况下还是建议使用更为健全的认证方式,如基于数据库的认证、JWT令牌认证或OAuth2。在下一章节我们将重点讲述数据库的认证,敬请期待…