Spring Security基本原理
<1>、过滤器调用时序解析
在调用API前通过过滤器链链式调用过滤器,所有过滤器通过后才调用API。
Spring Security过滤器调用时序图:
自定义鉴权配置:
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//http.httpBasic()//spring默认校验方式
http.formLogin()//表单登陆
.and()
.authorizeRequests()//对请求授权
.antMatchers("/user**").permitAll()
.anyRequest()
.authenticated();//进行身份认证
}
}
过滤器链和WebSecurityConfigurerAdapter :
SecurityContextPersistenceFilter: 过滤器链中的第一个过滤器,第一个进最后一个出的过滤器,SecurityContextPersistenceFilter是承接容器的session与spring security的重要filter,主要工作是从session中获取SecurityContext,然后放到上下文中(线程中),返回时,从线程中获取SecurityContext,若存在则放入session中。之后的filter大多依赖这个来获取登录态。其主要是通过HttpSessionSecurityContextRepository来存取的。
绿色部分的过滤器: 可自行配置时序,或是否需要运行,UsernamePasswordAuthenticationFilter用来处理http.formLogin()的,BasicAuthenticationFilter用来处理http.httpBasic()登录的 。
ExceptionTranslationFilter: 执行过滤器链的下一个过滤器(FilterSecurityInterceptor),捕获抛出的异常,判断异常原因,并重新抛出异常信息。
FilterSecurityInterceptor: 所有的请求到了这一个filter,如果这个filter之前没有执行过的话,那么首先执行的InterceptorStatusToken token = super.beforeInvocation(fi);这句是由AbstractSecurityInterceptor提供。它就是spring security处理鉴权的入口,主要处理登录,获取用户token信息,接着就是根据WebSecurityConfigurerAdapter的一些权限校验的配置进行对应的权限校验,通过则调用API,不通过则抛出异常。
//FilterSecurityInterceptor 部分源码
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
//省略其他方法...
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if (fi.getRequest() != null
&& fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null
&& this.observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
if (fi.getRequest() != null) {
fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
}
<2>、认证流程
发送一个请求的部分源码解读(按调用顺次解读):
(1)UsernamePasswordAuthenticationFilter :
该类主要是生成一个用户票据(包含用户信息,权限,是否认证成功),传递给AuthenticationManager进行认证校验。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
//省略部分代码...
//通过formLogin发起登陆请求主要逻辑
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//默认Post方式才接受
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//UsernamePasswordAuthenticationToken是Authentication的子类
//token中包含了用户名、密码、是否认证通过(此时为false),权限(此时为空)
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
//省略部分代码...
}
(2)ProviderManager:
该类主要是根据Authentication的类型交给能处理的Provider进行处理。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
//省略部分代码...
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//authentication有很多种,OAuth,Social等等...
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
Iterator var6 = this.getProviders().iterator();
while(var6.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
if (provider.supports(toTest)) {//根据authentication的类型匹配到合适的provider进行认证逻辑的校验
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
//校验核心逻辑
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var11) {
this.prepareException(var11, authentication);
throw var11;
} catch (InternalAuthenticationServiceException var12) {
this.prepareException(var12, authentication);
throw var12;
} catch (AuthenticationException var13) {
lastException = var13;
}
}
}
if (result == null && this.parent != null) {
try {
result = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var9) {
;
} catch (AuthenticationException var10) {
lastException = var10;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
this.eventPublisher.publishAuthenticationSuccess(result);
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
this.prepareException((AuthenticationException)lastException, authentication);
throw lastException;
}
}
//省略部分代码...
}
(3)AbstractUserDetailsAuthenticationProvider:
该类包含了认证流程的主要逻辑,获取用户信息,将已认证的用户凭证返回。
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
//...
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//重新获取用户信息
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
//预检查 若过期、锁定、冻结,则抛出相应异常
this.preAuthenticationChecks.check(user);
//检查密码的正确性,不正确则抛出BadCredentialsException
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
//提交前检查 检查密码是否过期,过期就抛出异常
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//最后一步:创建成功的认证信息并返回
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
//最后一步:创建成功的认证信息(认证状态为true,包含用户信息,用户权限)
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
//提交前检查(内部类) 检查密码是否过期,过期就抛出异常
private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
private DefaultPostAuthenticationChecks() {
}
public void check(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
}
}
}
//预检查(内部类) 若过期、锁定、禁用,则抛出相应异常
private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
private DefaultPreAuthenticationChecks() {
}
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {//账户是否锁定
AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is locked");
throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
} else if (!user.isEnabled()) {//账户是否禁用
AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is disabled");
throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
} else if (!user.isAccountNonExpired()) {//账户是否过期
AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is expired");
throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
}
}
}
//...
}
(3)DaoAuthenticationProvider :
该类获取用户信息的方式是通过可自定义的UserDetailsService进行加载用户信息的。
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
Object salt = null;
if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
}
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
//将提交上来的密码与数据库中的比对,传入盐值解密
if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
UserDetails loadedUser;
try {
//调用自定义获取用户信息的方法
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
} catch (UsernameNotFoundException var6) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
}
throw var6;
} catch (Exception var7) {
throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
}
}
(4)自定义UserDetailsService:
自定义获取用户信息。
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//...可进行一些数据库查询操作,作为返回值放入User中
return new User(s, encoderPwd, true, true, true, true, AuthorityUtils.createAuthorityList("admin"));
}
}
(5)User:
该类仅贴出它的属性值,对属性的获取,设置逻辑省略。
public class User implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = 420L;
//密码
private String password;
//用户名
private final String username;
//权限
private final Set<GrantedAuthority> authorities;
//账户是否过期
private final boolean accountNonExpired;
//账户是否锁定
private final boolean accountNonLocked;
//密码是否过期
private final boolean credentialsNonExpired;
//账户是否禁用
private final boolean enabled;
//...
}
(6)AbstractAuthenticationProcessingFilter
认证结束会进入该filter。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
this.successfulAuthentication(request, response, chain, authResult);
}
}
//成功认证
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
//将认证信息放入Security的上下文环境
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
//认证成功,调用successHandler(可继承并自定义)
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
//不成功认证
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication request failed: " + failed.toString(), failed);
this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
}
this.rememberMeServices.loginFail(request, response);
//认证失败,调用failureHandler(可继承并自定义)
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
}
<3>、获取当前登录信息
获取所有登陆信息:(包含IP,sessionId等一些其他信息)
@RestController
@RequestMapping("/user")
public class UserController {
//方式1
@GetMapping("/currentUserOne")
public Object getCurrentUserOne(){
return SecurityContextHolder.getContext().getAuthentication();
}
//方式2
@GetMapping("/currentUserTwo")
public Object getCurrentUserTwo(Authentication authentication){
return authentication;
}
}
返回结果:
{
"authorities": [
{
"authority": "admin"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": "1AA3FB90F026A178C5D73A3D4ED52B06"
},
"authenticated": true,
"principal": {
"password": null,
"username": "user",
"authorities": [
{
"authority": "admin"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"credentials": null,
"name": "user"
}
获得登陆的用户信息:
@RestController
@RequestMapping("/user")
public class UserController {
//方式1
@GetMapping("/currentUserOne")
public Object getCurrentUserOne(){
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
//方式2
@GetMapping("/currentUserTwo")
public Object getCurrentUserTwo(@AuthenticationPrincipal UserDetails userDetails){
return userDetails;
}
}
返回结果:
{
"password": null,
"username": "user",
"authorities": [
{
"authority": "admin"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
}