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

    public static Subject getSubject()
{
return SecurityUtils.getSubject();
}

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

一、Subject的创建

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

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException { Throwable t = null; try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain); //创建Subject
final Subject subject = createSubject(request, response); //执行Subject绑定
//noinspection unchecked
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
} catch (Throwable throwable) {
t = throwable;
} if (t != null) {
if (t instanceof ServletException) {
throw (ServletException) t;
}
if (t instanceof IOException) {
throw (IOException) t;
}
//otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
String msg = "Filtered request failed.";
throw new ServletException(msg, t);
}
}

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

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

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

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

         */
public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
super(securityManager);
if (request == null) {
throw new IllegalArgumentException("ServletRequest argument cannot be null.");
}
if (response == null) {
throw new IllegalArgumentException("ServletResponse argument cannot be null.");
}
setRequest(request);
setResponse(response);
}

buildWebSubject方法中开始构造Subject对象

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

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

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

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

    public Subject createSubject(SubjectContext subjectContext) {
//create a copy so we don't modify the argument's backing map:
SubjectContext context = copy(subjectContext); //复制一个SubjectContext对象 //ensure that the context has a SecurityManager instance, and if not, add one:
context = ensureSecurityManager(context); // 检查并初始化SecurityManager对象 //Resolve an associated Session (usually based on a referenced session ID), and 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);//解析获取Sesssion信息 //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);//解析获取resolvePrincipals信息 Subject subject = doCreateSubject(context);//创建Subject //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;
}

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

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

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

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

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

二、Subject的绑定

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

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

         //执行Subject绑定
//noinspection unchecked
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});

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

    public <V> V execute(Callable<V> callable) throws ExecutionException {
Callable<V> associated = associateWith(callable);//初始化一个SubjectCallable对象,并把回调方法传进去
try {
return associated.call();
} catch (Throwable t) {
throw new ExecutionException(t);
}
} public <V> Callable<V> associateWith(Callable<V> callable) {
return new SubjectCallable<V>(this, callable);
}

看下SubjectCallable类的具体实现

public class SubjectCallable<V> implements Callable<V> {

    protected final ThreadState threadState;
private final Callable<V> callable; public SubjectCallable(Subject subject, Callable<V> delegate) {
this(new SubjectThreadState(subject), delegate);//初始化构造方法
} protected SubjectCallable(ThreadState threadState, Callable<V> delegate) {
if (threadState == null) {
throw new IllegalArgumentException("ThreadState argument cannot be null.");
}
this.threadState = threadState;//SubjectThreadState对象
if (delegate == null) {
throw new IllegalArgumentException("Callable delegate instance cannot be null.");
}
this.callable = delegate;//回调对象
} public V call() throws Exception {
try {
threadState.bind();//执行绑定操作
return doCall(this.callable);//执行回调操作
} finally {
threadState.restore();
}
} protected V doCall(Callable<V> target) throws Exception {
return target.call();
}
}

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

    public void bind() {
SecurityManager securityManager = this.securityManager;
if ( securityManager == null ) {
//try just in case the constructor didn't find one at the time:
securityManager = ThreadContext.getSecurityManager();
}
this.originalResources = ThreadContext.getResources();
ThreadContext.remove();//首先执行remove操作 ThreadContext.bind(this.subject);//执行绑定操作
if (securityManager != null) {
ThreadContext.bind(securityManager);
}
}

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

    private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();//定义一个InheritableThreadLocalMap

    public static void bind(Subject subject) {
if (subject != null) {
put(SUBJECT_KEY, subject);//向InheritableThreadLocalMap中放入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);
}
}

 三、总结

从以上对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 合并两个list 并去重

    //两个list合并并去除重复 public static void main(String[] args) throws Exception { List list1 =new ArrayList( ...

  2. 多维数组遍历.php

    $a=array('fruits'=>array('a'=>'orange','b'=>'grape','c'=>'apple'),     'numbers'=>arr ...

  3. Sentinel滑动窗口算法

    在前面搞清楚了Sentinel的使用后,大致理了一下Sentinel的责任链,搞清楚了这个,基本就已经梳理清楚sentinel-core模块的大部分内容,顺着这条链路可以继续梳理很多东西. 知其然.知 ...

  4. C语言结构体参数传递

    结构体的形参或实参传递和和一般的程序一样: #include<stdio.h> #include<string.h> struct student //结构体定义 { char ...

  5. 如何创建 mapbox 精灵图

    前面文章介绍了如何在本地发布OSM数据,并使用 maputnik 自定义 mapbox 格式的地图样式. 在使用 maputnik 配图时,如果想要使用自己的图片作为地图符号,就需要制作精灵图. ma ...

  6. postgresql-从表中随机获取一条记录

    目录 postgresql如何从表中高效的随机获取一条记录 随机获取一条记录random() 改写1 改写2 改写3 对比 注意 结语 postgresql如何从表中高效的随机获取一条记录 selec ...

  7. linux安装ftp步骤

    1,查看是否安装了FTP:rpm -qa |grep vsftpd 2,如果没有安装,可以使用如下命令直接安装 yum -y install vsftpd 默认安装目录:/etc/vsftpd 3,添 ...

  8. 百度智能(文本识别),API传图OC代码与SDK使用

    百度智能中的文本识别中的身份证识别,有API方式和SDK方式 API方式 百度智能(文本识别),百度API传图没有提供OC的示例,这里提供一下 - (void)OCTest:(NSString*)to ...

  9. NAS基础知识

    一.什么是NAS 1.NAS的定义 NAS(Network Attached Storage:网络附属存储)按字面简单说就是连接在网络上,具备资料存储功能的装置,因此也称为"网络存储器&qu ...

  10. 与HBase对比,Cassandra的优势特性是什么?

    在1月9日Cassandra中文社区开年活动开始之前的闲聊时间,活动的四位嘉宾就"HBase和Cassandra的对比"这一话题展开了讨论.   总的来说,HBase和Cassan ...