技术之路不进即退,无岸,又何谈回头。
从三个方面来说明
- 认证流程说明
- 认证结果如何在多个请求之间共享
- 获取认证用户信息
springSecurity其实就是一组过滤器,绿色的过滤器是由自己控制的,可以选择它是否出现。但是其他颜色就不可以了,并且他们的顺序也是死的。在springboot启动的时候会自动的进行加载。如下图所示:在这一组过滤器中,一个块就代表着一种方式,像表单和http Basic认证。橘色的框会根据之前绿色过滤器进行相应的判断,然后抛出异常。(例如身份认证没有通过) 然后由蓝色框会捕获抛出来的异常。
PS:一般来说链上的过滤器一般会有十几种举例
在这里我们就那上面那个实例来进行描述。在config中开启了任何url进行过滤之后。我们访问这样/mqtt/messagepub/new url会直接跳到橘色块,然后抛出未进行身份登录的异常,然后由蓝色框进行捕获,处理之后直接重定向到一个页面,进行登录。在登陆成功之后又会重新到橘色框,此次便不在抛异常了。
我们将依据下面这幅图来进行详细描述
1-认证处理流程
2-认证结果如何在多个请求之间共享
3-获取认证用户信息
在过滤器中依据用户名和密码构建了一个UsernamePasswordAuthenticationToken(Authentication的一个实现)对象,其实就是一个Authentication的实现,他封装了我们需要的认证信息。之后会调用AuthenticationManager.。这个类其实并不会去验证我们的信息,信息验证的逻辑都是在AuthenticationProvider里面,而Manager的作用则是去管理Provider,管理的方式是通过for循环去遍历(因为不同的登录逻辑是不一样的,比如表单登录、第三方登录。换句话说 不同的Provider支持的是不同的Authentication)。这里我们举一个实际的例子去看。
在 AuthenticationManager调用DaoAuthenticationProvider。而DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider ,从而也就获得了其中的authenticate方法去进行验证。
result = provider.authenticate(authentication);
AbstractUserDetailsAuthenticationProvider通过方法 retrieveUser拿到UserDetails这个类的信息(用户的信息),然后调用preAuthenticationChecks去进行预检查(里面有三个校验,没有密码过期的校验)。然后调用additionalAuthenticationChecks(附加检查),去进行密码的加密解密。
最后的postAuthenticationChecks区进行的密码是否过期的校验。
Details四个Blooen验证工过之后则会再次调用UsernamePasswordAuthenticationToken,当然这次调用与之前是不同的,之前的调用的是两个参数的,并没有获得用户权限的信息,因此调用父类方法的时候传的是空(super())。这次调用则传了相应的用户权限信息(三个参数,多了权限信息)。
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
……
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
……
try {
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
postAuthenticationChecks.check(user);
在这里我们对 认证流程说明 做一个简单的总结。
AuthenticationProvider去调用UserDetailsServicen拿到用户信息,然后做一些检查。最后把用户信息拼装到一个已经认证了的Authentication里面。然后Authentication则会沿着调用线最后返回到UernamePassword……Filter。
一说到共享,我感觉大家等会想到Session 那么问题来了。把什么放到session,有什么时候从session读取?
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
//这里是重点
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
从上面我们可以了解 实际上这里是把我们认证成功的authtication放到securitycont里面,然后再放到SecourityContextHolder里面。
SecurityContextImpl implements SecurityContext,他重写了hashcode和equals保证
authentication的唯一性。而SecurityContextHolder是对threadLocal的一个封装。目的是为了方便SecurityContextPersistrncrFilter随时可以读取到。而这个过滤器在整个过滤器的最前面。因为在过滤器的最前端,所以请求最先进到这个过滤器,当完成相应之后最后经过它。所以他的作用
1---当请求进来的时候检查seesion,里面是否有SecurityContext,如果有就拿出来放到线程里,如果没有则直接放过。
2---当完成相应后,线程里有认证信息则放到Session里。
第三点 @GetMapping("/me")
public Object getCurrentUser(){
return SecurityContextHolder.getContext().getAuthentication();
}
//只获得UserDetails 信息
@GetMapping("/me1")
public Object getCurrentUser(@AuthenticationPrincipal UserDetails user){
return user;
}