Coding & Life

求知若饥,虚心若愚

0%

Spring Security22 - 图解认证过滤器UsernamePasswordAuthenticationFilter

1. 前言

欢迎阅读Spring Security 实战干货系列文章,在集成Spring Security安全框架的时候我们最先处理的可能就是根据我们项目的实际需要来定制注册登录了,尤其是Http登录认证。根据以前的相关文章介绍,Http登录认证由过滤器UsernamePasswordAuthenticationFilter进行处理。我们只有把这个过滤器搞清楚才能做一些定制化。今天我们就简单分析它的源码和工作流程。

2. UsernamePasswordAuthenticationFilter 源码分析

UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter(另文分析)。它的作用是拦截登录请求并获取账号和密码,然后把账号密码封装到认证凭据UsernamePasswordAuthenticationToken中,然后把凭据交给特定配置的AuthenticationManager去作认证。源码分析如下:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// 默认取账户名、密码的key
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
// 可以通过对应的set方法修改
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
// 默认只支持 POST 请求
private boolean postOnly = true;

// 初始化一个用户密码 认证过滤器 默认的登录uri 是 /login 请求方式是POST
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}

// 实现其父类 AbstractAuthenticationProcessingFilter 提供的钩子方法 用去尝试认证
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 判断请求方式是否是POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}

// 先去 HttpServletRequest 对象中获取账号名、密码
String username = obtainUsername(request);
String password = obtainPassword(request);

if (username == null) {
username = "";
}

if (password == null) {
password = "";
}

username = username.trim();

// 然后把账号名、密码封装到 一个认证Token对象中,这是就是一个通行证,但是这时的状态时不可信的,一旦通过认证就变为可信的
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);

// 会将 HttpServletRequest 中的一些细节 request.getRemoteAddr() request.getSession 存入的到Token中
setDetails(request, authRequest);

// 然后 使用 父类中的 AuthenticationManager 对Token 进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
// 获取密码 很重要 如果你想改变获取密码的方式要么在此处重写,要么通过自定义一个前置的过滤器保证能此处能get到
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}

// 获取账户很重要 如果你想改变获取密码的方式要么在此处重写,要么通过自定义一个前置的过滤器保证能此处能get到
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}

// 参见上面对应的说明为凭据设置一些请求细节
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}

// 设置账户参数的key
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}

// 设置密码参数的key
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}

// 认证的请求方式是只支持POST请求
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}

public final String getUsernameParameter() {
return usernameParameter;
}

public final String getPasswordParameter() {
return passwordParameter;
}
}

为了加强对流程的理解,我特意画了一张图来对这个流程进行清晰的说明:

3. 我们可以定制什么

根据上面的流程,我们理解了UsernamePasswordAuthenticationFilter工作流程后可以做这些事情:

  • 定制我们的登录请求URI和请求方式。
  • 登录请求参数的格式定制化,比如可以使用JSON格式提交甚至几种并存。
  • 将用户名和密码封装入凭据UsernamePasswordAuthenticationToken,定制业务场景需要的特殊凭据。

4. 我们会有什么疑问

AuthenticationManager从哪儿来,它又是什么,它是如何对凭据进行认证的,认证成功的后续细节是什么,认证失败的后续细节是什么。不要走开,持续关注为你揭晓这个答案。

转载自@felord.cn