认证流程图
流程图详细说明
示例代码中为了简洁 已省略部分代码的展示
① UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter抽象类
所以调用的是父类AbstractAuthenticationProcessingFilter的doFilter方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
//...此处省略了部分代码 直接看最重要的
Authentication authResult;
try {
//因为attemptAuthentication被UsernamePasswordAuthenticationFilter重写
//所以这里调用了子类的方法 具体内容往下翻
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
//session处理策略 不重要
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch () {
//省略其他无关代码
//如果进入catch 这里会调用认证失败的方法
//首先会清空Security上下文保留的认证信息
//然后根据认证过程中出现的相应错误给出相应的提示
this.unsuccessfulAuthentication(request, response, var8);
return;
}
//以上过程都正常后 会调用认证成功的方法
//首先将认证信息保存在security上下文中
//SecurityContextHolder.getContext().setAuthentication(authResult);
//然后根据相应的配置做出认证成功处理 如跳转到网站首页 或 返回认证成功信息 等待
this.successfulAuthentication(request, response, chain, authResult);
}
}
UsernamePasswordAuthenticationFilter
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//这里校验了登陆请求是否是POST的请求 如果不是则报错
//ps:请求类型可配置 默认Post请求
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
//通过请求获得前端登陆表单中的username(登录名)及password(登陆密码)
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
//省略部分代码
username = username.trim();//去除登录名首尾两端的空格
//这里就是将登陆信息封装成Authentication(未认证)对象
//UsernamePasswordAuthenticationToken 实现了 Authentication类
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//将请求中的其他有效信息 比如头信息(header)中信息添加的Authentication(未认证)对象中去
this.setDetails(request, authRequest);
//这里就开始调用验证登陆方法了 进入流程图第二步
return this.getAuthenticationManager().authenticate(authRequest);
}
}
对于未认证对象的解释
Authentication(未认证)对象
通过UsernamePasswordAuthenticationToken提供的两个构造器可知
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal; //这个对象中保存了用户的认证信息
this.credentials = credentials;
this.setAuthenticated(false); //这里表示当前对象是否已认证 false为未认证
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal; //这个对象中保存了用户的认证信息
this.credentials = credentials;
super.setAuthenticated(true); //这里表示当前对象是否已认证 true为已认证
}
② AuthenticationManager
AuthenticationManager 这是一个接口 用于管理用户登陆的认证信息
public interface AuthenticationManager {
//以上return处就是调用了这个方法 而AuthenticationManager的默认实现类是ProviderManager
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
因为默认实现类是ProviderManager 所以默认调用了ProviderManager.authenticate方法进行验证
ProviderManager提供了多种认证方式 即多种AuthenticationProvider
如下
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//省略部分代码
//获取当前Authentication(未认证)对象的类型 即UsernamePasswordAuthenticationToken
Class<? extends Authentication> toTest = authentication.getClass();
//这里获取了所有ProviderManager提供的所有认证方式
//getProviders()返回 List<AuthenticationProvider>
Iterator var8 = this.getProviders().iterator();
//通过迭代器依次匹配 找到当前待验证对象的所匹配的认证方式
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
//如果当前认证方式适合认证UsernamePasswordAuthenticationToken类型的信息 则进入
if (provider.supports(toTest)) {
}
try {
//调用认证类AuthenticationProvider具体实现的校验方法
//这里说一下 AuthenticationProvider是一个接口 有多个实现类
//其中默认使用的实现类是DaoAuthenticationProvider
//具体内容在下一个小标题中
result = provider.authenticate(authentication);
if (result != null) {
//如果认证成功有返回结果了 就将认证完成信息放入原来的未认证对象中 使之成为认证对象
this.copyDetails(authentication, result);
break;
}
//以下是认证失败报错信息 已省略
} catch () {
}
}
}
}
③ DaoAuthenticationProvider
DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider抽象类
但是并没有重写其authenticate方法 所以以上provider.authenticate(authentication);走的是抽象类的authenticate方法 代码如下
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//省略部分代码
//前面的构造器那块内容有写过 authentication.getPrincipal()获取的是用户的认证信息
//这里很简单 就是获取了用户的登录名
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//DaoAuthenticationProvider实现了该方法
//走的是DaoAuthenticationProvider的方法 具体解释往下翻
//该方法主要是用来查询数据库拿到username的用户信息的
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
//略
}
}
try {
/**
* 因为UserDetails user对象里有四个方法
* boolean isAccountNonExpired(); 当前账号是否未过期
* boolean isAccountNonLocked(); 当前账号是否未锁定
* boolean isCredentialsNonExpired(); 密码是否未过期
* boolean isEnabled(); 是否激活
* 如果这4个方法中有一个是false 则报错了
* 所以这里校验的就是user对象这四个方法是否都为true
* 简单点说 就是校验账号状态是否正常
*/
this.preAuthenticationChecks.check(user);
/**
* 该方法用于校验前端输入的密码和retrieveUser返回的user对象密码是否一致
* 具体细节往下个代码块看
*/
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
//如果我们不是使用缓存存储用户登陆信息 上面代码报错 认证过程就会在这结束
if (!cacheWasUsed) {
throw var7;
}
//如果我们的账号密码是存储在缓存中 security会在上面的认证失败后再重加载一次最新的用户信息并进行比较
//如果比较成功了还是可以登陆的
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
//这里再校验了一次用户的登陆密码是否过期 原因是防止上面catch可能重新加载用户信息导致用户密码过期
this.postAuthenticationChecks.check(user);
//将用户信息放入容器缓存
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
//前面提到过 principal保存了用户的认证信息
Object principalToReturn = user;
//如果我们设置忽略用户的其他信息的话 principal就保存一个用户的用户名
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//走到这里就说明认证完成了 调用以下方法将原先的未认证对象设置为认证对象并返回
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
以上authenticate认证方法中调用的retrieveUser方法解析如下
该方法是用于加载数据库存储的用户信息的
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
//如果当前密码没加密 则进入该方法调用我们指定的方法进行密码加密
this.prepareTimingAttackProtection();
try {
//这里调用了UserDetailsService.loadUserByUsername()方法进行加载数据库用户信息
//正常是加载数据库信息的 在使用时我们都会重写security默认的loadUserByUsername方法
//通过登陆用户名查询数据库得到账号的信息并返回封装成UserDetails(接口)对象
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 () {
//省略报错信息代码
}
}
以上authenticate认证方法中调用的additionalAuthenticationChecks方法解析如下
该方法是用于校验登陆密码的
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
//省略校验信息
//(security5.x以上提供了多种加密方法 要求使用时必须指定一种 这个在配置文件中体现)
//拿到前面所说的未认证对象中封装的登陆密码(前端传过来的)通过我们指定的密码加密
//和userDetails(retrieveUser方法查询数据库得到的)的密码进行比较 如果匹配失败就报错
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"));
}
}
④ UserDetailsService
UserDetailsService是一个接口
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
常用实现类有两个:
- JdbcUserDetailsManager 使用jdbc查询数据库获取对象信息(需配置)
- InMemoryUserDetailsManager 在内存中加载用户信息 (需配置)
正常使用都是新建一个类去实现UserDetailsService接口并实现loadUserByUsername方法
如:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.查询数据库用户信息
User user = userMapper.get(username);
//判断是否有用户
if (user == null){
throw new UsernameNotFoundException("用户不存在");
}
//查询该用户的权限信息
List<String> permissionValueList = authorityMapper.get(username);
//将查出的用户信息拷贝至 存储到security上下文的用户类中
com.security.entity.User currentUser = new com.security.entity.User();
BeanUtils.copyProperties(user, currentUser);
//新建UserDetails实例
UserBean userBean = new UserBean();
//设置当前对象信息
userBean.setUser(currentUser);
//设置权限信息
userBean.setPermissionValueList(permissionValueList);
return userBean;
}
至此 认证源码介绍就结束了 感谢观看