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-用户自定义认证