在前面两节Spring security (一)架构框架-Component、Service、Filter分析Spring Security(二)--WebSecurityConfigurer配置以及filter顺序为Spring Security认证作好了准备,可以让我们更好的理解认证过程以及项目代码编写。

1.认证过程工作流程

认证工作流程:

 AbstractAuthenticationProcessingFilter
doFilter()(attemptAuthentication()获取Authentication实体)
->UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子类)
attemptAuthentication() (在UsernamePasswordAuthenticationToken()中将username 和 password 生成 UsernamePasswordAuthenticationToken对象,getAuthenticationManager().authenticate进行认证以及返回获取Authentication实体)
->AuthenticationManager
->ProviderManager()(AuthenticationManager接口实现)
authenticate()(AuthenticationProvider.authenticate()进行认证并获取Authentication实体)
->AbstractUserDetailsAuthenticationProvider(内置缓存机制,如果缓存中没有用户信息就调用retrieveUser()获取用户)
authenticate() (获取Authentication实体需要userDetails,在缓存中或者retrieveUser()获取userDetails;验证additionalAuthenticationChecks(); createSuccessAuthentication()生成Authentication实体)
->DaoAuthenticationProvider
retrieveUser() (调用自定义UserDetailsService中loadUserByUsername()加载userDetails)
->UserDetailsService
loadUserByUsername()(获取userDetails)

具体流程请看下面小节。

1.1:请求首先经过过滤器AbstractAuthenticationProcessingFilter以及UsernamePasswordAuthenticationFilter进行处理

  当请求来临时,在默认情况下,请求先经过AbstractAuthenticationProcessingFilter的子类UsernamePasswordAuthenticationFilter过滤器。在UsernamePasswordAuthenticationFilter过滤器调用attemptAuthentication()方法现实主要的两步过程:

  1. 创建拥有用户的详情信息的Authentication对象,在默认的UsernamePasswordAuthenticationFilter中将创建UsernamePasswordAuthenticationToken的Authentication对象;

  2. AuthenticationManager调用authenticate()方法进行认证过程,在默认情况,使用ProviderManager类进行认证。

UsernamePasswordAuthenticationFilter源码分析:

    public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
....
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
.....
//1.创建拥有用户的详情信息的Authentication对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);

// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//2.AuthenticationManager进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
...
}  

1.2请求经过过滤器处理之后,在AuthenticationManager以及ProviderManager认证

  在UsernamePasswordAuthenticationFilter中看出,将调用AuthenticationManager接口的authenticate()方法进行详细认证。默认情况将使用AuthenticationManager子类ProviderManager的authenticate()进行认证,可以分成三个主要过程:

  1. AuthenticationProvide.authenticate()进行认证,默认下,将使用AbstractUserDetailsAuthenticationProvider进行认证;

  2. 认证成功后,从authentication中删除凭据和其他机密数据,否则抛出异常或者认证失败;

  3. 发布认证成功事件,并将Authentication对象保存到security context中。

ProviderManager源码分析:

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
...
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
...
//AuthenticationProvider依次进行认证
for (AuthenticationProvider provider : getProviders()) {
...
try {
//1.1进行认证,并返回Authentication对象
result = provider.authenticate(authentication);

if (result != null) {
copyDetails(authentication, result);
break;
}
}
...
catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
//1.2如果1.1认证中没有一个验证通过,则使用父类型AuthenticationManager进行验证
result = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = e;
}
}
//2.从authentication中删除凭据和其他机密数据
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
//3.发布认证成功事件,并将Authentication对象保存到security context中
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
}

1.3 认证过程详细处理:AuthenticationProvider、AbstractUserDetailsAuthenticationProvider以及DaoAuthenticationProvider

  在默认认证详细处理过程中,AuthenticationProvider认证由AbstractUserDetailsAuthenticationProvider抽象类以及AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider进行方法重写协助共同工作进行认证的。主要可以分成以下步骤:

  1. 获取用户信息UserDetails,首先从缓存中读取信息,如果缓存中没有的化,在UserDetailsService中加载,其最主要可以从我们自定义的UserDetailsService进行读取用户信息UserDetails;

  2. 验证三步走: 1). preAuthenticationChecks

    2). additionalAuthenticationChecks:使用PasswordEncoder.matches()方法进行认证,其验证方式中验证数据已经过PasswordEncoder算法加密,可以通过实现PasswordEncoder接口来定义算法加密方式。

    3). postAuthenticationChecks

  3. 将已通过验证的用户信息封装成 UsernamePasswordAuthenticationToken对象并返回;该对象封装了用户的身份信息,以及相应的权限信息。

  AbstractUserDetailsAuthenticationProvider主要功能提供authenticate()认证方法以及给DaoAuthenticationProvider重写方法源码分析:

  
 public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
...
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
...
boolean cacheWasUsed = true;
//1.1获取缓存中UserDetails信息
UserDetails user = this.userCache.getUserFromCache(username);
//1.2 如果缓存中没有信息,从UserDetailsService中获取
if (user == null) {
cacheWasUsed = false; try {
//使用DaoAuthenticationProvider中重写的方法去获取信息
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}catch{
...
}
...
try {
//进行检验认证
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}catch{
...
}
...
postAuthenticationChecks.check(user);
....
// 将已通过验证的用户信息封装成 UsernamePasswordAuthenticationToken对象并返回
return createSuccessAuthentication(principalToReturn, authentication, user);
}

  DaoAuthenticationProvider功能主要为认证凭证加密PasswordEncoder,以及重写AbstractUserDetailsAuthenticationProvider抽象类的retrieveUser、additionalAuthenticationChecks方法,其中retrieveUser主要是获取UserDetails信息,源码分析

    protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//根据UserDetailsService获取UserDetails信息,从自定义的UserDetailsService获取
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}

additionalAuthenticationChecks主要使用PasswordEncoder进行密码验证,源码分析:

protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");

throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}

String presentedPassword = authentication.getCredentials().toString();
//进行密码验证
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");

throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}

1.4 认证中所需的认证凭证获取:UserDetailsService

  在认证中必须获取认证凭证,从UserDetailsService获取到认证凭证,UserDetailsService接口只有一个方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

通过用户名 username 调用方法 loadUserByUsername 返回了一个UserDetails接口对象:

public interface UserDetails extends Serializable {
//1.权限集合
Collection<? extends GrantedAuthority> getAuthorities();
//2.密码
String getPassword();
//3.用户名
String getUsername();
//4.用户是否过期
boolean isAccountNonExpired();
//5.是否锁定
boolean isAccountNonLocked();
//6.用户密码是否过期
boolean isCredentialsNonExpired();
//7.账号是否可用(可理解为是否删除)
boolean isEnabled();
}

我们通过实现UserDetailsService自定义获取UserDetails类,可以从不同数据源中获取认证凭证。

1.5 总结

总结Spring Security(二)--WebSecurityConfigurer配置以及filter顺序和本节Spring security(三)想要实现简单认证过程:

  1. 第一步:配置WebSecurityConfig

  2. 第二步: 实现自定义UserDetailsService,自定义从数据源码获取认证凭证。

2 Spring boot与Spring security整合

2.1配置WebSecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
//super.configure(http);
http .csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login/form")
.failureUrl("/login-error")
.permitAll() //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
.and()
.logout().permitAll();
}
}

2.2 UserDetailsService实现

@service
public class CustomUserService implements UserDetailsService {
@Autowired
private UserInfoMapper userInfoMapper;
@Autowired
private PermissionInfoMapper permissionInfoMapper;
@Autowired
private BCryptPasswordEncoderService bCryptPasswordEncoderService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
//这里可以可以通过username(登录时输入的用户名)然后到数据库中找到对应的用户信息,并构建成我们自己的UserInfo来返回。
UserInfoDTO user = userInfoMapper.getUserInfoByUserName(username);
if (user != null) {
List<PermissionInfoDTO> permissionInfoDTOS = permissionInfoMapper.findByAdminUserId(userInfo.getId());
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
for (PermissionInfoDTO permissionInfoDTO : permissionInfoDTOS) {
if (permissionInfoDTO != null && permissionInfoDTO.getPermissionName() != null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(
permissionInfoDTO.getPermissionName());
grantedAuthorityList.add(grantedAuthority);
}
}
return new User(userInfo.getUserName(), bCryptPasswordEncoderService.encode(userInfo.getPasswaord()), grantedAuthorityList);
}else {
throw new UsernameNotFoundException("admin" + username + "do not exist");
}
}
}

往期文章:

各位看官还可以吗?喜欢的话,动动手指点个赞

【权限管理系统】Spring security(三)---认证过程(原理解析,demo)的更多相关文章

  1. Spring Security 接口认证鉴权入门实践指南

    目录 前言 SpringBoot 示例 SpringBoot pom.xml SpringBoot application.yml SpringBoot IndexController SpringB ...

  2. 最简单易懂的Spring Security 身份认证流程讲解

    最简单易懂的Spring Security 身份认证流程讲解 导言 相信大伙对Spring Security这个框架又爱又恨,爱它的强大,恨它的繁琐,其实这是一个误区,Spring Security确 ...

  3. Spring Security(三) —— 核心配置解读

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-3/ 「老徐」欢迎转载,保留摘要,谢谢! 3 核心配置解读 上一篇文章<Spring Secu ...

  4. Spring Cloud实战 | 第九篇:Spring Cloud整合Spring Security OAuth2认证服务器统一认证自定义异常处理

    本文完整代码下载点击 一. 前言 相信了解过我或者看过我之前的系列文章应该多少知道点我写这些文章包括创建 有来商城youlai-mall 这个项目的目的,想给那些真的想提升自己或者迷茫的人(包括自己- ...

  5. Spring Boot干货系列:(三)启动原理解析

    Spring Boot干货系列:(三)启动原理解析 2017-03-13 嘟嘟MD 嘟爷java超神学堂 前言 前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说 ...

  6. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  7. SpringBoot Spring Security 核心组件 认证流程 用户权限信息获取详细讲解

    前言 Spring Security 是一个安全框架, 可以简单地认为 Spring Security 是放在用户和 Spring 应用之间的一个安全屏障, 每一个 web 请求都先要经过 Sprin ...

  8. Spring Security三种认证

    Spring Security: 1.用户名+密码认证 2.手机号+短信认证 Spring Social: 1.第三方认证, QQ登录等 Spring Security OAuth: 1.把认证之后的 ...

  9. Spring Security 安全认证

    Spring Boot 使用 Mybatis 依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> ...

  10. Spring Security 入门(1-5)Spring Security - 匿名认证

    匿名认证 对于匿名访问的用户,Spring Security 支持为其建立一个匿名的 AnonymousAuthenticationToken 存放在 SecurityContextHolder 中, ...

随机推荐

  1. 用Vsftpd服务传输文件(铺垫篇)

    文件传输协议 文件传输协议(FTP,File Transfer Protocol),即能够让用户在互联网中上传.下载文件的文件协议,而FTP服务器就是支持FTP传输协议的主机,要想完成文件传输则需要F ...

  2. 『题解』LibreOJ6277 数列分块入门 1

    更好的阅读体验 Portal Portal1: LibreOJ Description 给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,单点查值. Input 第一行输入一个数字 ...

  3. JMeter的安装部署——Linux系统

    1.配置Java环境 在官网https://www.oracle.com/technetwork/java/javase/downloads/jdk10-downloads-4416644.html下 ...

  4. thinkphp5中取消了3.2版本中的单字母函数,初用tp5可能不大适应,下边给出两者的对应参照表,以便查阅。

    3.2版本 5.0版本 C config E exception G debug L lang T 废除 I input N 废除 D model M db A controller R action ...

  5. Java编程思想——第14章 类型信息(二)反射

    六.反射:运行时的类信息 我们已经知道了,在编译时,编译器必须知道所有要通过RTTI来处理的类.而反射提供了一种机制——用来检查可用的方法,并返回方法名.区别就在于RTTI是处理已知类的,而反射用于处 ...

  6. Unix, Linux以及NT内核和它们各自衍生的系统关系图

  7. 并行模式之Master-Worker模式

    并行模式之Master-Worker模式 一).Master-Worker模式 作用: 将一个大任务分解成若干个小任务,分发给多个子线程执行. 注: 将大任务分解成小任务,小任务的实现逻辑要相同. 二 ...

  8. Openlayers ol.interaction.Select传值问题

    说明: 在使用ol.interaction.Select时,我定义的变量作用域作用不到其回调函数里,但我在select结果中,需要用到这些变量 解决方案: 虽想了个办法解决了,但不知道是不是合理:是否 ...

  9. ASP使用ajax来传递中文参数的编码处理

    背景 asp的第一版是0.9测试版,自从1996年ASP1.0诞生,迄今20余载.虽然asp在Windows2000 IIS服务5.0所附带的ASP 3.0发布后好像再没有更新过了,但是由于其入手简单 ...

  10. 胜利点组——“萌猿填词”微信小程序评价

    此作业要求参见https://edu.cnblogs.com/campus/nenu/2019fall/homework/9860 1.根据(不限于)NABCD评论作品的选题; (1).你的创意解决了 ...