前言

shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。

spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖过于紧密,没有shiro使用简单。

shiro不依赖于spring,shiro不仅可以实现 web应用的权限管理,还可以实现c/s系统,分布式系统权限管理,shiro属于轻量框架,越来越多企业项目开始使用shiro。

Shiro运行流程学习笔记

项目中使用到了shiro,所以对shiro做一些比较深的了解。

也不知从何了解起,先从shiro的运行流程开始。

运行流程

  1. 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
  2. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
  3. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
  4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
  5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。

绑定线程

这里从看项目源码开始。

看第一步,Subject.login(token)方法。

UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
subject.login(token);

出现了一个UsernamePasswordToken对象,它在这里会调用它的一个构造函数。

public UsernamePasswordToken(final String username, final String password, final boolean rememberMe) {
this(username, password != null ? password.toCharArray() : null, rememberMe, null);
}

据笔者自己了解,这是shiro的一个验证对象,只是用来存储用户名密码,以及一个记住我属性的。

之后会调用shiro的一个工具类得到一个subject对象。

public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}

通过getSubject方法来得到一个Subject对象。

这里不得不提到shiro的内置线程类ThreadContext,通过bind方法会将subject对象绑定在线程上。

public static void bind(Subject subject) {
if (subject != null) {
put(SUBJECT_KEY, subject);
}
}
public static void put(Object key, Object value) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} if (value == null) {
remove(key);
return;
} ensureResourcesInitialized();
resources.get().put(key, value); if (log.isTraceEnabled()) {
String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
}

shirokey都是遵循一个固定的格式。

public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";

经过非空判断后会将值以KV的形式put进去。

当你想拿到subject对象时,也可以通过getSubject方法得到subject对象。

在绑定subject对象时,也会将securityManager对象进行一个绑定。

而绑定securityManager对象的地方是在Subject类的一个静态内部类里(可让我好一顿找)。

getSubject方法中的一句代码调用了内部类的buildSubject方法。

subject = (new Subject.Builder()).buildSubject();

PS:此处运用到了建造者设计模式,可以去菜鸟教程仔细了解

进去观看源码后可以看见。

首先调用无参构造,在无参构造里调用有参构造函数。

public Builder() {
this(SecurityUtils.getSecurityManager());
} public Builder(SecurityManager securityManager) {
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
}
this.securityManager = securityManager;
this.subjectContext = newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
"cannot be null.");
}
this.subjectContext.setSecurityManager(securityManager);
}

在此处绑定了securityManager对象。

当然,他也对securityManager对象的空状况进行了处理,在getSecurityManager方法里。

public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
SecurityManager securityManager = ThreadContext.getSecurityManager();
if (securityManager == null) {
securityManager = SecurityUtils.securityManager;
}
if (securityManager == null) {
String msg = "No SecurityManager accessible to the calling code, either bound to the " +
ThreadContext.class.getName() + " or as a vm static singleton. This is an invalid application " +
"configuration.";
throw new UnavailableSecurityManagerException(msg);
}
return securityManager;
}

真正的核心就在于securityManager这个对象。

SecurityManager

SecurityManager是一个接口,他继承了步骤里所谈到的AuthenticatorAuthorizer类以及用于Session管理的SessionManager

public interface SecurityManager extends Authenticator, Authorizer, SessionManager {

	Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;   

    void logout(Subject subject);

    Subject createSubject(SubjectContext context);
}

看一下它的实现。

且这些类和接口都有依次继承的关系。

Relam

接下来了解一下另一个重要的概念Relam

Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当与像用户帐户这类安全相关数据进行交互,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找很多内容。

从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件 等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

一般情况下,都会自定义Relam来使用。

先看一下实现。

以及自定义的一个UserRelam

看一下类图。

每个抽象类继承后所需要实现的方法都不一样。

public class UserRealm extends AuthorizingRealm

这里继承AuthorizingRealm,需要实现它的两个方法。

//给登录用户授权
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals); //这个抽象方法属于AuthorizingRealm抽象类的父类AuthenticatingRealm类 登录认证,也是登录的DAO操作所在的方法
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

之后再来看看这个验证方法,在之前的步骤里提到了,验证用到了Authenticator ,也就是第五步。

Authenticator

Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。

再回到之前登录方法上来看看。

subject.login(token)在第一步中调用了Subjectlogin方法,找到它的最终实现DelegatingSubject类。

里面有调用了securityManagerlogin方法,而最终实现就在DefaultSecurityManager这个类里。

Subject subject = securityManager.login(this, token);

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
}

之后就是验证流程,这里我们会看到第四步,点进去会到抽象类AuthenticatingSecurityManager。再看看它的仔细调用。

public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}

真正的调用Relam进行验证并不在这,而是在ModularRealmAuthenticator

他们之间是一个从左到右的过程。

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}

在这里咱们就看这个doSingleRealmAuthentication方法。

Relam验证。

protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" +
token + "]. Please ensure that the appropriate Realm implementation is " +
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
//在此处调用你自定义的Relam的方法来验证。
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " +
"submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
}
return info;
}

再看看多Relam的。

protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {

    AuthenticationStrategy strategy = getAuthenticationStrategy();

    AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

    for (Realm realm : realms) {

        aggregate = strategy.beforeAttempt(realm, token, aggregate);

        if (realm.supports(token)) {

            AuthenticationInfo info = null;
Throwable t = null;
try {
//调用自定义的Relam的方法来验证。
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
} aggregate = strategy.afterAttempt(realm, token, info, aggregate, t); } else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
} aggregate = strategy.afterAllAttempts(token, aggregate); return aggregate;
}

会发现调用的都是RelamgetAuthenticationInfo方法。

看到了熟悉的UserRelam,此致,闭环了。

但是也只是了解了大概的流程,对每个类的具体作用并不是很了解,所以笔者还是有很多地方要去学习,不,应该说我本来就是菜鸡,就要学才能变带佬。

最后

大家看完有什么不懂的可以在下方留言讨论,也可以关注我私信问我,我看到后都会回答的。也欢迎大家关注我的公众号:前程有光,马上金九银十跳槽面试季,整理了1000多道将近500多页pdf文档的Java面试题资料放在里面,助你圆梦BAT!文章都会在里面更新,整理的资料也会放在里面。谢谢你的观看,觉得文章对你有帮助的话记得关注我点个赞支持一下!

厉害啊!第一次见到把Shiro运行流程写的这么清楚的,建议收藏起来慢慢看的更多相关文章

  1. shiro原理及其运行流程介绍

    shiro原理及其运行流程介绍 认证执行流程 1.通过ini配置文件创建securityManager 2.调用subject.login方法主体提交认证,提交的token 3.securityMan ...

  2. 使用 paramsPrepareParamsStack 拦截器栈后的运行流程

    2. 使用 paramsPrepareParamsStack 拦截器栈后的运行流程 1). paramsPrepareParamsStack 和 defaultStack 一样都是拦截器栈. 而 st ...

  3. SpringMVC 运行流程以及与Spring 整合

    1. 运行流程 2. Spring 和 SpringMVC 整合 // 1. 导入 jar 包 // 2. 配置 web.xml <!-- 配置 Spring 的核心监听器 --> < ...

  4. 宜信开源|分布式任务调度平台SIA-TASK的架构设计与运行流程

    一.分布式任务调度的背景 无论是互联网应用或者企业级应用,都充斥着大量的批处理任务.我们常常需要一些任务调度系统来帮助解决问题.随着微服务化架构的逐步演进,单体架构逐渐演变为分布式.微服务架构.在此背 ...

  5. 身份认证系统(四)OAuth2运行流程

    上一节介绍过什么是OAuth2,这节准备用生动的事例来告诉大家OAuth2运行的流程. 我们来想这样一个场景:假设我们有一个叫做万方网盘的服务是用来帮助用户存储论文文档的,我们向外提供了符合OAuth ...

  6. 【转】 Python生成器generator之next和send运行流程

    原文链接:https://blog.csdn.net/pfm685757/article/details/49924099 对于普通的生成器,第一个next调用,相当于启动生成器,会从生成器函数的第一 ...

  7. react-native start 运行流程

    在CMD下键入 C:\Node_JS\MyAwesomeProject>react-native start 运行流程: C:\Users\Grart\AppData\Roaming\npm\r ...

  8. 1、CC2541蓝牙4.0芯片中级教程——基于OSAL操作系统的运行流程了解+定时器和串口例程了解

    本文根据一周CC2541笔记汇总得来—— 适合概览和知识快速索引—— 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 ...

  9. java里的分支语句--程序运行流程的分类(顺序结构,分支结构,循环结构)

    JAVA里面的程序运行流程分三大类: 1,顺序结构:顺序结构就是依次执行每一行代码 2,分支结构:分支结构就是按不同的条件进行分支 3,循环结构:一段代码依条件进行循环执行. 其中,分支结构有两大类: ...

随机推荐

  1. 蓝桥杯2020 E:七段码

    题解 正规解法是 dfs + 并查集,首先用 dfs 将其所有的情况枚举出来,再用并查集来判断是否在一个连通块上. 许多小伙伴计算的答案为76,主要是判断连通块这方面有问题,倘若不用并查集,直接枚举一 ...

  2. springcloud中使用dubbo开发rpc服务及调用

    spring cloud中基于springboot开发的微服务,是基于http的rest接口,也可以开发基于dubbo的rpc接口. 一,创建goodsService模块 1, 在创建的goodsSe ...

  3. android自带下拉刷新SwipeRefreshLayout

    也是一个布局容器,只有一个子组件,类似scrollView <?xml version="1.0" encoding="utf-8"?> <a ...

  4. poj 2229 一道动态规划思维题

    http://poj.org/problem?id=2229 先把题目连接发上.题目的意思就是: 把n拆分为2的幂相加的形式,问有多少种拆分方法. 看了大佬的完全背包代码很久都没懂,就照着网上的写了动 ...

  5. day05 selenium基本使用

    本文通过举例介绍selenium的基本使用方法,用来爬取京东笔记本电脑的商品信息,包括名称,url,价格,评价信息. from selenium import webdriver # 导入键盘Keys ...

  6. ASP.NET Core Authentication系列(一)理解Claim, ClaimsIdentity, ClaimsPrincipal

    前言 首先我们来看一下在ASP.NET时代,Authentication是如何使用的.下面介绍的是System.Web.Security.FormsAuthentication: // 登录 Syst ...

  7. Scipy 学习第3篇:数字向量的距离计算

    计算两个数字向量u和v之间的距离函数 1,欧氏距离(Euclidean distance) 在数学中,欧几里得距离或欧几里得度量是欧几里得空间中两点间"普通"(即直线)距离.使用这 ...

  8. Hash 哈希(上)

    Hash 哈希(上) 目录 Hash 哈希(上) 简介 Hash函数的构造 取余法 乘积取整法 其他方法 冲突的处理 挂链法 开放定址法 线性探查法 二次探查法 双哈希法 结语 简介 Hash,又称散 ...

  9. Java的类加载器有几种?什么是双亲委派机制?

    一.JAVA类加载器包括几种? 启动类加载器 bootstrap class loader 启动类加载器主要加载的是JVM自身需要的类,这个类加载是用C++语言实现的,是虚拟机自身的一部分,它负责将 ...

  10. STC转STM32第一次开发

    目录 前言 项目 1. 模数转换,并通过OLED屏显示出来 需求: 实验器材: 接线: 源程序: 成品: 2. 简易频率计(0.1-10MHZ) 需求: 原理: 实验器材: 接线: 源程序: 写在结尾 ...