JAVA Sercurity ,Shiro源码学习(1)---------SecurityUtils.getSubject()

1 篇文章 0 订阅
1 篇文章 0 订阅

最近在学习shiro的源码,所以记录下来自己的心得。

学习源码查看网上教程一直都是很迷,源码这种东西需要自己不断去定位去看才能看明白。

那么我们现在开始:这次我们主要讲解SecurityUtils.getSubject()并从这个方法入手看看到底怎么获取到我们要的subject。

 

  private static SecurityManager securityManager;

    /**
     * Returns the currently accessible {@code Subject} available to the calling code depending on
     * runtime environment.
     * <p/>
     * This method is provided as a way of obtaining a {@code Subject} without having to resort to
     * implementation-specific methods.  It also allows the Shiro team to change the underlying implementation of
     * this method in the future depending on requirements/updates without affecting your code that uses it.
     *
     * @return the currently accessible {@code Subject} accessible to the calling code.
     * @throws IllegalStateException if no {@link Subject Subject} instance or
     *                               {@link SecurityManager SecurityManager} instance is available with which to obtain
     *                               a {@code Subject}, which which is considered an invalid application configuration
     *                               - a Subject should <em>always</em> be available to the caller.
     */
    public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }

                                                                   1.1                      

这个类中只有一个securityManager对象,我们知道shiro所有的认证,授权,以及session管理都通过securityManager来进行控制,而SecurityUtils只是对其的在一层代理罢了。

从getSubject()我们能够看到,先从ThreadContext中拿取,如果没有则(new Subject.Builder()).buildSubject(); 并且绑定到当前线程中。

我们先假设当前线程中存在。这时候我们是怎么去获取我们要的subject呢。进入方法查看:

   //我直接把相关ThreadContext类的相关方法全部拿出来了
//一个静态字符串
 public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";

//一个线程变量map
 private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();



//我们进入的方法
 public static Subject getSubject() {
        return (Subject) get(SUBJECT_KEY);
    }


//get方法
public static Object get(Object key) {
        if (log.isTraceEnabled()) {
            String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }

//只要看这一行就可以了
        Object value = getValue(key);
        if ((value != null) && log.isTraceEnabled()) {
            String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
                    key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }
        return value;
    }


//如何获取value
  private static Object getValue(Object key) {
        Map<Object, Object> perThreadResources = resources.get();
        return perThreadResources != null ? perThreadResources.get(key) : null;
    }

                                                       1.2

可以看出来,我们进入get(SUBJECT_KEY) 也就是get一个静态字符串。get方法中只是为了调用这一句Object value = getValue(key);

这里的key就是这个静态字符串。也就是说我们所有用户访问时候其实都只是get(SUBJECT_KEY),而这个getValue方法中是使用resouce的get方法。resouce是一个线程变量,也就是所有线程都维护自己的变量不共享且互斥的。

我们进入这个resource.get() 来看看

  public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

                                                  1.3

这里我们简单讲解一下ThreadLocal怎么维护各线程都只访问自己的变量不干扰,若想了解,可以深入去研究下。

首先获取当前线程,之后get(t)得到 t.threadLocals; 也就是一个ThreadLocalMap对象 这个ThreadLocalMap是ThreadLocal一个内部类

这个内部类中有一个Entry内部类 以及一个Entry[] table 数组,根据hash算法获取对应的value;那么问题来了

map.getEntry(this); 这个this指的是啥。 这个this指的就是1.1中的resource变量。

所以我们大概理解下就是,各线程去访问ThreadLocal对象 resouce (其实只有一个resouce)这时候进来先获取自己线程对象,然后在线程中get得到ThreadLocalMap,若存在则使用这个resouce作为key来找并使用hash算法来算,若不存在则初始化,并set一下。那么我们明白了我们登陆的Subject对象保存在哪里以及为什么多个用户访问能够分别区分,其实就是使用了线程变量。

 

 

/我是分割线/

 

接下来我们来看一下如果 在ThreadContext中没有获取到Subject会去哪里取呢?

private static SecurityManager securityManager;

    /**
     * Returns the currently accessible {@code Subject} available to the calling code depending on
     * runtime environment.
     * <p/>
     * This method is provided as a way of obtaining a {@code Subject} without having to resort to
     * implementation-specific methods.  It also allows the Shiro team to change the underlying implementation of
     * this method in the future depending on requirements/updates without affecting your code that uses it.
     *
     * @return the currently accessible {@code Subject} accessible to the calling code.
     * @throws IllegalStateException if no {@link Subject Subject} instance or
     *                               {@link SecurityManager SecurityManager} instance is available with which to obtain
     *                               a {@code Subject}, which which is considered an invalid application configuration
     *                               - a Subject should <em>always</em> be available to the caller.
     */
    public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }

进入new Subject.Builder() 以及buildSubject() 方法来看

     public Builder() {
            this(SecurityUtils.getSecurityManager());
        }

        /**
         * Constructs a new {@link Subject.Builder} instance which will use the specified {@code SecurityManager} when
         * building the {@code Subject} instance.
         *
         * @param securityManager the {@code SecurityManager} to use when building the {@code Subject} instance.
         */
        public Builder(SecurityManager securityManager) {
            if (securityManager == null) {
                throw new NullPointerException("SecurityManager method argument cannot be null.");
            }
        //主要看这里设置securityManager
            this.securityManager = securityManager;
       //创建一个subjectContext对象
            this.subjectContext = newSubjectContextInstance();
            if (this.subjectContext == null) {
                throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                        "cannot be null.");
            }
          //在这个subjectContext中设置SecurityManager进去
            this.subjectContext.setSecurityManager(securityManager);
        }




    public Subject buildSubject() {
          //使用securityManager来创建Subject并且把这个subjectConext传进去
            return this.securityManager.createSubject(this.subjectContext);
        }

 

这个

this.subjectContext = newSubjectContextInstance();这个subject上线文就是一个map里面存放了相关信息
newSubjectContextInstance() = this.backingMap = new HashMap<String, Object>();也就是父类中的backingMap。里面为null

最后把securityManager设置进去也就是把这个安全管理器放到这个备份的map中。

然后开始创建subject把这个map传进去。

    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), 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);

        //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方法的实现。 首先把这个map备份一下,采用它的副本context来处理。

之后来检测是否有securityManager,再来检测是否有session,再来检测是否能解析Principals 最后创建Subject 并且保存一下。

这个检测是否有session等等这些方法逻辑处理都是一样的,检测是否有,有就返回context没有就创建并加入到context中再返回,我们以session为例一些逻辑处理最后定位到

protected Session createSession(SessionContext sessionContext) throws AuthorizationException {
        if (!WebUtils.isHttp(sessionContext)) {
            String msg = "SessionContext must be an HTTP compatible implementation.";
            throw new IllegalArgumentException(msg);
        }

        HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);

        HttpSession httpSession = request.getSession();

        //SHIRO-240: DO NOT use the 'globalSessionTimeout' value here on the acquired session.
        //see: https://issues.apache.org/jira/browse/SHIRO-240

        String host = getHost(sessionContext);

        return createSession(httpSession, host);
    }

这就很明白了,session其实也就是得到httpServletRequest中的getSession();进行一层包装代理。所以到此Subject对象也就创建好了回到代码1.1我们再进行和当前线程绑定一下,则就缓存到当前线程变量resource中了,这样当我们随时想要获取Subject对象都可以获得这个Subject。

 

 

都是一个一个码的,不容易啊。希望能有所帮助,很多地方可能不足请多指教。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值