Shiro-Subject 分析

2024-08-21 00:32
文章标签 分析 shiro subject

本文主要是介绍Shiro-Subject 分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Subject反正就好像呈现的视图。所有Subject 都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
对于上面这句话的理解呢?怎么去理解这个很重要,看看别人的代码设计的流程也是比较的清楚的,Subject都绑定到了SecurityManager,因此我们在创建Subject的时候,必须给框架的内部绑定了一个SecurityManager,在前一个博客,我们已经基本的看了SecurityManager,大致的主要的架构,现在来看看Subject的主要的源码,学习一下别人这么写的用意何在?自己也是多多的总结很有很好,看看别人的优秀代码。
和上一个一样的
shrio.ini

[users]
zhang=123
wang=123
  Factory<org.apache.shiro.mgt.SecurityManager> factory =new IniSecurityManagerFactory("classpath:shiro.ini");//2、得到SecurityManager实例 并绑定给SecurityUtilsorg.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");try {//4、登录,即身份验证subject.login(token);} catch (AuthenticationException e) {//5、身份验证失败}Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录//6、退出subject.logout();

SecurityUtils:是一个非常关键的类,这里可以获取到我们的全局的资源,和当前的线程相关的,放置在ThreadLocal里面的,Subject也是如此哦,和当前的环境相关


package org.apache.shiro;import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;/*** Accesses the currently accessible {@code Subject} for the calling codedepending on runtime environment.*获取Subject,和当前的环境相关* @since 0.2*/
public abstract class SecurityUtils {/***ThreadContext 这里保存的是和线程相关的东西,这里只是个备份*感觉作用不是很大,这里只是用作在单线程的环境中* ONLY used as a 'backup' in VM Singleton environments (that is, standalone environments), since the* ThreadContext should always be the primarysource for Subject instances when possible.*/private static SecurityManager securityManager;public static Subject getSubject() {Subject subject = ThreadContext.getSubject();if (subject == null) {subject = (new Subject.Builder()).buildSubject();ThreadContext.bind(subject);}return subject;}//这里一般都是只在单线程中使用的,//获取这个一般在ThreadLoacal中获取,而不是这里哦public static void setSecurityManager(SecurityManager securityManager) {SecurityUtils.securityManager = securityManager;}//每次都是先去找线程相关的,然后没有在去在备份的staticpublic static SecurityManager getSecurityManager()throws UnavailableSecurityManagerException {SecurityManager securityManager = ThreadContext.getSecurityManager();if (securityManager == null) {securityManager = SecurityUtils.securityManager;}if (securityManager == null) {throw new UnavailableSecurityManagerException(msg);}return securityManager;}
}

如我们所知道的,设置securityManager,之后才能绑定到.子进程共享父进程的信息 ThreadLoacl http://blog.csdn.net/jiafu1115/article/details/7548605 这里讲的还不错。http://blog.csdn.net/feier7501/article/details/19088905 这里的例子 笔者也去试了一下子,这种用法太高级了。


public abstract class ThreadContext {// 这种唯一的Key设置值得学习一下哦,通过名字public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";//这里使用了InheritableThreadLocalMap//子线程会接收所有可继承的线程局部变量的初始值,//以获得父线程所具有的值。通常,子线程的值与父线程的值是一致的//这个就是比较高级的用法了,让子线程也可以获取到private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();protected ThreadContext() {}//这个每次获取的都是新的哦,线程安全的。public static Map<Object, Object> getResources() {return resources != null ? new HashMap<Object, Object>(resources.get()) : null;}private static Object getValue(Object key) {return resources.get().get(key);}public static Object get(Object key) {Object value = getValue(key);        return value;}public static void put(Object key, Object value) {if (key == null) {throw new IllegalArgumentException("key cannot be null");}if (value == null) {remove(key);return;}resources.get().put(key, value);}public static Object remove(Object key) {Object value = resources.get().remove(key);return value;}public static void remove() {resources.remove();}//获取总管家public static SecurityManager getSecurityManager() {return (SecurityManager) get(SECURITY_MANAGER_KEY);}public static void bind(SecurityManager securityManager) {if (securityManager != null) {put(SECURITY_MANAGER_KEY, securityManager);}}public static SecurityManager unbindSecurityManager() {return (SecurityManager) remove(SECURITY_MANAGER_KEY);}public static Subject getSubject() {return (Subject) get(SUBJECT_KEY);}public static void bind(Subject subject) {if (subject != null) {put(SUBJECT_KEY, subject);}}private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {protected Map<Object, Object> initialValue() {return new HashMap<Object, Object>();}/*** This implementation was added to address a* <a href="http://jsecurity.markmail.org/search/?q=#query:+page:1+mid:xqi2yxurwmrpqrvj+state:results">* user-reported issue</a>.* @param parentValue the parent value, a HashMap as defined in the {@link #initialValue()} method.* @return the HashMap to be used by any parent-spawned child threads (a clone of the parent HashMap).*/@SuppressWarnings({"unchecked"})protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {if (parentValue != null) {return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone();} else {return null;}}}
}

上面的当前线程的值,保存了总管家了,和Subject的信息。Subject和总管家之间的关系如何呢?这个看看创建Subject的时候怎么去处理的。一步步的解开谜底。
之前已经绑定总管家了

 //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)Subject subject = SecurityUtils.getSubject();

–>下一步从当前线程中获取Subject有没有?没有创建一个,通过Subject自己的Build设计模式,创建一个Subject,此时我们跟进Subject里面去看看。public interface Subject,Subject是个接口,Builder是一个内部静态类。这种用法你不会使用吧

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

Subject内部结构图可以看到Builder中和管家绑定有关系吧!而且这个接口有很多的权限的查看信息这个和管家里面的继承结构那有关系的,哈哈,代理的模式估计应该就是那样的。这种定义build可以值得学习,用起来比较爽,比如Okhttp好像也是这样的,模式哦很多的默认的参数,也可以自己设置自己喜欢的模式,进行处理。这个就是优点,比如android里面的Dialog的参数设置,你可以自己设置,也可以使用默认的参数。
这里写图片描述

  public static class Builder {/*** Hold all contextual data via the Builder instance's method invocations to be sent to the* {@code SecurityManager} during the {@link #buildSubject} call.数据保持器,在最后调用buildSubject的时候被使用。*/private final SubjectContext subjectContext;private final SecurityManager securityManager;/*** Constructs a new {@link Subject.Builder} instance, using the {@code SecurityManager} instance available*///这里使用了管家 SubjectContext 保存数据?被// sent to the {@code SecurityManager} to create a new {@code Subject} instance.public Builder() {this(SecurityUtils.getSecurityManager());}public Builder(SecurityManager securityManager) {if (securityManager == null) {throw new NullPointerException("null.");}this.securityManager = securityManager;this.subjectContext = newSubjectContextInstance();if (this.subjectContext == null) {throw new IllegalStateException("newSubjectContextInstance' " +"cannot be null.");}//这个有点意思了,保存当前管家的一个引用。this.subjectContext.setSecurityManager(securityManager);}/*** Creates a new {@code SubjectContext} instance tobe used to populate with subject contextual data that* will then be sent to the {@code SecurityManager}to create a new {@code Subject} instance.* @return a new {@code SubjectContext} instance*///这个有点意思,放置在管家中去创建一个Subjectprotected SubjectContext newSubjectContextInstance() {return new DefaultSubjectContext();}//让后代使用protected SubjectContext getSubjectContext() {return this.subjectContext;}public Builder sessionId(Serializable sessionId) {if (sessionId != null) {this.subjectContext.setSessionId(sessionId);}return this;}public Builder host(String host) {if (StringUtils.hasText(host)) {this.subjectContext.setHost(host);}return this;}......//这里才是真正的返回实例,这里调用了管家创建的方法//SubjectContext 创建的信息,反应到当前的信息当中去处理public Subject buildSubject() {return this.securityManager.createSubject(this.subjectContext);}}

DefaultSubjectContext的结构又是如何的?
public class DefaultSubjectContext extends MapContext implements SubjectContext
DefaultSubjectContext 中的信息字段是由MapContext这个类型安全的来维护的,DefaultSubjectContext 中的所有的字段的信息都是放置在Map中的去维护的,且可以指定返回类型的安全性,如果非法,触发异常。MapContext中主要是维护DefaultSubjectContext 中定义的字段的信息。
这里写图片描述
简单介绍 DefaultSubjectContext 中的信息维护都是这样的类型

//这样可以指定返回的类型哦,不对的话,触发异常public SecurityManager getSecurityManager() {return getTypedValue(SECURITY_MANAGER, SecurityManager.class);}
//非空插入哦public void setSecurityManager(SecurityManager securityManager) {nullSafePut(SECURITY_MANAGER, securityManager);}

MapContext设置得也是比较的精巧,获取的成员变量backingMap 是不允许直接引用的哦

    private final Map<String, Object> backingMap;public MapContext() {this.backingMap = new HashMap<String, Object>();}

不让外面直接的就引用,修改值。

public Set<String> keySet() {return Collections.unmodifiableSet(backingMap.keySet());}public Collection<Object> values() {return Collections.unmodifiableCollection(backingMap.values());}public Set<Entry<String, Object>> entrySet() {return Collections.unmodifiableSet(backingMap.entrySet());}

非空检查

  protected void nullSafePut(String key, Object value) {if (value != null) {put(key, value);}}

检查得到的结果,是不是期待的呢?类型安全
isAssignableFrom()方法是从类继承的角度去判断,instanceof()方法是从实例继承的角度去判断。
isAssignableFrom()方法是判断是否为某个类的父类,instanceof()方法是判断是否某个类的子类。
Class.isAssignableFrom()是用来判断一个类Class1和另一个类Class2是否相同或是另一个类的子类或接口。
我记得好像是在Java神书上面说过的。

    protected <E> E getTypedValue(String key, Class<E> type) {E found = null;Object o = backingMap.get(key);if (o != null) {if (!type.isAssignableFrom(o.getClass())) {String msg = "Invalid object found in SubjectContext“;throw new IllegalArgumentException(msg);}found = (E) o;}return found;}

说彪了,其实都是学习没关系的…
继续之前的Subject的内部类创建Subject的过程最后是
这个时候和我们的管家扯上关系了,我们知道管家的继承结构非常的复杂,里面的处理流程非常的多,最后的实现是在

 public Subject buildSubject() {return this.securityManager.createSubject(this.subjectContext);}

这里写图片描述
最后的一个管理者实现了创造subject的方法
DefaultSecurityManager,这里做了一些乱七八糟的东西很难懂,跟着业务..

  public Subject createSubject(SubjectContext subjectContext) {//create a copy so we don't modify the argument's backing map:SubjectContext context = copy(subjectContext);//ensure that the context has a SecurityManager instance, and if not, add one:context = ensureSecurityManager(context);//Resolve an associated Session (usually based on a referenced session ID),//place it in the context before//sending to the SubjectFactory.  The SubjectFactory should not need to//know how to acquire sessions as the//process is often environment specific - better to shield the SF from these details:context = resolveSession(context);//Similarly, the SubjectFactory should not require any concept of RememberMe - //translate that here first//if possible before handing off to the SubjectFactory:context = resolvePrincipals(context);//都是一些业务的逻辑,这里才是真正的创建Subject subject = doCreateSubject(context);//save this subject for future reference if necessary://(this is needed here in case rememberMe principals were //resolved and they need to be stored in the//session, so we don't constantly rehydrate the rememberMe //PrincipalCollection on every operation).//Added in 1.2://保存备份信息把,不用每次都这么麻烦save(subject);return subject;}

得到创建Subject的工厂,创建Subject

protected SubjectFactory subjectFactory;
public DefaultSecurityManager() {super();this.subjectFactory = new DefaultSubjectFactory();this.subjectDAO = new DefaultSubjectDAO();}
//调用的这里哦
protected Subject doCreateSubject(SubjectContext context) {return getSubjectFactory().createSubject(context);}

DefaultSubjectFactory 唯一的实现了SubjectFactory
SubjectContext 这个运输信息的,终于被弄出来了,然后呢,创建一个Subject的实现,这个是最终的目的。 DelegatingSubject 创建一个Subject的实现了

public Subject createSubject(SubjectContext context) {SecurityManager securityManager = context.resolveSecurityManager();Session session = context.resolveSession();boolean sessionCreationEnabled = context.isSessionCreationEnabled();PrincipalCollection principals = context.resolvePrincipals();boolean authenticated = context.resolveAuthenticated();String host = context.resolveHost();return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);}

然后就是subjectDao保存,这个不在去看了…

但是subject.login->使用的是实现类DelegatingSubject 中的总管家的的方法,然后总管家在调用内部的实现。调用内部的验证,在调用….这样的关系就拉上了。

这里写图片描述

这里写图片描述

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,将按照相应的顺序及策略进
行访问。
哈哈,这里这么多的东西,我还没开始了解呢!

这篇关于Shiro-Subject 分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1091608

相关文章

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

python-nmap实现python利用nmap进行扫描分析

《python-nmap实现python利用nmap进行扫描分析》Nmap是一个非常用的网络/端口扫描工具,如果想将nmap集成进你的工具里,可以使用python-nmap这个python库,它提供了... 目录前言python-nmap的基本使用PortScanner扫描PortScannerAsync异

Oracle数据库执行计划的查看与分析技巧

《Oracle数据库执行计划的查看与分析技巧》在Oracle数据库中,执行计划能够帮助我们深入了解SQL语句在数据库内部的执行细节,进而优化查询性能、提升系统效率,执行计划是Oracle数据库优化器为... 目录一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 S

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud