账号密码认证部分
进入认证过滤器doFilter方法:
AbstractAuthenticationProcessingFilter.doFilter()
/*
* 判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。
* 踩坑:如果登陆接口 错误使用了GET方法 而不是POST方法
* 你就会惊喜的发现 attemptAuthentication 方法不执行 直接到下一个过滤器的authenticate方法
*/
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
这里也可以实现UsernamePasswordAuthenticationFilter类 并重写attemptAuthentication方法
// doFilter方法里面 调用了子类(UsernamePasswordAuthenticationFilter)的方法
authResult = this.attemptAuthentication(request, response);
UsernamePasswordAuthenticationFilter attemptAuthentication方法的处理
// 认证请求的方式必须为POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 接下来 对账号密码为空的处理
username = username != null ? username : "";
username.trim()
.....
// Authenticaiton类的实现类UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 构造方法
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
// 构造器设置权限为null?super((Collection)null);
// 授权为false?this.setAuthenticated(false);
// 这里是刚刚登陆过来,账号密码还没验证,所以这里需要放行
// super:
public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
if (authorities == null) {
this.authorities = AuthorityUtils.NO_AUTHORITIES;
} else {
Iterator var2 = authorities.iterator();
while(var2.hasNext()) {
GrantedAuthority a = (GrantedAuthority)var2.next();
Assert.notNull(a, "Authorities collection cannot contain any null elements");
}
this.authorities = Collections.unmodifiableList(new ArrayList(authorities));
}
}
// 现在回到attemptAuthentication方法:
return this.getAuthenticationManager().authenticate(authRequest);
this.getAuthenticationManager() 获取到ProviderManager类
ProviderManager.authenticate(authentication)方法做了什么?
//全局变量 private List<AuthenticationProvider> providers;
// 获取所有的providers 所以AuthenticationProvider类也是可以由我们重写
Iterator var9 = this.getProviders().iterator();
Authentication authResult = null;
while(var9.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var9.next();
// 如果有支持当前token的
if (provider.supports(toTest)) {
// 核心:调用处理 在authenticate方法里面进行密码校验
authResult = provider.authenticate(authentication);
}
}
// return authResult
这里我们再分析一下核心步骤: provider.authenticate(authentication);
// provider默认是调用AbstractUserDetailsAuthenticationProvider类的
AbstractUserDetailsAuthenticationProvider
.authenticate(Authentication authentication)
this.retrieveUser
(username, (UsernamePasswordAuthenticationToken)authentication);
会去调用子类DaoAuthenticationProvider的retrieveUser方法
在retrieveUser方法里面会去调用这个我们最熟悉的方法:
userDetailsService.loadUserByUsername
将结果逐级返回后 至此 attemptAuthentication方法结束 , 别忘了这是在doFilter方法调用的,
回到doFilter里面:
// 最终认证成功后,会处理一些与session相关的方法(比如将认证信息存到session等操作)。
sessionStrategy.onAuthentication(authResult, request, response);
// doFilter 接下来的操作:
this.successfulAuthentication(request, response, chain, authResult);
最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中并调用成功处理器做相应的操作。
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
// 核心:将当前的认证信息放到SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
// 调用成功处理器,可以自己实现AuthenticationSuccessHandler接口重写方法写自己的逻辑
successHandler.onAuthenticationSuccess(request, response, authResult);
}
总结:
进入校验过滤器 doFilter方法
判断当前filter是否可以处理
默认调用子类 账号密码校验过滤器的attemptAuthentication方法
attemptAuthentication中校验是否为post请求,且对空账号密码进行处理
进入账号密码校验Token构造方法,构造方法会设置不需要权限 进行放行
(因为此时没有去匹配账号密码 必须放进去匹配一次)
调用provider.authenticate()
遍历并取出一个可以提供支持的provider并调用
调用retrieveUser方法
调用我们熟悉的 userDetails.loadUserByUsername
自定义账号密码校验规则
返回结果
结果做些session处理
结果成功处理: 存入securityContentHolder中
默认是使用threadLocal 实现的
token认证部分
实现BasicAuthenticationFilter 过滤器,在doFilterInternal方法进行校验
(token的储存是在账号密码认证成功后执行的 并将token返回至header)
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
public TokenAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationManager, authenticationEntryPoint);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String token = request.getHeader("Authorization");
// 模拟从redis获取token : TokenUtils.getTokenList()
if (!TokenUtils.getTokenList().contains(token)) {
throw new RemoteException("token校验异常");
}
// 模拟 getUser by token
UserVO userVO = new UserVO();
userVO.setUsername("qiuhuanhen");
userVO.setPassword("123456");
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userVO, "", new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException {
System.out.println("token 校验 成功");
super.onSuccessfulAuthentication(request, response, authResult);
}
@Override
protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
System.out.println("token 校验 失败");
super.onUnsuccessfulAuthentication(request, response, failed);
}
}
快速入门demo
写了个简单的登录认证demo
https://gitee.com/qiuhuanhen/springboot-security-login
注意事项: 见 README 文件