Shiro 登录认证源码详解

Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证、授权管理、企业级会话管理和加密等功能,相比 Spring Security 来说要更加的简单。

本文主要介绍 Shiro 的登录认证(Authentication)功能,主要从 Shiro 设计的角度去看这个登录认证的过程。

一、Shiro 总览

首先,我们思考整个认证过程的业务逻辑:

  1. 获取用户输入的用户名,密码;
  2. 从服务器数据源中获取相应的用户名和密码;
  3. 判断密码是否匹配,决定是否登录成功。

我们现在来看看 Shiro 是如何设计这个过程的:

img

图中包含三个重要的 Shiro 概念:SubjectSecurityManagerRealm。接下来,分别介绍这三者有何用:

  • Subject:表示“用户”,表示当前执行的用户。Subject 实例全部都绑定到了一个 SecurityManager 上,当和 Subject 交互时,它是委托给 SecurityManager 去执行的。
  • SecurityManager:Shiro 结构的心脏,协调它内部的安全组件(如登录,授权,数据源等)。当整个应用配置好了以后,大多数时候都是直接和 Subject 的 API 打交道。
  • Realm:数据源,也就是抽象意义上的 DAO 层。它负责和安全数据交互(比如存储在数据库的账号、密码,权限等信息),包括获取和验证。Shiro 支持多个 Realm,但是至少也要有一个。Shiro 自带了很多开箱即用的 Reams,比如支持 LDAP、关系数据库(JDBC)、INI 和 properties 文件等。但是很多时候我们都需要实现自己的 Ream 去完成获取数据和判断的功能。

登录验证的过程就是:Subject 执行 login 方法,传入登录的「用户名」和「密码」,然后 SecurityManager 将这个 login 操作委托给内部的登录模块,登录模块就调用 Realm 去获取安全的「用户名」和「密码」,然后对比,一致则登录,不一致则登录失败。

Shiro 详细结构

ShiroArchitecture

二、Shiro 登录示例

代码来自 Shiro 官网教程。Shiro 配置 INI 文件:

# ----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# ----------------------------------------------------------------------------
[users]
wang=123

测试 main 方法:

public static void main(String[] args) {log.info("My First Apache Shiro Application");//1.从 Ini 配置文件中获取 SecurityManager 工厂Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");//2.获取 SecurityManager 实例SecurityManager securityManager = factory.getInstance();//3.将 SecurityManager 实例绑定给 SecurityUtilsSecurityUtils.setSecurityManager(securityManager);//4.获取当前登录用户Subject currentUser = SecurityUtils.getSubject();//5.判断是否登录,如果未登录,则登录if (!currentUser.isAuthenticated()) {//6.创建用户名/密码验证Token(Web 应用中即为前台获取的用户名/密码)UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");try {//7.执行登录,如果登录未成功,则捕获相应的异常currentUser.login(token);} catch (UnknownAccountException uae) {log.info("There is no user with username of " + token.getPrincipal());} catch (IncorrectCredentialsException ice) {log.info("Password for account " + token.getPrincipal() + " was incorrect!");} catch (LockedAccountException lae) {log.info("The account for username " + token.getPrincipal() + " is locked.  " +"Please contact your administrator to unlock it.");}// ... catch more exceptions here (maybe custom ones specific to your application?catch (AuthenticationException ae) {//unexpected condition?  error?}}}

三、登录逻辑详解

Shiro 登录过程主要涉及到 Subject.login 方法,接下来我们将通过查看源码来分析整个登录过程。

  1. 创建 AuthenticationToken 接口的实例 token,比如例子中的 UsernamePasswordToken,包含了登录的用户名和密码;
  2. 获取当前用户 Subject,然后调用 Subject.login(AuthenticationToken) 方法;
  3. Subjectlogin 代理给 SecurityManagerlogin()

3.1 创建AuthenticationToken

第一步是创建 AuthenticationToken 接口的身份 token,比如例子中的 UsernamePasswordToken

package org.apache.shiro.authc;public interface AuthenticationToken extends Serializable {// 获取“用户名”Object getPrincipal();// 获取“密码”Object getCredentials();
}

3.2 获取当前用户并执行登录

获取的 Subject 当前用户是我们平时打交道最多的接口,有很多方法,但是这里我们只分析 login 方法。

package org.apache.shiro.subject;public interface Subject {void login(AuthenticationToken token) throws AuthenticationException;}

login 方法接受一个 AuthenticationToken 参数,如果登录失败则抛出 AuthenticationException 异常,可通过判断异常类型来知悉具体的错误类型。

接下来,分析 Subject 接口的实现类 DelegatingSubject 是如何实现 login 方法的:

public void login(AuthenticationToken token) throws AuthenticationException {clearRunAsIdentitiesInternal();// 代理给SecurityManagerSubject subject = securityManager.login(this, token);...
}

3.3 SecurityManager 接口

前面说过,整个 Shiro 安全框架的心脏就是 SecurityManager,我们看这个接口都有哪些方法:

package org.apache.shiro.mgt;public interface SecurityManager extends Authenticator, Authorizer, SessionManager {Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;void logout(Subject subject);Subject createSubject(SubjectContext context);
}

SecurityManager 包含很多内置的模块来完成功能,比如登录(Authenticator),权限验证(Authorizer)等。这里我们看到 SecurityManager 接口继承了 Authenticator 登录认证的接口:

package org.apache.shiro.authc;public interface Authenticator {public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)throws AuthenticationException;
}

那么,SecurityManager 的实现都是怎样来实现 Authenticator 接口的呢?答案是:使用了组合。SecurityManager 都拥有一个 Authenticator 的属性,这样调用 SecurityManager.authenticate 的时候,是委托给内部的 Authenticator 属性去执行的。

SecurityManager

3.4 SecurityManager.login 的实现

// DefaultSecurityManager.java
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info;try {info = authenticate(token);} catch (AuthenticationException ae) {try {onFailedLogin(token, ae, subject);} catch (Exception e) {if (log.isInfoEnabled()) {log.info("onFailedLogin method threw an " +"exception.  Logging and propagating original AuthenticationException.", e);}}throw ae; //propagate}Subject loggedIn = createSubject(token, info, subject);onSuccessfulLogin(token, info, loggedIn);return loggedIn;
}// AuthenticatingSecurityManager.java
/*** Delegates to the wrapped {@link org.apache.shiro.authc.Authenticator Authenticator} for authentication.*/
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {return this.authenticator.authenticate(token);
}
  1. 调用自己的 authenticate 方法执行登录;
  2. authenticate 方法中代理给 Authenticator 接口类型的属性去真正执行 authenticate(token) 方法。

3.5 Authenticator 登录模块

Authenticator 接口如下:

package org.apache.shiro.authc;public interface Authenticator {public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)throws AuthenticationException;
}

其实现类有 AbstractAuthenticatorModularRealmAuthenticator

Authenticator

下面来看看如何实现的 authenticate 方法:

// AbstractAuthenticator.java
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info;try {// 调用doAuthenticate方法info = doAuthenticate(token);if (info == null) {...}} catch (Throwable t) {...}...
}// ModularRealmAuthenticator.java
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {assertRealmsConfigured();Collection<Realm> realms = getRealms();if (realms.size() == 1) {// Realm唯一时return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);} else {return doMultiRealmAuthentication(realms, authenticationToken);}
}protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {if (!realm.supports(token)) {...}// 调用Realm的getAuthenticationInfo方法获取AuthenticationInfo信息AuthenticationInfo info = realm.getAuthenticationInfo(token);if (info == null) {...}return info;
}

从源码中可以看出,最后会调用 RealmgetAuthenticationInfo(AuthenticationToken) 方法。

3.6 Realm 接口

Realm 相当于数据源,功能是通过 AuthenticationToken 获取数据源中的安全数据,这个过程中可以抛出异常,告诉 shiro 登录失败。

package org.apache.shiro.realm;public interface Realm {// 获取 shiro 唯一的 realm 名称String getName();// 是否支持给定的 AuthenticationToken 类型boolean supports(AuthenticationToken token);// 获取 AuthenticationInfoAuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}

Shiro 自带了很多开箱即用的 Realm 实现,具体的类图如下:

Realm

3.7 总结

到此,我们把整个 Shiro 的登录认证流程分析了一遍。

  1. 创建 AuthenticationToken,然后调用 Subject.login 方法进行登录认证;
  2. Subject 委托给 SecurityManager
  3. SecurityManager 委托给 Authenticator 接口;
  4. Authenticator 接口调用 Realm 获取登录信息。

整个过程中,如果登录失败,就抛出异常,是使用异常来进行逻辑控制的。

四、登录密码的存储

  1. 页面使用 Https 协议;
  2. 页面传送密码时要先加密后再传输,最好是不可逆的加密算法(MD5,SHA2);
  3. 后端存储时要结合**盐(随机数)**一起加密存储;
  4. 使用不可逆的加密算法,而且可以加密多次;
  5. 把加密后的密码和盐一起存储到数据库;

五、学习 Shiro 源码感悟

  1. 从整体去思考框架的实现,带着业务逻辑去看实现逻辑;
  2. 不要抠细节,要看抽象,学习其实现方法;
  3. 首先看官方文档,官方文档一般会从整体设计方面去说明,遇到具体的接口再去看Javadoc文档;
  4. 结合类图等工具方便理解;

六、参考

  1. Apache Shiro
  2. 跟我学Shiro目录贴

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556653.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中3d库后接负载_Nature子刊:近红外光交联水凝胶用于活体生物3D打印

由于打印精度高、速度快&#xff0c;光固化3D打印成为组织再生等医学研究的有利工具&#xff0c;然而由于现在光固化3D打印普遍采用波长较短的光源&#xff0c;对组织损害较大且穿透力不高&#xff0c;这大大限制生物体内3D打印的发展。最近&#xff0c;来自意大利帕多瓦大学的…

怎么看调用的接口_Hadoop RPC调用实例分析

以ClientProtocol接口中的rename RPC调用进行一次实例分析。rename方法在ClientProtocol接口中定义&#xff0c;它的两个参数是String类型的&#xff0c;不能直接通过网络传输。我们看谁实现了ClientProtocol接口并重写rename方法。看到是ClientNamenodeProtocolTranslatorPB这…

父子结构查询_Java面试准备(5)之数据结构与算法——红黑树

欢迎点赞评论关注~~~~~~~如上图&#xff0c;二叉查找树极端情况下可能会变成一个单链表&#xff0c;这种查询时间复杂度就变成O(n)了&#xff0c;红黑树在二叉查找树的基础上进行了自平衡。1.原理分析如上图&#xff0c;红黑树具有以下特征&#xff1a;1. 每个节点要么是黑色&a…

中海达数据怎么转rinex_cors账号网最新实战教程,中海达 F61 Plus RTK连接千寻cors账号的方法...

RTK在工程测量工作中越来越流行&#xff0c;在这股潮流中&#xff0c;中海达品牌的F61 Plus因为融合了中海达多年的成熟建站技术HD-CORS&#xff0c;在工程测量工作中也受到了很大欢迎。它可以配套各种cors账号使用&#xff0c;使用过程中只需一台RTK移动站即可进行数据采集、施…

Shiro之UsernamePasswordTokenRememberMeAuthenticationTokenAuthenticationToken

继承关系 先看一下三者的继承关系&#xff0c;会有一个比较清楚的认识 AuthenticationToken AuthenticationToken 用于收集用户提交的身份&#xff08;如用户名&#xff09;及凭据&#xff08;如密码&#xff09;。Shiro会调用CredentialsMatcher对象的doCredentialsMatch方法对…

华为手机滑动速度设置_华为手机打字速度慢?开启这个设置,一分钟就能打200字...

很多朋友都说自己的打字速度非常慢&#xff01;每次使用手机聊天、记录工作事项大半天还没打一段字来。其实使用华为手机就能非常快速的码字&#xff0c;不管聊天还是工作&#xff0c;使用这个方法&#xff0c;一分钟就能打200字。1.聊天快速打字手机键盘因为体积的原因&#x…

SpringBoot 使用AOP功能

RPC&#xff0c;AOP都会用到代理&#xff0c;代理的技术有jdk的Proxy代理(必须实现接口)&#xff0c;cglib(可以不实现接口&#xff0c;直接实现类)&#xff0c;Javassist(jboss )而Spring boot本身也在方方面面使用了代理技术,在Spring中有两种动态代理方式&#xff0c;分别为…

cognos报表导出excel_有了这个报表工具,一键生成自定义的各种报表,还可以导出Excel...

EasyReport是一个简单易用的Web报表工具,它的主要功能是把SQL语句查询出的数据转换成报表页面&#xff0c; 同时支持表格的跨行(RowSpan)与跨列(ColSpan)配置。 同时它还支持报表Excel导出、图表显示及固定表头与左边列的功能。功能介绍本工具从数据库(MySQL,Oracle,SQLServer,…

java自定义注解实现日志功能

一、spring aop的通知类型 1、前置通知&#xff08;Before&#xff09;&#xff1a;在连接点前执行&#xff0c;不会影响连接点的执行&#xff0c;除非抛异常&#xff1b; 2、后置通知&#xff08;AfterReturning&#xff09;&#xff1a;在连接点正常执行完成后执行&#xff0…

java自定义注解annotation记录操作日志

说到注解我们平常用的可以说非常多啦&#xff0c;说几个常用的的注解 RestController Service Autowired 这些都是我们平常使用spring框架最常见的注解了&#xff0c;我们只知道它们非常好用&#xff0c;使用RestController 就能构建一个restful的控制器,Service 这个是我们常用…

数组concat_js 标准二维数组变一维数组的方法

问题&#xff1a;[[1, 1], [2, 3], [4, 5]] -> [1, 1, 2, 3, 4, 5]&#xff1f;方法一利用es5的arr.reduce(callback[, initialValue])实现var arr1 [[0, 1], [2, 3], [4, 5]]; var arr2 arr1.reduce(function (a, b) { return a.concat(b)} ); // arr2 [0, 1, 2, 3, 4, 5…

java 自定义注解+AOP实现日志记录

ssm版本&#xff1a; 1、首先自定义一个注解&#xff0c;该注解有两个属性&#xff0c;一个是模块名&#xff0c;一个是操作的内容。该注解是用来修饰Service层中的方法的。 2、创建一个切面类&#xff0c;该切面使用Aspect和Component注解修饰&#xff0c;该页面需要注入一个…

是人是谁_谁是白鹤滩最可爱的人

白鹤滩水电站是全球在建第一大水电站&#xff0c;主要特性指标均位居世界水电工程前列&#xff0c;2021年7月&#xff0c;首批机组投产发电将是白鹤滩工程为建党一百周年献礼的重大壮举。建设中的白鹤滩水电站工程建设不停步&#xff0c;白鹤滩水电站未来的运行管理者——白鹤滩…

potplayer 多个进程_进程组、会话、控制终端概念,如何创建守护进程?

守护进程概念&#xff1a;守护进程&#xff0c;也就是通常所说的Daemon进程&#xff0c;是Linux中的后台服务进程。周期性的执行某种任务或等待处理某些发生的事件。Linux系统有很多守护进程&#xff0c;大多数服务都是用守护进程实现的。比如&#xff1a;像我们的tftp&#xf…

acs880 用户手册_华中数控、广州数控系统用户手册

数控加工仿真系统 广州数控系统用户手册上海宇龙软件工程有限公司2004 年 5 月华中数控、广州数控系统用户手册 目录I目录第一章 基本操作 ............................................................................................ 1 1.1 项目文件 ...................…

亚马逊出的平板电脑_美国最畅销的安卓平板电脑,还只有2GB内存

在美国除了iPad&#xff0c;谁家的平板电脑卖得最好&#xff1f;不是华为小米&#xff0c;也不是微软或谷歌&#xff0c;而是Amazon亚马逊。主打入门级定位的亚马逊Fire系列平板电脑&#xff0c;在北美的平板市场上&#xff0c;有着举足轻重的地位。今天&#xff0c;亚马逊正式…

对接kafka_Kafka系列9:面试题是否有必要深入了解其背后的原理?我觉得应该刨根究底(上)...

​前言在本文开始之前&#xff0c;作者一直有个疑惑&#xff0c;就是面试题是只写写问题和答案就草草了事&#xff0c;还是应该深入分析一下其背后发生的一些原理。和朋友探讨以后作者还是决定采用后者的方式&#xff0c;因为我认为不仅要做到知其一&#xff0c;更要知其二&…

资源不足的情况怎么设置sparkrdd并行度_监控录像机资源不足或达到上限的原因及解决方法!...

在安装网络监控摄像机过程中&#xff0c;很多人遇到硬盘录像机画面上提升“资源不足”或性能“达到上限”的问题&#xff0c;新手遇到这样的问题会选择重启录像机&#xff0c;但是几次反复发现并不能解决。监控录像机资源不足或达到上限的原因及解决方法&#xff01;首先这种提…

主动变被动9个例句_高中英语 :主动表被动的适用范围

1. 表衡量的动词。The room measures 5 by 6.2. sell, write, wash, read, wear, keep, drink等表状态的情况下。The pen writes well. The clothes wash well.3. 在动词不定式中主语发出的动作。I have a lot of work to do.4. 动词不定式前面的词为形容词时。The question …

为什么要重写hashcode方法和equals方法

为什么要重写hashcode方法和equals方法 我们可能经常听到说重写equals方法必须重写hashcode方法&#xff0c;这是为什么呢&#xff1f;java中所有的类都是Object的子类&#xff0c;直接上object源码 /** Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights …