我们在平常使用Shrio进行身份认证时,经常通过获取Subject 对象中保存的Session、Principal等信息,来获取认证用户的信息,也就是说Shiro会把认证后的用户信息保存在Subject 中供程序使用

  1. public static Subject getSubject()
  2. {
  3. return SecurityUtils.getSubject();
  4. }

Subject 是Shiro中核心的也是我们经常用到的一个对象,那么Subject 对象是怎么构造创建,并如何存储绑定供程序调用的,下面我们就对其流程进行一下探究,首先是Subject 接口本身的继承与实现,这里我们需要特别关注下WebDelegatingSubject这个实现类,这个就是最终返回的具体实现类

一、Subject的创建

在Shiro中每个http请求都会经过SpringShiroFilter的父类AbstractShiroFilte中的doFilterInternal方法,我们看下具体代码

  1. protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
  2. throws ServletException, IOException {
  3.  
  4. Throwable t = null;
  5.  
  6. try {
  7. final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
  8. final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
  9.  
  10. //创建Subject
  11. final Subject subject = createSubject(request, response);
  12.  
  13. //执行Subject绑定
  14. //noinspection unchecked
  15. subject.execute(new Callable() {
  16. public Object call() throws Exception {
  17. updateSessionLastAccessTime(request, response);
  18. executeChain(request, response, chain);
  19. return null;
  20. }
  21. });
  22. } catch (ExecutionException ex) {
  23. t = ex.getCause();
  24. } catch (Throwable throwable) {
  25. t = throwable;
  26. }
  27.  
  28. if (t != null) {
  29. if (t instanceof ServletException) {
  30. throw (ServletException) t;
  31. }
  32. if (t instanceof IOException) {
  33. throw (IOException) t;
  34. }
  35. //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
  36. String msg = "Filtered request failed.";
  37. throw new ServletException(msg, t);
  38. }
  39. }

继续进入createSubject方法,也就是创建Subject对象的入口

  1. protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
  2. return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
  3. }

这里使用了build的对象构建模式,进入WebSubject接口中查看Builder与buildWebSubject()的具体实现

Builder()中主要用于初始化SecurityManager 、ServletRequest 、ServletResponse 等对象,构建SubjectContext上下文关系对象

  1. */
  2. public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
  3. super(securityManager);
  4. if (request == null) {
  5. throw new IllegalArgumentException("ServletRequest argument cannot be null.");
  6. }
  7. if (response == null) {
  8. throw new IllegalArgumentException("ServletResponse argument cannot be null.");
  9. }
  10. setRequest(request);
  11. setResponse(response);
  12. }

buildWebSubject方法中开始构造Subject对象

  1. public WebSubject buildWebSubject() {
  2. Subject subject = super.buildSubject();//父类build方法
  3. if (!(subject instanceof WebSubject)) {
  4. String msg = "Subject implementation returned from the SecurityManager was not a " +
  5. WebSubject.class.getName() + " implementation. Please ensure a Web-enabled SecurityManager " +
  6. "has been configured and made available to this builder.";
  7. throw new IllegalStateException(msg);
  8. }
  9. return (WebSubject) subject;
  10. }

进入父类的buildSubject对象我们可以看到,具体实现是由SecurityManager来完成的

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

在createSubject方法中会根据你的配置从缓存、redis、数据库中获取Session、Principals等信息,并创建Subject对象

  1. public Subject createSubject(SubjectContext subjectContext) {
  2. //create a copy so we don't modify the argument's backing map:
  3. SubjectContext context = copy(subjectContext); //复制一个SubjectContext对象
  4.  
  5. //ensure that the context has a SecurityManager instance, and if not, add one:
  6. context = ensureSecurityManager(context); // 检查并初始化SecurityManager对象
  7.  
  8. //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
  9. //sending to the SubjectFactory. The SubjectFactory should not need to know how to acquire sessions as the
  10. //process is often environment specific - better to shield the SF from these details:
  11. context = resolveSession(context);//解析获取Sesssion信息
  12.  
  13. //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
  14. //if possible before handing off to the SubjectFactory:
  15. context = resolvePrincipals(context);//解析获取resolvePrincipals信息
  16.  
  17. Subject subject = doCreateSubject(context);//创建Subject
  18.  
  19. //save this subject for future reference if necessary:
  20. //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
  21. //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
  22. //Added in 1.2:
  23. save(subject);
  24.  
  25. return subject;
  26. }

在doCreateSubject中通过SubjectFactory创建合成Subject对象

  1. protected Subject doCreateSubject(SubjectContext context) {
  2. return getSubjectFactory().createSubject(context);
  3. }

我们可以看到最后返回的是具体实现类WebDelegatingSubject

  1. public Subject createSubject(SubjectContext context) {
  2. //SHIRO-646
  3. //Check if the existing subject is NOT a WebSubject. If it isn't, then call super.createSubject instead.
  4. //Creating a WebSubject from a non-web Subject will cause the ServletRequest and ServletResponse to be null, which wil fail when creating a session.
  5. boolean isNotBasedOnWebSubject = context.getSubject() != null && !(context.getSubject() instanceof WebSubject);
  6. if (!(context instanceof WebSubjectContext) || isNotBasedOnWebSubject) {
  7. return super.createSubject(context);
  8. }
  9. //获取上下文对象中的信息
  10. WebSubjectContext wsc = (WebSubjectContext) context;
  11. SecurityManager securityManager = wsc.resolveSecurityManager();
  12. Session session = wsc.resolveSession();
  13. boolean sessionEnabled = wsc.isSessionCreationEnabled();
  14. PrincipalCollection principals = wsc.resolvePrincipals();
  15. boolean authenticated = wsc.resolveAuthenticated();
  16. String host = wsc.resolveHost();
  17. ServletRequest request = wsc.resolveServletRequest();
  18. ServletResponse response = wsc.resolveServletResponse();
  19.  
  20. //构造返回WebDelegatingSubject对象
  21. return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
  22. request, response, securityManager);
  23. }

以上是Subject的创建过程,创建完成后我们还需要与当前请求线程进行绑定,这样才能通过SecurityUtils.getSubject()方法获取到Subject

二、Subject的绑定

Subject对象本质上是与请求所属的线程进行绑定,Shiro底层定义了一个ThreadContext对象,一个基于ThreadLocal的上下文管理容器,里面定义了一个InheritableThreadLocalMap<Map<Object, Object>>(),Subject最后就是被放到这个map当中,我们获取时也是从这个map中获取

首先我们看下绑定操作的入口,execuse是执行绑定,后续操作采用回调机制来实现

  1. //执行Subject绑定
  2. //noinspection unchecked
  3. subject.execute(new Callable() {
  4. public Object call() throws Exception {
  5. updateSessionLastAccessTime(request, response);
  6. executeChain(request, response, chain);
  7. return null;
  8. }
  9. });

初始化一个SubjectCallable对象,并把回调方法传进去

  1. public <V> V execute(Callable<V> callable) throws ExecutionException {
  2. Callable<V> associated = associateWith(callable);//初始化一个SubjectCallable对象,并把回调方法传进去
  3. try {
  4. return associated.call();
  5. } catch (Throwable t) {
  6. throw new ExecutionException(t);
  7. }
  8. }
  9.  
  10. public <V> Callable<V> associateWith(Callable<V> callable) {
  11. return new SubjectCallable<V>(this, callable);
  12. }

看下SubjectCallable类的具体实现

  1. public class SubjectCallable<V> implements Callable<V> {
  2.  
  3. protected final ThreadState threadState;
  4. private final Callable<V> callable;
  5.  
  6. public SubjectCallable(Subject subject, Callable<V> delegate) {
  7. this(new SubjectThreadState(subject), delegate);//初始化构造方法
  8. }
  9.  
  10. protected SubjectCallable(ThreadState threadState, Callable<V> delegate) {
  11. if (threadState == null) {
  12. throw new IllegalArgumentException("ThreadState argument cannot be null.");
  13. }
  14. this.threadState = threadState;//SubjectThreadState对象
  15. if (delegate == null) {
  16. throw new IllegalArgumentException("Callable delegate instance cannot be null.");
  17. }
  18. this.callable = delegate;//回调对象
  19. }
  20.  
  21. public V call() throws Exception {
  22. try {
  23. threadState.bind();//执行绑定操作
  24. return doCall(this.callable);//执行回调操作
  25. } finally {
  26. threadState.restore();
  27. }
  28. }
  29.  
  30. protected V doCall(Callable<V> target) throws Exception {
  31. return target.call();
  32. }
  33. }

具体绑定的操作是通过threadState.bind()来实现的

  1. public void bind() {
  2. SecurityManager securityManager = this.securityManager;
  3. if ( securityManager == null ) {
  4. //try just in case the constructor didn't find one at the time:
  5. securityManager = ThreadContext.getSecurityManager();
  6. }
  7. this.originalResources = ThreadContext.getResources();
  8. ThreadContext.remove();//首先执行remove操作
  9.  
  10. ThreadContext.bind(this.subject);//执行绑定操作
  11. if (securityManager != null) {
  12. ThreadContext.bind(securityManager);
  13. }
  14. }

在上面bind方法中又会执行ThreadContext的bind方法,这里就是之前说到的shiro底层维护了的一个ThreadContext对象,一个基于ThreadLocal的上下文管理容器,bind操作本质上就是把创建的Subject对象维护到resources 这个InheritableThreadLocalMap中, SecurityUtils.getSubject()方法其实就是从InheritableThreadLocalMap中获取所属线程对应的Subject

  1. private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();//定义一个InheritableThreadLocalMap
  2.  
  3. public static void bind(Subject subject) {
  4. if (subject != null) {
  5. put(SUBJECT_KEY, subject);//向InheritableThreadLocalMap中放入Subject对象
  6. }
  7. }
  8.  
  9. public static void put(Object key, Object value) {
  10. if (key == null) {
  11. throw new IllegalArgumentException("key cannot be null");
  12. }
  13.  
  14. if (value == null) {
  15. remove(key);
  16. return;
  17. }
  18.  
  19. ensureResourcesInitialized();
  20. resources.get().put(key, value);
  21.  
  22. if (log.isTraceEnabled()) {
  23. String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
  24. key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
  25. log.trace(msg);
  26. }
  27. }

 三、总结

从以上对Shiro源码的分析,我们对Subject对象的创建与绑定进行了基本的梳理,Subject对象的创建是通过不断的对context上下文对象进行赋值与完善,并最终构造返回WebDelegatingSubject对象的过程;Subject对象创建后,会通过Shiro底层维护的一个基于ThreadLocal的上下文管理容器,即ThreadContext这个类,与请求所属的线程进行绑定,供后续访问使用。对Subject对象创建与绑定流程的分析,有助于理解Shiro底层的实现机制与方法,加深对Shiro的认识,从而在项目中能够正确使用。希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。

Shiro中Subject对象的创建与绑定流程分析的更多相关文章

  1. HibernateSessionFactory类中Session对象的创建步骤

    HibernateSessionFactory类中Session对象的创建步骤: 1.初始化Hibernate配置管理类Configuration 2.通过Configuration类实例创建Sess ...

  2. iOS中归档对象的创建,数据写入与读取

    归档(archiving)是指另一种形式的序列化,但它是任何对象都可以实现的更常规的模型.专门编写用于保存数据的任何模型对象都应该支持归档.比属性列表多了很良好的伸缩性,因为无论添加多少对象,将这些对 ...

  3. Java中String对象的创建

    字符串对象是一种特殊的对象.String类是一个不可变的类..也就说,String对象一旦创建就不允许修改 String类有一个对应的String池,也就是 String pool.每一个内容相同的字 ...

  4. 面试题:JVM在Java堆中对对象的创建、内存结构、访问方式

    一.对象创建过程 1.检查类是否已被加载 JVM遇到new指令时,首先会去检查这个指令参数能否在常量池中定位到这个类的符号引用,检查这个符号引用代表的类是否已被加载.解析.初始化,若没有,则进行类加载 ...

  5. C#中Monitor对象与Lock关键字的区别分析

    这篇文章主要介绍了C#中Monitor对象与Lock关键字的区别,需要的朋友可以参考下 Monitor对象 1.Monitor.Enter(object)方法是获取 锁,Monitor.Exit(ob ...

  6. 转:C#中Monitor对象与Lock关键字的区别分析

    Monitor对象1.Monitor.Enter(object)方法是获取 锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最常用的两个方法,当然在使用过程中为了避免获取 ...

  7. Openstack之Nova创建虚机流程分析

    前言        Openstack作为一个虚拟机管理平台,核心功能自然是虚拟机的生命周期的管理,而负责虚机管理的模块就是Nova. 本文就是openstack中Nova模块的分析,所以本文重点是以 ...

  8. tornado 学习笔记10 Web应用中模板(Template)的工作流程分析

             第8,9节中,我们分析Tornado模板系统的语法.使用以及源代码中涉及到的相关类,而且对相关的源代码进行了分析.那么,在一个真正的Web应用程序中,模板到底是怎样使用?怎样被渲染? ...

  9. js中对象的创建

    json方式,构造函数方式,Object方式,属性的删除和对象的销毁 <html> <head> <title>js中的对象的创建</title> &l ...

随机推荐

  1. JAVA递归算法及经典递归例子 对于这个汉诺塔问题

    前言:递归(recursion):递归满足2个条件 1)有反复执行的过程(调用自身) 2)有跳出反复执行过程的条件(递归出口) 第一题:汉诺塔 对于这个汉诺塔问题,在写递归时,我们只需要确定两个条件: ...

  2. [LeetCode]129. Sum Root to Leaf Numbers路径数字求和

    DFS的标准形式 用一个String记录路径,最后判断到叶子时加到结果上. int res = 0; public int sumNumbers(TreeNode root) { if (root== ...

  3. Mac电脑jsp连接mysql

    四个步骤教你如何用jsp代码连接mysql 第一步:下载jdbc驱动 进入mysql官网:https://dev.mysql.com/downloads/connector/ 找到Connect/J ...

  4. JDBC数据连接之增删改查MVC

    每天叫醒自己的不是闹钟,而是梦想 conn层 package conn; import java.sql.Connection; import java.sql.DriverManager; impo ...

  5. rm(操作系统的删除文件)与git rm的区别

    git rm:1.删除了一个文件2.把这个删除的文件纳入暂存区如果想要恢复这个文件,则需要做2个操作a.git reset HEAD file_name --将文件从暂存区恢复到工作区b.git ch ...

  6. hbase读写优化

    一.hbase读优化 客户端优化 1.scan缓存是否设置合理? 优化原理:一次scan请求,实际并不会一次就将所有数据加载到本地,而是多次RPC请求进行加载.默认100条数据大小. 优化建议:大sc ...

  7. Scaled-YOLOv4 快速开始,训练自定义数据集

    代码: https://github.com/ikuokuo/start-scaled-yolov4 Scaled-YOLOv4 代码: https://github.com/WongKinYiu/S ...

  8. JAVA开发手册-Markdown

    前言 前 言 <Java 开发手册>是技术团队的集体智慧结晶和经验总结,经历了多次大规模一线实战的检验及不断完善.现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点 ...

  9. Shiro配置Session检测时Quartz版本冲突

    项目背景: shiro 1.3 + quartz 2.x 2018-9-11 22:20:35补充: 经过测试,本人发现 ,通过实现 org.apache.shiro.session.mgt.Exec ...

  10. 【C++】《Effective C++》第二章

    第二章 构造/析构/赋值运算 条款05:了解C++默默编写并调用哪些函数 默认函数 一般情况下,编译器会为类默认合成以下函数:default构造函数.copy构造函数.non-virtual析构函数. ...