Spring Security 的 Web 应用和指纹登录实践(3)
- UID
- 1066743
|
Spring Security 的 Web 应用和指纹登录实践(3)
Spring Security 在 Web 中的认证示例下面的视频中会介绍采用 Spring Security 提供的 UsernamePasswordAuthenticationFilter 实现登录验证。
下面以用户名密码登录为例来梳理 Spring Security 在 Web 中的认证流程。上文提到 Spring Security 是以 Filter 来作为校验的入口点。在用户名密码登录中对应的 Filter 是 UsernamePasswordAuthenticationFilter。attemptAuthentication 函数会执行调用校验的逻辑。在 attemptAuthentication 函数中,可以找到以下代码,如清单 9 所示:
清单 9. attemptAuthentication 函数代码片段1
2
3
4
5
6
7
8
| public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse
response) throws AuthenticationException {
......
UsernamePasswordAuthenticationToken authRequest = new
UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
|
attemptAuthentication 函数会调用 AuthenticationManager 执行校验逻辑,并获取到重新注入后的 Authentication。在 UsernamePasswordAuthenticationFilter 父类 AbstractAuthenticationProcessingFilter 的 successfulAuthentication 函数中发现以下代码,如清单 10 所示:
清单 10. successAuthentication 函数1
2
3
4
5
6
| protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication
authResult)throws IOException, ServletException {
...... SecurityContextHolder.getContext().setAuthentication(authResult);
......
}
|
successfulAuthentication 函数会把重新注入的 Authentication 填充到 SecurityContext 中,完成验证。
在 Web 中,AuthenticationManager 的实现类 ProviderManager 并没有实现校验逻辑,而是代理给 AuthenticationProvider, 在用户名密码登录中就是 DaoAuthenticationProvider。DaoAuthenticationProvider 主要完成 3 个功能:获取 UserDetails、校验密码、重新注入 Authentication。在 authenticate 函数中发现以下代码,如清单 11 所示:
清单 11. DaoAuthenticationProvider.authenticate 函数签名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
| public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
......
// 获取 UserDetails
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
......
}
......
try {
......
//校验密码
additionalAuthenticationChecks(
user,
(UsernamePasswordAuthenticationToken) authentication
);
}
......
// 从新注入 Authentication
return createSuccessAuthentication(
principalToReturn,
authentication,
user
);
}
|
首先从 userCache 缓存中查找 UserDetails, 如果缓存中没有获取到,调用 retrieveUser 函数获取 UserDetails。retrieveUser 函数签名如清单 12 所示:
清单 12. retrieveUser 函数签名1
2
3
4
5
6
7
8
9
10
| protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
......
return loadedUser;
}
|
retrieveUser 函数调用 UserDetailsService 获取 UserDetails 对象。UserDetailsService 接口签名如清单 13 所示:
清单 13. UserDetailsService 接口签名1
2
3
| public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
|
UserDetailsService 非常简单,只有一个 loadUserByUserName 函数,函数参数虽然名为 username,但只要是用户的唯一标识符即可。下面是基于数据库存储的简单示例, 如清单 14 所示:
清单 14. CustomUserDetailsService 类签名1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
User user = userDao.findByName(username);
if(Objects.isNull(user)) {
throw new UsernameNotFoundException();
}
UserDetails details = new CustomUserDetails(user);
return details;
}
}
|
调用 UserDao 获取 User 对象,将 User 对象包装成 UserDetails 对象。如果没有找到 User 对象,需要抛出 UsernameNotFoundException 异常。
DaoAuthenticationProvider 密码校验调用 additionalAuthenticationChecks 函数,具体通过 PasswordEncoder 比对用户输入的密码和存储在应用中的密码是否相等,如果不相等,抛出 BadCredentialsException 异常。
DaoAuthenticationProvider 对 Authentication 对象的重新注入通过调用 createSuccessAuthentication 函数, 如清单 15 所示:
清单 15. createSuccessAuthentication 函数签名1
2
3
4
5
6
7
8
9
10
11
| protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
UsernamePasswordAuthenticationToken result = new
UsernamePasswordAuthenticationToken(
principal,
authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities())
);
result.setDetails(authentication.getDetails());
return result;
}
|
以上就是 Spring Security 在 Web 环境中对于用户名密码校验的整个流程,简言之:
- UsernamePasswordAuthenticationFilter 接受用户名密码登录请求,将 Authentication 传递给 ProviderManager 进行校验。
- ProviderManager 将校验任务代理给 DaoAuthenticationProvider。
- DaoAuthenticationProvider 对 Authentication 的用户名和密码进行校验,校验通过后返回重新注入的 Authentication 对象。
- UsernamePasswordAuthenticationFilter 将重新注入的 Authentication 对象填充到 SecurityContext 中。
|
|
|
|
|
|