SpringSecurity5 自定义认证逻辑的另类延伸

回到序章

来来来,点这

总结的说

就是通过自定义 UsernamePasswordAuthenticationTokendetails 实现检验。

正文

在没事干的情况下,登陆成功后通过下面的命令打印了一下当前登陆用户的 authentication

1
System.out.println(SecurityContextHolder.getContext().getAuthentication().toString());

得到的结果是:

1
UsernamePasswordAuthenticationToken [Principal=SysUser(id=1, username=user1, password=123, nickName=测试账号1, locked=true, status=1, roles=[SysRole(id=1, code=test1, name=test1角色)]), Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=9914AF6FFBCE3DB0AAC5E761E8574AB0], Granted Authorities=[ROLE_test1]]

好奇为啥 Details 的类型会是 WebAuthenticationDetails,于是开始研究。

首先进到 UsernamePasswordAuthenticationToken,没找到 details 参数;

继续找父类 AbstractAuthenticationToken,哦,找到了,是个 Object 类型,找一下 setter 方法,没啥特殊的:

1
2
3
public void setDetails(Object details) {
this.details = details;
}

看一下哪里用到了这个方法,于是看到了 UsernamePasswordAuthenticationFilter,这个过滤器在检验账号密码的时候,会创建一个 Authentication 对象( UsernamePasswordAuthenticationToken )返回。

查看该过滤器调用到的方法如下:

1
2
3
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}

查看 buildDetails(),最终定位到 WebAuthenticationDetailsSourcebuildDetails() 方法。该方法就创建了 WebAuthenticationDetails 的实例。

1
2
3
4
@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new WebAuthenticationDetails(context);
}

进到对应的类中,发现是存储 sessionId 和 ip地址的。且入参是 HttpServletRequest

于是突发奇想,如果增加一个子类继承它,然后增加一个参数,接着拿这个 HttpServletRequest 获得验证码,跟入参的验证码做比对,成功的话将参数设置为另一个值,Provider 里获得 details 然后直接判断这个值。

实现过程如下:

首先弄一个子类继承 WebAuthenticationDetails:

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 class MyWebAuthenticationDetails extends WebAuthenticationDetails {

private boolean isVerify = false;

/**
* Records the remote address and will also set the session Id if a session already
* exists (it won't create one).
*
* @param request that the authentication request was received from
*/
public MyWebAuthenticationDetails(HttpServletRequest request) {
super(request);

String code = request.getParameter("code");
String sessionCode = request.getSession().getAttribute("code") + "";
if (code != null && !"".equals(code)
&& sessionCode != null && !"".equals(sessionCode)
&& code.equals(sessionCode)) {
isVerify = true;
}
}

public boolean isVerify() {
return isVerify;
}

public void setVerify(boolean verify) {
isVerify = verify;
}
}

然后重写 Provider 试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;

public class MyAuthenticationProvider extends DaoAuthenticationProvider {

@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) authentication.getDetails();
if (!details.isVerify()) {
throw new AuthenticationServiceException("验证码错误");
}

super.additionalAuthenticationChecks(userDetails, authentication);
}
}

测试,Emmm,报错,提示 WebAuthenticationDetails 不能强转成 MyWebAuthenticationDetails,继续看一下源码。

哦,WebAuthenticationDetailsSource,重写它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class MyWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource {

@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new MyWebAuthenticationDetails(context);
}
}

再试,Emmm,还是不能强转,上网查,哦,在 http.formLogin() 加上 .authenticationDetailsSource(authenticationDetailsSource)

1
2
3
4
5
6
7
8
9
10
11
@Autowired
private MyWebAuthenticationDetailsSource authenticationDetailsSource;

@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.formLogin()
.authenticationDetailsSource(authenticationDetailsSource);
...
}

完结撒花。