在 Spring Security 中,"未认证" 到 "已认证" 的转换是通过 Authentication
对象的状态变化 来实现的。以下是具体的实现原理和步骤:
1. 核心原理
- "未认证":最初,
Authentication
对象中没有认证成功的标志,例如用户凭据(用户名和密码)仅供验证使用。 - "已认证":一旦验证成功,会创建一个新的
Authentication
对象,标记为已认证(isAuthenticated = true
),并且这个对象中包含用户的详细信息、权限信息等。
Spring Security 通过 AuthenticationManager
和 Provider
的协作来完成这个状态切换,核心在于构造和返回一个已认证的 Authentication
对象。
2. 实现步骤
以 UsernamePasswordAuthenticationToken
为例,整个流程如下:
(1) 未认证对象的创建
过滤器(如 UsernamePasswordAuthenticationFilter
)会将用户的输入(用户名和密码)封装成一个 "未认证" 的 Authentication
对象:
// 用户输入的用户名和密码 String username = ...; String password = ...; // 构建一个未认证的 Authentication 对象 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);
此时:
principal
= 用户名(如字符串 "user")。credentials
= 密码(如字符串 "password")。authorities
=null
,因为还未认证。
(2) 传递给 AuthenticationManager
这个未认证的 Authentication
对象会被传递给 AuthenticationManager
,由其负责认证。
Authentication result = authenticationManager.authenticate(authentication);
(3) 由 Provider
验证用户信息
AuthenticationManager
会将Authentication
对象传递给合适的AuthenticationProvider
。- 具体的
Provider
(如DaoAuthenticationProvider
)会通过UserDetailsService
加载用户信息,并对密码进行校验。
关键代码示例:
java@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 1. 从 Authentication 中获取用户凭据 String username = authentication.getName(); String password = authentication.getCredentials().toString(); // 2. 从数据库加载用户 UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 3. 校验密码 if (!passwordEncoder.matches(password, userDetails.getPassword())) { throw new BadCredentialsException("Invalid username or password"); } // 4. 返回已认证的 Authentication 对象 return new UsernamePasswordAuthenticationToken( userDetails, null, // 密码可以不再存储 userDetails.getAuthorities() // 用户权限 ); }
(4) 创建一个新的、已认证的 Authentication
对象
- 校验成功后,
Provider
会返回一个新的UsernamePasswordAuthenticationToken
对象。 - 这个对象中:
principal
=UserDetails
对象,包含用户的详细信息。credentials
= 通常设置为null
,因为密码已经校验通过,不需要再保存。authorities
= 用户的权限列表,供后续授权使用。isAuthenticated
=true
,标记为已认证。
(5) 存储到 SecurityContext 中
- 最后,过滤器会将新的
Authentication
对象存储到SecurityContextHolder
中,标志用户已认证。 - 核心代码:
SecurityContextHolder.getContext().setAuthentication(authentication);
3. "未认证" 到 "已认证" 的具体对比
属性 | 未认证状态 | 已认证状态 |
---|---|---|
principal |
用户名(如字符串 "user") | 用户详细信息(UserDetails 对象) |
credentials |
用户输入的密码 | 通常为 null ,因为密码已校验成功,不再需要存储 |
authorities |
null |
用户的权限列表(从 UserDetails 中提取) |
isAuthenticated() |
false |
true ,表示认证通过 |
存储位置 | 仅存在于过滤器的临时变量中 | 存储在 SecurityContextHolder 中,供后续使用 |
4. 为什么要返回一个新的 Authentication
对象?
-
状态的区分:
- "未认证" 和 "已认证" 是两种状态,直接标记在
Authentication
对象上。 - Spring Security 的设计中,
Authentication
对象是不可变的,因此要返回一个新的对象表示状态的改变。
- "未认证" 和 "已认证" 是两种状态,直接标记在
-
安全性:
- 未认证的对象可能存储着用户的敏感数据(如明文密码)。
- 返回新的对象时,可以将密码设置为
null
,
您说:
isAuthenticated = true 这个是默认就是true还是怎么说,AuthenticationManager 又怎么感知对象对象切换的ChatGPT 说:
ChatGPT1. isAuthenticated = true
是默认的吗?
不是默认的,isAuthenticated
的值是由 Authentication
对象的设计逻辑决定的,具体可以分为以下几种情况:
-
在 "未认证" 的状态下:
- 当你手动创建一个
Authentication
对象(如UsernamePasswordAuthenticationToken
)时,默认的isAuthenticated
值是false
,表示对象尚未认证。 - 例如:
java
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); System.out.println(token.isAuthenticated()); // 输出 false
- 当你手动创建一个
-
在认证通过后:
- 如果认证成功,Spring Security 通常会返回一个新的
Authentication
对象(如UsernamePasswordAuthenticationToken
),并将其isAuthenticated
设置为true
。 - 设置为
true
的动作通常是在代码中明确完成的。例如: javaUsernamePasswordAuthenticationToken authenticatedToken = new UsernamePasswordAuthenticationToken(userDetails, null, authorities); authenticatedToken.setAuthenticated(true); // 明确标记为已认
- 如果认证成功,Spring Security 通常会返回一个新的