Shiro学习笔记(四)源码解析

1,433 阅读5分钟

    上一次说完了登陆中认证和授权的流程,今天学习下在Authenticator和Authorizer之后,下

一步的流程。

1.3,Subject、SecurityManager的认证流程解析

   学习过第二章,实际搭建的同学会了解,我们一般登陆验证的代码:

1 //获取Subject对象
2 Subject subject = SecurityUtils.getSubject();
3 //创建用户名密码Token
4 UsernamePasswordToken usernamePasswordToken = 
5                              new UsernamePasswordToken(username, password);
6 //捕获相应的异常
7 try{
8     //实际登陆操作,验证用户名密码
9     subject.login(usernamePasswordToken);
10 }catch (UnauthenticatedException e){
11     System.err.println("UnauthenticatedException");
12 }catch (UnknownAccountException e){
13     System.err.println("UnknownAccountException");
14 }catch (IncorrectCredentialsException e){
15     System.err.println("IncorrectCredentialsException");
16 }

    首先我们一步一步分析。

    第2行,首先程序通过SecurityUtils拿到Subject,SecurityUtils可以看作是一个Shiro的工具

类,内部主要就是负责获取Subject和SecurityManager全局资源。那么Subject作为我们使用

Shiro的API核心工具,我们只有拿到它才可以做登陆、鉴权、等一系列操作。那么我们就来研

究下Subject的实例过程吧。

    下面是Subject由SecurityUtils中getSubject()方法获取:

public static Subject getSubject() {
    //首先去ThreadContext寻找当前线程是否已经绑定了Subject
    Subject subject = ThreadContext.getSubject();
    //如果查找为空则进行新建Subject 之后进行线程绑定
    if (subject == null) {
        subject = (new Subject.Builder()).buildSubject();
        ThreadContext.bind(subject);
    }
    return subject;
}

    在获取Subject的时候,SecurityUtils每次都会先去ThreadContext中去查找是否已存在当前

线程绑定的Subject,ThreadContext即为线程相关的上下文,像Subject作为线程相关的资源

都会放在里面的resources对象中。

public abstract class ThreadContext {

    /**
     * Private internal log instance.
     */
    private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
    //预先定义好的securityManager的Key字符串 
    //一般为 org.apache.shiro.mgt.DefaultSecurityManager_SECURITY_MANAGER_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";
    //存储键值对,核心存储与当前线程相关的对象
    private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();

 

     ThreadContext内部主要提供了put、get、bind绑定、unbind解绑定等操作。每次获取

Subject的时候会做一些基本检查,例如线程追踪等一些操作,你就把它理解为Map对象通过

get(KEY)进行获取就可以了。

     因为我们是第一次运行,故此时在ThreadContext内肯定是获取不到Subject对象的,于是

下一步就是New一个Subject对象:

<-------SecurityUtils类--------->
subject = (new Subject.Builder()).buildSubject();<-------Subject类--------->
//追踪发现
//新建Subject首先要获取SecurityManager所有又返回SecurityUtils中获取
public Builder() {
    this(SecurityUtils.getSecurityManager());
}
<-------SecurityUtils类--------->

//查看SecurityUtils中的getSecurityManager
public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {    //首先和Subject如出一辙,先去ThreadContext尝试获取是否已经创建的securityManager
    SecurityManager securityManager = ThreadContext.getSecurityManager();
    //此时securityManager获取肯定为null
    if (securityManager == null) {
        //这里就比较重要的官方说这是VM单例中的备份,
        //当ThreadContext中没有的时候才用此securityManager进行获取Subject
        securityManager = SecurityUtils.securityManager;
    }
    //此时我们已经获取了DefaultSecurityManager
    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之后,接着进行Subject的获取。

public Builder(SecurityManager securityManager) {
    //进行对Subject中的属性赋值
    if (securityManager == null) {
        throw new NullPointerException("SecurityManager method argument cannot be null.");
    }
    //赋值securityManager
    this.securityManager = securityManager;
    //赋值subjectContext,可是现在为空,所以先进行创建subjectContext
    //subjectContext是构建subject的基础
    this.subjectContext = newSubjectContextInstance();
    if (this.subjectContext == null) {
        throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                "cannot be null.");
    }
    this.subjectContext.setSecurityManager(securityManager);
}

    这里介绍下SubjectContext,它是Subject的一个数据视图,用于构建Subject的实例。作为

接口,由DefaultSubjectContext具体类去实现。DefaultSubjectContext继承MapContext类,

MapContext类中的backMap为空,故此时找不到DefaultSubjectContext。那么此时会调用

DefaultSubjectContext中

//把securityManager
public void setSecurityManager(SecurityManager securityManager) {
    nullSafePut(SECURITY_MANAGER, securityManager);
}
//进行推入DefaultSubjectContext
protected void nullSafePut(String key, Object value) {    if (value != null) {
        put(key, value);
    }
}

到这里就拿到了DefaultSubjectContext对象了。

//真正的创建Subject对象
public Subject buildSubject() {
    return this.securityManager.createSubject(this.subjectContext);
}

可以看到Subject对象本质还是由SecurityManager进行创建。下面是创建的核心方法:

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:
    //确保必须有SecurityManager实例
    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 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;
}

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

public SubjectFactory getSubjectFactory() {
    return subjectFactory;
}
//兜兜转转又把创建SecurityManager的工作交给了Subject工厂类
public Subject createSubject(SubjectContext context) {
    //先获取securityManager
    SecurityManager securityManager = context.resolveSecurityManager();
    Session session = context.resolveSession();
    boolean sessionCreationEnabled = context.isSessionCreationEnabled();
    PrincipalCollection principals = context.resolvePrincipals();
    boolean authenticated = context.resolveAuthenticated();
    String host = context.resolveHost();
    //上面分别是设置session 设置凭证 和设置认证情况 一些基本属性
    return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
}

最好又把Subject创建的任务交给了托管类

public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
                         Session session, boolean sessionCreationEnabled, SecurityManager securityManager) {
    if (securityManager == null) {
        throw new IllegalArgumentException("SecurityManager argument cannot be null.");
    }
    this.securityManager = securityManager;
    this.principals = principals;
    this.authenticated = authenticated;
    this.host = host;
    if (session != null) {
        this.session = decorate(session);
    }
    this.sessionCreationEnabled = sessionCreationEnabled;
}

到此为止真正的创建完成。

之后执行SAVE保存在Session中,返回一个DelegationSubject类。

public Subject save(Subject subject) {
    if (isSessionStorageEnabled(subject)) {
        saveToSession(subject);
    } else {
        log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
                "authentication state are expected to be initialized on every request or invocation.", subject);
    }

    return subject;
}

最后一步就是进行绑定,把他推进ThreadContext中的resources中,官方说明一般Subject获

取的主要来源都是ThreadContext

public static void bind(Subject subject) {
    if (subject != null) {
        put(SUBJECT_KEY, subject);
    }
}

至此Subject获取结束。


有很多待修改,请多原谅.