SpringSecurity登录认证流程
SpringSecurity登录认证流程
目录
SpringSecurity简介
springSecurity登录认证流程
一、Springsecurity简介
Spring Security是一个灵活和强大的身份验证和访问控制框架,以确保基于Spring的Java Web应用程序的安全,其中就是两方面的功能:认证和授权,认证意思是你是谁,授权意思是你能干什么。核心是通过一组过滤器,里面的每个过滤器去执行一种认证方式。
二、登录认证流程
我们知道,登录的整体流程是:用户点击登录从前端发起请求,后端接受前端发送来的用户名和密码,然后去数据库里面查询是否存在此用户,如果存在,就放行,让用户进入系统,如果不存在用户或者密码错误,则提示用户信息错误。那么在springsecurity中,也是大致遵循这个思路,只不过做了很多校验。下面就让我们一一解读一下。
获取到请求里传递来的用户名/密码之后,接下来就构造一个 UsernamePasswordAuthenticationToken 对象,传入 username 和 password,username 对应了 UsernamePasswordAuthenticationToken 中的 principal 属性,而 password 则对应了它的 credentials 属性。见下图
我们获得UsernamePasswordAuthenticationToken 对象后,会去执行authenticationManager的authenticae方法。当我们点authenticationManager会看到,它其实是一个接口,里面就只有一个authenticae空方法
那我们找到这个接口的实现类ProviderManager,里面有authenticae的实现方法,
首先获取到传进来的 authentication 的 Class,判断当前 provider 是否support该 authentication(标记1处)。
如果支持,则调用 provider 的 authenticate 方法(标记2处)开始做校验,校验完成后,会返回一个新的 Authentication。
这里的 provider 可能有多个,如果 provider 的 authenticate 方法没能正常返回一个 Authentication,则调用 provider 的 parent 的 authenticate 方法继续校验(标记3处)。
最主要的流程,就是这样,在 for 循环中,第一个 provider 是一个 AnonymousAuthenticationProvider,这个 provider 压根就不支持 UsernamePasswordAuthenticationToken这种认证,也就是会直接在 provider.supports 方法中返回 false,结束 for 循环,然后会进入到下一个 if 中,直接调用 parent 的 authenticate 方法进行校验。而 parent 就是 ProviderManager,所以会再次回到这个 authenticate 方法中。再次回到 authenticate 方法中,provider 也变成了 DaoAuthenticationProvider,这个 provider 是支持 UsernamePasswordAuthenticationToken 的,所以会顺利进入到该类的 authenticate 方法去执行,而 DaoAuthenticationProvider 继承自 AbstractUserDetailsAuthenticationProvider 并且没有重写 authenticate 方法,所以 我们最终来到 AbstractUserDetailsAuthenticationProvider#authenticate 方法中:
public Authentication authenticate(Authentication authentication)throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));// 从authentication里面拿到当前登录用户的用户名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 = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);}catch (UsernameNotFoundException notFound) {logger.debug("User '" + username + "' not found");if (hideUserNotFoundExceptions) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}else {throw notFound;}}Assert.notNull(user,"retrieveUser returned null - a violation of the interface contract");}try {//检查此用户中的各个账户状态属性是否正常,例如账户是否被禁用、账户是否被锁定、账户是否过期等等preAuthenticationChecks.check(user);//检查用户密码对比是否正确additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}catch (AuthenticationException exception) {if (cacheWasUsed) {// There was a problem, so try again after checking// we're using latest data (i.e. not from the cache)cacheWasUsed = false;user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);preAuthenticationChecks.check(user);additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}else {throw exception;}}postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();}return createSuccessAuthentication(principalToReturn, authentication, user);}
上述代码就是去校验登录用户的各个状态。
- 首先从 Authentication 提取出登录用户名。
- 然后通过拿着 username 去调用 retrieveUser 方法去获取当前用户对象,这一步会调用我们自己在登录时候的写的 loadUserByUsername 方法,所以这里返回的 user 其实就是你的登录对象
- 接下来调用 preAuthenticationChecks.check 方法去检验 user 中的各个账户状态属性是否正常,例如账户是否被禁用、账户是否被锁定、账户是否过期等等。
- additionalAuthenticationChecks 方法则是做密码比对的
- 最后在 postAuthenticationChecks.check 方法中检查密码是否过期。
- 最后,通过 createSuccessAuthentication 方法构建一个新的 UsernamePasswordAuthenticationToken。
下面我们依次展开来说,
- 首先是retrieveUser函数,这个函数的实现在DaoAuthenticationProvider类里面,因为这个类继承了AbstractUserDetailsAuthenticationProvider类,故retrieveUser函数会去数据库里面查找有没有用户名为username的用户,DaoAuthenticationProvider里面的retrieveUser实现代码如下:
protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {//this.getUserDetailsService()读取了框架中的UserDetailsService(这是个接口),进而去UserDetailsService里面执行loadUserByUsername方法,然而我们通常会写xxDetailsService来实现UserDetailsService接口,并注入到框架中,所以会去执行我们自己写的xxDetailsService里面的loadUserByUsername方法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);}
}
this.getUserDetailsService()读取了框架中的UserDetailsService(这是个接口),然后去UserDetailsService里面执行loadUserByUsername方法,然而我们通常会写xxDetailsService来实现UserDetailsService接口,并注入到框架中,所以会去执行我们自己写的xxDetailsService里面的loadUserByUsername方法。下面是我的接口实现类
public class UserDetailsServiceImpl implements UserDetailsService
{private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);@Autowiredprivate UserService userService;// @Autowired
// private SysPermissionService permissionService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{User user = userService.selectUserByUserName(username);if (StringUtils.isNull(user)){log.info("登录用户:{} 不存在.", username);throw new UsernameNotFoundException("登录用户:" + username + " 不存在");}return new LoginUser(user);}// public UserDetails createLoginUser(SysUuser user)
// {// return new LoginUser(user, permissionService.getMenuPermission(user));
// }
}
主要是userService.selectUserByUserName(username)方法,会去调用相应的service层和mapper层查询数据库,这里不再讲述。如果存在用户就重新new一个LoginUser,这个LoginUser是UserDetails的实现类,故可以直接返回。
2.preAuthenticationChecks.check()函数,这个函数会去检查我们账户的状态信息,例如账户是否被禁用、账户是否被锁定、账户是否过期,具体代码如下:
public void check(UserDetails user) {//检查账户是否锁定if (!user.isAccountNonLocked()) {throw new LockedException(messages.getMessage("AccountStatusUserDetailsChecker.locked", "User account is locked"));}//检查账户是否启用if (!user.isEnabled()) {throw new DisabledException(messages.getMessage("AccountStatusUserDetailsChecker.disabled", "User is disabled"));}//检查账户是否过期if (!user.isAccountNonExpired()) {throw new AccountExpiredException(messages.getMessage("AccountStatusUserDetailsChecker.expired","User account has expired"));}if (!user.isCredentialsNonExpired()) {throw new CredentialsExpiredException(messages.getMessage("AccountStatusUserDetailsChecker.credentialsExpired","User credentials have expired"));}
}
默认情况下,在我的UserDetails接口实现类LoginUser里面,重写了这些属性值,默认返回true就好。
3.additionalAuthenticationChecks()函数,这个函数会去匹配密码,当然是拿登录用户传进来的明文密码去匹配数据库里面的加密后的密码,具体代码如下
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();//将明文密码与之前从数据库里面获取的userDetails对象的密文密码做匹配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"));}
}
首先获得登录用户的明文密码 ,然后将明文密码与之前从数据库里面获取的userDetails对象的密文密码做匹配。匹配函数实现不做讲述。
最终通过createSuccessAuthentication(principalToReturn, authentication, user)方法,返回一个authentication,在这个方法里会去重新创建一个UsernamePasswordAuthenticationToken,将已认证状态标志注明。
将明文密码与之前从数据库里面获取的userDetails对象的密文密码做匹配。匹配函数实现不做讲述。
最终通过createSuccessAuthentication(principalToReturn, authentication, user)方法,返回一个authentication,在这个方法里会去重新创建一个UsernamePasswordAuthenticationToken,将已认证状态标志注明。
在这里插入图片描述
SpringSecurity登录认证流程相关推荐
- 【若依】开源框架学习笔记 07 - 登录认证流程(Spring Security 源码)
文章目录 一.概述 二.登录过程代码实现 三.用户验证流程(Spring Security 源码) 1.处理用户认证逻辑过滤器 `UsernamePasswordAuthenticationFilte ...
- 一文梳理SpringSecurity中的登录认证流程
前言 SpringSecurity作为一个出自Spring家族很强大的安全框架时长被引用到SpringBoot项目中用作登录认证和授权模块使用,但是对于大部分使用者来说都只停留在实现使用用户名和密码的 ...
- Security 登录认证流程详细分析 源码与图相结合
最近在写毕业设计的时候用这个框架,小伙伴给我提了个多种登录方式的需求,说仅仅只有账号.密码登录不太行,说让我增加几种方式,如:手机短信验证登录.邮箱验证登录.第三方登录等等(前两个已经实现,第三方登录 ...
- QQ第三方登录认证流程
一.注册成为QQ开发者 1.进入注册网站:https://connect.qq.com/index.html 2.登录QQ号,点击头像进入认证页面 3.选择"个人开发者",并如实填 ...
- 手把手带你在集成SpringSecurity的SpringBoot应用中添加短信验证码登录认证功能
本文目录 前言 1 自定义AuthenticationToken类 2 自定义AuthenticationProvider类 3 自定义MobilePhoneAuthenticationFilter ...
- 一文读懂 Shiro 登录认证全流程
一文读懂 Shiro 登录认证全流程 登录入口 执行登录 UserRealm Apache Shiro 是 Java 的一个安全框架.Shiro 可以帮助我们完成:认证.授权.加密.会话管理.与 We ...
- java用户的登录图片_Java 如何用 token 做用户登录认证
1.什么是 token??? Token 是服务端生成的一串字符串,以作客户端进行请求的一个令牌. 2.token 做用户登录认证 ● 流程 3. Java 实现 ● 用户登录生成 token,保存到 ...
- Shiro 登录认证源码详解
Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证.授权管理.企业级会话管理和加密等功能,相比 Spring Security 来说要更加的简单. 本文主要介绍 Shi ...
- java+登录window域认证网页_Java 如何用 token 做用户登录认证
1.什么是 token??? Token 是服务端生成的一串字符串,以作客户端进行请求的一个令牌. 2.token 做用户登录认证 ● 流程 3. Java 实现 ● 用户登录生成 token,保存到 ...
最新文章
- 刚体运动中变换矩阵的逆
- iOS - OC PList		数据存储
- Mybatis执行select语句无匹配对象时返回集为Empty还是null
- Android UI线程和非UI线程
- 微软Build 2016前瞻:让开发者编写能畅行所有设备的app
- centos 修改密码_centos7忘记root密码怎么改
- java logfaction_Java8 下 重构log
- [WP8.1UI控件编程]Windows Phone XAML页面的编译
- 跃迁 成为高手的技术
- java wifi 对讲机_freevoice(局域网对讲机)——Android4项目实战视频教程 - 移动编程 - 私塾在线 - 只做精品视频课程服务...
- 手把手教你将小米手机刷机!
- 还儿童一个健康上网环境,正式开启我的路由器URL网址白名单之旅
- bzoj5185 [Usaco2018 Jan]Lifeguards(dp+单调队列优化)
- Win10笔记本用雷电3接口外接显卡加速tensorflow深度学习步骤
- vs2017取消起始页(设定起始页)/(.ashx文件的添加)
- BIM模型文件下载——轻轨站模型
- 国家名称映射和省市名称映射
- 《转》atheros无线驱动之:系统初始化
- Docker 三大核心之容器 之一 docker ps
- 如何快速搜索多个文件中内容是否有你需要的东西?
热门文章
- 搭建瑞芯微rk3128本地android代码服务器
- Cookie和Token的区别
- 从使命召唤手游国际版将于暑假公测 谈论代理IP在游戏中的作用
- Java进阶专题(八) 设计模式之适配器模式、装饰者模式、观察者模式
- linux设备驱动归纳总结(三):6.poll和sellct【转】
- 在北京,几行代码实现看房自由!
- 每日新闻 | 未来量子通信成为可能虚拟办公将落地;英特尔推出10纳米基站芯片...
- mysql数据库导出命令_MYSQL 数据库导入导出命令
- Python 日期类型字符判断
- R语言学习笔记(excel表格读写、数据连接JION)