虽然Spring Security有很多的拦截器,但是关键的拦截器其实并不多,本人通过查看源码和参照网上其他人的研究成功,大概总结出核心的Filter有如下几个(由于现在流行jwt格式的token认证,所以就不介绍session那块了)。
SecurityContextPersistenceFilter 这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),进来时候把数据放到SecurityContext里,出去时候把数据清除掉;
UsernamePasswordAuthenticationFilter 这个是校验用户登录的Filter,也就是Authentication(登录认证)
ExceptionTranslationFilter 这个从名字就能看出来是异常处理Filter,他的位置是登录认证之后,也就是说它主要是捕获登录异常的
FilterSecurityInterceptor 这个是权限校验的Filter,他依赖于AuthenticationManager(认证管理器,当然上面说的UsernamePasswordAuthenticationFilter 肯定也依赖于他),AccessDecisionManager(访问决策器,决定角色是否是权限访问资源),FilterInvocationSecurityMetadataSource(资源数据,具体就是角色和权限的关联表)
重点Filter:
UsernamePasswordAuthenticationFilter ,他的源码里没有doFilter方法,去他父类AbstractAuthenticationProcessingFilter里找
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);
}
}
其实也没什么东西,就是执行attemptAuthentication()方法,然后成功以后调用successfulAuthentication(),失败调用unsuccessfulAuthentication(),这次主要先梳理一下登录逻辑,不写的太细,先看一下attemptAuthentication()方法,这个方法就在UsernamePasswordAuthenticationFilter 这个类里面,上代码:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
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 authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
这个方法最主要的其实就一个getAuthenticationManager().authenticate(authRequest),这个就是验证用户是否合法的方法,然后追一下这个方法,这个方法是AuthenticationManager接口里的方法,是通过ProviderManager这个实现类来实现的,继续上源码
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
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)) {
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;
}
}
关键代码result = provider.authenticate(authentication),这个方法是AuthenticationProvider接口里的,实现类是AbstractUserDetailsAuthenticationProvider,看看这个实现类里的具体方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return 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
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 {
//检查user,比如是否锁定,是否过期等等
this.preAuthenticationChecks.check(user);
//检查密码是否正确,正确什么都不干,错误抛异常
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);
}
3个方法retrieveUser,preAuthenticationChecks,additionalAuthenticationChecks
retrieveUser 根据username查询user,这个方法是在子类DaoAuthenticationProvider里,其实这3个方法都是在里实现的
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
//关键方法,用于从数据库查询出user
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);看这个方法就能解释为什么从自己的数据库里拿用户的时候需要实现UserDetailsService这个接口了,因为用户数据就是从这个方法里拿的,另外为了逻辑不乱,我会在最后面贴一个自己实现的UserDetailsService接口的实现类
preAuthenticationChecks检查user是否合法,这个源码不贴了,实在没啥可看的,直接看additionalAuthenticationChecks,这个才是验证密码是否一致的方法,看源码
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
//这里为什么要先验证一下Credentials,不太清楚
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.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
长吧?看了半天终于验证,其实追了这么多源码,就是为了解释开头说的UsernamePasswordAuthenticationFilter这个Filter里的attemptAuthentication()方法。
最后贴一个自己实现UserDetailsService接口的实现类
@Service
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ISysUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("用户名:"+username);
UserVo userVo = userService.selectVoByLoginName(username);
if (userVo == null ) {
throw new UsernameNotFoundException(username);
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
StringBuilder roleIds = new StringBuilder();
Iterator<SysRole> iRole = userVo.getRolesList().iterator();
while (iRole.hasNext()){
roleIds.append(","+iRole.next().getId());
}
String roles = roleIds.toString().substring(1);
authorities.add(new SimpleGrantedAuthority(roles));
return new User(userVo.getLoginName(), userVo.getPassword(),authorities);
}
}
这个实现类通过username查询数据库中的用户,并且返回了一个User,这个User是Spring Security包里的User,实现了UserDetails接口,等于返回了一个UserDetails。不过也有一个疑问,这个authorities是干什么的呢?我也不太理解,就给放了角色的ID