上一章,给大家介绍了如何引入SpringSecurity。但是Security的默认安全校验不是很人性化,
所以我们需要实现自己的个性化校验逻辑。
在实现个性化校验逻辑之前,我们先来看看SpringSecurity的基本原理,以方便我们个性化。
security原理:
使用一系列的过滤器对请求进行拦截,形成一个过滤链。一层一层下来最后到访问rest api为止。
security的登录过滤器为:AbstractAuthenticationProcessingFilter
默认的登录为basic登录,过滤器为:BasicAuthenticationFilter
表单登陆过滤器为:UsernamePasswordAuthenticationFilter
下面我们使用第一节的代码,在BasicAuthenticationFilter类上dubug测试。
主要代码:
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//获取请求头中数据
String header = request.getHeader("Authorization");
//如果为空或者不是以Basic开头就走下一个过滤链
if (header == null || !header.startsWith("Basic ")) {
chain.doFilter(request, response);
return;
}
try {
//将请求头的数据解析成账户密码
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String username = tokens[0];
//判断是否认证过,通过SecurityContextHolder
if (authenticationIsRequired(username)) {
//通过账户密码构建UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, tokens[1]);
authRequest.setDetails(
this.authenticationDetailsSource.buildDetails(request));
//提交给AuthenticationManager
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
//认证完成以后放入SecurityContextHolder,基于TreadLocal
SecurityContextHolder.getContext().setAuthentication(authResult);
//记住我
this.rememberMeServices.loginSuccess(request, response, authResult);
//登陆成功的处理。空实现
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, failed);
if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, failed);
}
return;
}
//继续下一个过滤器
chain.doFilter(request, response);
}
下面看AuthenticaitonManager,这是一个接口,默认实现为ProviderManager
主要代码:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
/**
* 遍历容器里的AuthenticationProvider,查看是否支持该authentication
* 如果不支持就continue,成功以后不进行后续的尝试
* 都不支持则抛出异常
*
*/
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
}
}
上面代码认证通过以后返回一个认证完的Authentication
下面看AuthenticationProvider接口的实现DaoAuthenticationProvider
主要代码:
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
/**
* 调用UserDetailsService,通过用户名加载用户信息
*/
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
//其他省略
return loadedUser;
}
返回UserDetails实例.UserDetailsService的默认实现是基于内存的。我们在项目中可以把这里改成基于数据库的
下面我们来看看UserDetailsService接口和UserDetails类
public interface UserDetailsService {
/**
* 通过用户名获取用户信息
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
最后SecurityContextHolder将认证成功的用户信息保存到session中。
最后,我们来捋一下登陆的基本流程
参考博客:
https://www.cnkirito.moe/2017/09/19/spring-security-1/