介绍
SpringSecurity核心功能:认证(身份校验,你是谁),授权(你能干什么),攻击防护(防止伪造身份)
一、原理过滤器链
REST API:相当于应用的controller,用户的增删该查的一些服务
Spring Security过滤器链:这个是最核心的部分,相当于一组Filter,请求和响应都会经过过滤器,这些过滤器在系统启动的时候,Spring boot会自动把它们都配置进去
绿色部分:
代表过滤器,每个方块都代表一个过滤器,UsernamePasswordAuthenticationFilter用来处理表单登录的,BasicAuthenticationFilter用来处理HTTPBasic登录的
绿色部分过滤器主要功能:检查当前的请求里面是不是有过这个过滤器所需要的信息,比如说:UsernamePasswordAuthenticationFilter这个过滤器来说:首先检查当前的请求是不是一个登录请求,然后再这个请求里面带没带用户名和密码,如果带了用户名和密码,这个过滤器就会用用户名和密码尝试去登录,如果这个请求里面没有带用户名和密码,继续进行下面的过滤器,下一个过滤器比如说是BasicAuthenticationFilter,这个过滤器会检查请求的请求头中是不是有Basic开头的authentication的信息,如果有的话,会尝试拿出来做basic流的字节码然后从中取出用户名和密码尝试做登录,springsecurity还提供了许多其他的认证方式,任何一个过滤器,成功的完成用户登录以后,都会再这个请求上做个标记,表明这个用户已经认证成功了,请求经历过这些过滤器后,就会到达这个写橘黄色的拦截器上比如FilterSecurityInterceptor,这个拦截器是整个Spring Security过滤器的最后一环,是最终的守门人,在它身后是我们自己写的controller的rest服务,在该拦截器中,它会去决定当前的请求能不能去访问后面真正的服务,判断的依据类似于下面:
上面的配置表明(任何请求都需要认证),就会去判断当前的请求是不是经过了前面某一个过滤器的身份认证,可以定义一些特别复杂的规则,将这些规则放到Filter SecurityInterceptor里面,这个过滤器会根据这些规则去判断,判断的结果是通过还是不同过,通过的话,请求就过去了,就能访问最终的服务了,如果不过,根据不过的原因会抛出不同的异常,比如没有认证的异常,或者该请求是给VIP用户用的,您不是VIP用户。异常抛出去以后,这个过滤器的前面还有蓝色的过滤器ExceptionTranslationFilter,这个过滤器它的作用就是用来捕获橘黄色过滤器所抛出的异常,蓝色的过滤器会根据抛出的异常做相应的处理,比如说你没有登录就访问,它会根据前面的配置引导你先去登录,比如我前面配置了UsernamePasswordFilter过滤器,这样就会把用户引导到登录页面上,比如说我前面配置了BasicAuthenticationFilter这个过滤器,这样就会在浏览器弹出一个窗口的调用,提醒用户输入用户名和密码,这就是Spring Security的一个基本原理,它应用的所用功能特性都是建立在这个过滤器链上的
在过滤器链上,绿色的部分是可以通过配置来实现的,如下:
上面是两种认证方式:
一种是httpbasic认证;还有一种是fromlogin表单认证
注意:过滤器链上的顺序不能更改,可以自定义加入其他过滤器。
二、认证过程
1.使用ctrl+shift+n组合键查找UsernamePasswordAuthenticationFilter过滤器,该过滤器是用来处理用户认证逻辑的,进入后如图:
(1)可以看到它默认的登录请求url是"/login",并且只允许POST方式的请求
(2)obtainUsername()方法点进去发现它默认是根据参数名为"username"和"password"来获取用户名和密码的
(3)通过构造方法实例化一个UsernamePasswordAuthenticationToken对象,此时调用的是UsernamePasswordAuthenticationToken的两个参数的构造函数,如图:
其中super(null)调用的是父类的构造方法,传入的是权限集合,因为目前还没有认证通过,所以不知道有什么权限信息,这里设置为null,然后将用户名和密码分别赋值给principal和credentials,同样因为此时还未进行身份认证,所以setAuthenticated(false)
(4)setDetails(request, authRequest)是将当前的请求信息设置到UsernamePasswordAuthenticationToken中
(5)通过调用getAuthenticationManager()来获取AuthenticationManager,通过调用它的authenticate方法来查找支持该token(UsernamePasswordAuthenticationToken)认证方式的provider,然后调用该provider的authenticate方法进行认证
2.AuthenticationManager是用来管理AuthenticationProvider的接口,通过查找后进入,然后使用ctrl+H组合键查看它的继承关系,找到ProviderManager实现类,它实现了AuthenticationManager接口,查看它的authenticate方法,它里面有段这样的代码:
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
...
try {
result = provider.authenticate(authentication);
...
}
}
通过for循环遍历AuthenticationProvider对象的集合,找到支持当前认证方式的AuthenticationProvider,找到之后调用该AuthenticationProvider的authenticate方法进行认证处理:
result = provider.authenticate(authentication);
3.AuthenticationProvider接口,就是进行身份认证的接口,它里面有两个方法:authenticate认证方法和supports是否支持某种类型token的方法,通过ctrl+h查看继承关系,找到AbstractUserDetailsAuthenticationProvider抽象类,它实现了AuthenticationProvider接口,它的supports方法如下:
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
说明它是支持UsernamePasswordAuthenticationToken类型的AuthenticationProvider
再看它的authenticate认证方法,其中有一段这样的代码:
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
...
}
如果从缓存中没有获取到UserDetails,那么它调用retrieveUser方法来获取用户信息UserDetails,这里的retrieveUser是抽象方法,等一会我们看它的子类实现。
用户信息UserDetails是个接口,我们进入查看,它包含以下6个接口方法:
Collection<? extends GrantedAuthority> getAuthorities();//获取权限集合
String getPassword(); //获取密码
String getUsername(); //获取用户名
boolean isAccountNonExpired(); //账户未过期
boolean isAccountNonLocked(); //账户未锁定
boolean isCredentialsNonExpired(); //密码未过期
boolean isEnabled(); //账户可用
查看它的继承关系发现User类实现了该接口,并实现了该接口的所有方法
接着AbstractUserDetailsAuthenticationProvider往下看,找到下面的代码:
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks预检查,在最下面的内部类DefaultPreAuthenticationChecks中可以看到,它会检查上面提到的三个boolean方法,即检查账户未锁定、账户可用、账户未过期,如果上面的方法只要有一个返回false,就会抛出异常,那么认证就会失败。
additionalAuthenticationChecks是附加检查,是个抽象方法,等下看子类的具体实现。
下面还有个postAuthenticationChecks.check(user)后检查,在最下面的DefaultPostAuthenticationChecks内部类中可以看到,它会检查密码未过期,如果为false就会抛出异常
如果上面的检查都通过并且没有异常,表示认证通过,会调用下面的方法:
createSuccessAuthentication(principalToReturn, authentication, user);
跟进发现此时通过构造方法实例化对象UsernamePasswordAuthenticationToken时,调用的是三个参数的构造方法:
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
此时会调用父类的构造方法设置权限信息,并调用父类的setAuthenticated(true)方法,到这里就表示认证通过了。
下面我们看看AbstractUserDetailsAuthenticationProvider的子类,同ctrl+h可查看继承关系,找到DaoAuthenticationProvider
4.DaoAuthenticationProvider类
(1)查看additionalAuthenticationChecks附加检查方法,它主要是检查用户密码的正确性,如果密码为空或者错误都会抛出异常
(2)获取用户信息UserDetails的retrieveUser方法,主要看下面这段代码:
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
它是调用了getUserDetailsService先获取到UserDetailsService对象,通过调用UserDetailsService对象的loadUserByUsername方法获取用户信息UserDetails
找到UserDetailsService,发现它是一个接口,查看继承关系,有很多实现,都是spring-security提供的实现类,并不满足我们的需要,我们想自己制定获取用户信息的逻辑,所以我们可以实现这个接口。比如从我们的数据库中查找用户信息
5.SecurityContextPersistenceFilter过滤器
那么用户认证成功之后,又是怎么保存认证信息的呢,在下一次请求过来是如何判断该用户是否已经认证了呢?
请求进来时会经过SecurityContextPersistenceFilter过滤器,进入SecurityContextPersistenceFilter过滤器并找到以下代码:
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
从session中获取SecurityContext对象,如果没有就实例化一个SecurityContext对象
SecurityContextHolder.setContext(contextBeforeChainExecution);
将SecurityContext对象设置到SecurityContextHolder中
chain.doFilter(holder.getRequest(), holder.getResponse());
表示放行,执行下一个过滤器
执行完后面的过滤并经过servlet处理之后,响应给浏览器之前再次经过此过滤器。查看以下代码:
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
通过SecurityContextHolder获取SecurityContext对象,然后清除SecurityContext,最后将获取的SecurityContext对象放入session中
其中SecurityContextHolder是与ThreadLocal绑定的,即本线程内所有的方法都可以获得SecurityContext对象,而SecurityContext对象中包含了Authentication对象,即用户的认证信息,spring-security判断用户是否认证主要是根据SecurityContext中的Authentication对象来判断。Authentication对象的详细信息如图:
源码分析:UsernamePasswordAuthenticationFilter和SocialAuthenticationFilter用的都是通过调用getAuthenticationManager()来获取AuthenticationManager,
代码:Authentication success = getAuthenticationManager().authenticate(token);
传入不同的token: SocialAuthenticationToken、UsernamePasswordAuthentication
AuthenticationManager选择不同的AuthenticationProvider认证处理
原文链接:https://blog.csdn.net/abcwanglinyong/article/details/80981389
转载自原文链接, 如需删除请联系管理员。
原文链接:SpringSecurity过滤器链和认证过程y源码分析,转载请注明来源!