1.认证流程分析
UsernamePasswordAuthenticationFilter
先看主要负责认证的过滤器UsernamePasswordAuthenticationFilter,有删减,注意注释。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; private String usernameParameter = "username"; private String passwordParameter = "password"; private boolean postOnly = true; public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); } 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中 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); //调用AuthenticationManager对象实现认证 return this.getAuthenticationManager().authenticate(authRequest); } } }
AuthenticationManager
由上面源码得知,真正认证操作在AuthenticationManager里面!
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { private static final Log logger = LogFactory.getLog(ProviderManager.class); private AuthenticationEventPublisher eventPublisher; private List<AuthenticationProvider> providers; protected MessageSourceAccessor messages; private AuthenticationManager parent; private boolean eraseCredentialsAfterAuthentication; //注意AuthenticationProvider这个对象,SpringSecurity针对每一种认证,什么qq登录啊, //用户名密码登陆啊,微信登录啊都封装了一个AuthenticationProvider对象。 public ProviderManager(List<AuthenticationProvider> providers) { this(providers, (AuthenticationManager)null); } public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); Iterator var8 = this.getProviders().iterator(); //循环所有AuthenticationProvider,匹配当前认证类型。 while(var8.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var8.next(); if (provider.supports(toTest)) { if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { //找到了对应认证类型就继续调用AuthenticationProvider对象完成认证业务。 result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (AccountStatusException var13) { this.prepareException(var13, authentication); throw var13; } catch (InternalAuthenticationServiceException var14) { this.prepareException(var14, authentication); throw var14; } catch (AuthenticationException var15) { lastException = var15; } } } if (result == null && this.parent != null) { try { result = parentResult = this.parent.authenticate(authentication); } catch (ProviderNotFoundException var11) { } catch (AuthenticationException var12) { parentException = var12; lastException = var12; } } if (result != null) { if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { ((CredentialsContainer)result).eraseCredentials(); } if (parentResult == null) { 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}")); } if (parentException == null) { this.prepareException((AuthenticationException)lastException, authentication); } throw lastException; } } }
AbstractUserDetailsAuthenticationProvider
咱们继续再找到AuthenticationProvider的实现类AbstractUserDetailsAuthenticationProvider:
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword"; private PasswordEncoder passwordEncoder; private volatile String userNotFoundEncodedPassword; private UserDetailsService userDetailsService; private UserDetailsPasswordService userDetailsPasswordService; protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { this.prepareTimingAttackProtection(); try { //重点来了!主要就在这里了! //可别忘了,咱们为什么要翻源码,是想用自己数据库中的数据实现认证操作啊! //UserDetails就是SpringSecurity自己的用户对象。 //this.getUserDetailsService()其实就是得到UserDetailsService的一个实现类 //loadUserByUsername里面就是真正的认证逻辑 //也就是说我们可以直接编写一个UserDetailsService的实现类,告诉SpringSecurity就可以了! //loadUserByUsername方法中只需要返回一个UserDetails对象即可 UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); //若返回null,就抛出异常,认证失败。 if (loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); } else { //若有得到了UserDetails对象,返回即可。 return loadedUser; } } catch (UsernameNotFoundException var4) { this.mitigateAgainstTimingAttack(authentication); throw var4; } catch (InternalAuthenticationServiceException var5) { throw var5; } catch (Exception var6) { throw new InternalAuthenticationServiceException(var6.getMessage(), var6); } } }
AbstractUserDetailsAuthenticationProvider
按理说到此已经知道自定义认证方法的怎么写了,但咱们把返回的流程也大概走一遍,上面不是说到返回了一个 UserDetails对象对象吗?
跟着它就又回到AbstractUserDetailsAuthenticationProvider对象中authenticate方法最后一行了。
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { public Authentication authenticate(Authentication authentication) throws AuthenticationException { //最后一行返回值,调用了createSuccessAuthentication方法,此方法就在下面! return this.createSuccessAuthentication(principalToReturn, authentication, user); } //咿!?怎么又封装了一次UsernamePasswordAuthenticationToken,开局不是已经封装过了吗? 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; } }
UsernamePasswordAuthenticationToken
来到UsernamePasswordAuthenticationToken对象发现里面有两个构造方法
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 510L; private final Object principal; private Object credentials; //认证成功前,调用的是这个带有两个参数的。 public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { super((Collection)null); this.principal = principal; this.credentials = credentials; this.setAuthenticated(false); } //认证成功后,调用的是这个带有三个参数的。 public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { //看看父类干了什么! super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); } }
AbstractAuthenticationToken
再点进去super(authorities)看看:
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer { private final Collection<GrantedAuthority> authorities; private Object details; private boolean authenticated = false; public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) { //这时两个参数那个分支! if (authorities == null) { this.authorities = AuthorityUtils.NO_AUTHORITIES; } else { //三个参数的,看这里! Iterator var2 = authorities.iterator(); //原来是多个了添加权限信息的步骤 GrantedAuthority a; do { if (!var2.hasNext()) { ArrayList<GrantedAuthority> temp = new ArrayList(authorities.size()); temp.addAll(authorities); this.authorities = Collections.unmodifiableList(temp); return; } a = (GrantedAuthority)var2.next(); } while(a != null); //若没有权限信息,是会抛出异常的! throw new IllegalArgumentException("Authorities collection cannot contain any null elements"); } } }
由此,咱们需要牢记自定义认证业务逻辑返回的UserDetails对象中一定要放置权限信息! 现在可以结束源码分析了?先不要着急! 咱们回到最初的地方UsernamePasswordAuthenticationFilter,你看好看了,这可是个过滤器,咱们分析这么 久,都没提到doFilter方法,你不觉得心里不踏实?可是这里面也没有doFilter呀?那就从父类找!
AbstractAuthenticationProcessingFilter
点开AbstractAuthenticationProcessingFilter,删掉不必要的代码!
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { //doFilter再次! 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 boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) { return this.requiresAuthenticationRequestMatcher.matches(request); } //成功走successfulAuthentication 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); } //认证成功,将认证信息存储到SecurityContext中! SecurityContextHolder.getContext().setAuthentication(authResult); //登录成功调用rememberMeServices this.rememberMeServices.loginSuccess(request, response, authResult); if (this.eventPublisher != null) { this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } this.successHandler.onAuthenticationSuccess(request, response, authResult); } //失败走unsuccessfulAuthentication 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); this.failureHandler.onAuthenticationFailure(request, response, failed); } }
可见AbstractAuthenticationProcessingFilter这个过滤器对于认证成功与否,做了两个分支,成功执行 successfulAuthentication,失败执行unsuccessfulAuthentication。
在successfulAuthentication内部,将认证信息存储到了SecurityContext中。并调用了loginSuccess方法,这就是常见的“记住我”功能!此功能具体应用,咱们后续再研究!
2.自定义认证实现流程
1.实现UserDetailService与自定义逻辑
自定义用户类需要实现UserDetails接口,并实现接口的方法,所以我们编写下述代码。
package com.yzxb.SpringSecurity.service; import com.yzxb.SpringSecurity.pojo.Role; import com.yzxb.SpringSecurity.pojo.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Objects; @Service public class MyUserService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1-本地mysql用户存在性查询 User user = selectUserFromDb(); if (Objects.isNull(user)) { throw new UsernameNotFoundException("用户不存在"); } // 2-本地查询权限 List<Role> roles = selectAuthFromDb(); // 3-设置权限信息 user.setRoles(roles); // 4-返回权限合集 return user; } /** * TODO 如果需要调用数据库查询,这里接入orm持久层框架即可 * @return 用户本地权限合集 */ private List<Role> selectAuthFromDb() { return new ArrayList<>(); } /** * TODO 如果需要调用数据库查询,这里接入orm持久层框架即可 * @return 本地用户信息 */ private User selectUserFromDb() { return new User(); } }
2.注册自定义实现类
package com.yzxb.SpringSecurity.config; import com.yzxb.SpringSecurity.service.MyUserService; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import javax.annotation.Resource; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private MyUserService myUserService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and().formLogin().loginPage("/login.html") .loginProcessingUrl("/doLogin") .defaultSuccessUrl("/demo/index ") .failureUrl("/login.html") .usernameParameter("uname") .passwordParameter("passwd") .permitAll() .and() .csrf() .disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserService); } }
改完的效果如下图
然后重启项目,就可以实现自定义的数据库认证逻辑。
3.完整代码获取
- git仓库地址:https://github.com/Herbbbb/SpringSecurity.git
- 分支名称:Day02-用户自定义认证