关于java:厉害啊第一次见到把Shiro运行流程写的这么清楚的建议收藏起来慢慢看

前言

shiro是apache的一个开源框架,是一个权限治理的框架,实现 用户认证、用户受权。
spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖过于严密,没有shiro应用简略。
shiro不依赖于spring,shiro不仅能够实现 web利用的权限治理,还能够实现c/s零碎,分布式系统权限治理,shiro属于轻量框架,越来越多企业我的项目开始应用shiro。

Shiro运行流程学习笔记

我的项目中应用到了shiro,所以对shiro做一些比拟深的理解。

也不知从何理解起,先从shiro的运行流程开始。

运行流程

  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,将依照相应的程序及策略进行拜访。

绑定线程

这里从看我的项目源码开始。

看第一步,Subject.login(token)办法。

UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
subject.login(token);

呈现了一个UsernamePasswordToken对象,它在这里会调用它的一个构造函数。

public UsernamePasswordToken(final String username, final String password, final boolean rememberMe) {
    this(username, password != null ? password.toCharArray() : null, rememberMe, null);
}

据笔者本人理解,这是shiro的一个验证对象,只是用来存储用户名明码,以及一个记住我属性的。

之后会调用shiro的一个工具类失去一个subject对象。

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

通过getSubject办法来失去一个Subject对象。

这里不得不提到shiro的内置线程类ThreadContext,通过bind办法会将subject对象绑定在线程上。

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

shirokey都是遵循一个固定的格局。

public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";

通过非空判断后会将值以KV的模式put进去。

当你想拿到subject对象时,也能够通过getSubject办法失去subject对象。

在绑定subject对象时,也会将securityManager对象进行一个绑定。

而绑定securityManager对象的中央是在Subject类的一个动态外部类里(可让我好一顿找)。

getSubject办法中的一句代码调用了外部类的buildSubject办法。

subject = (new Subject.Builder()).buildSubject();

PS:此处使用到了建造者设计模式,能够去菜鸟教程认真理解

进去观看源码后能够看见。

首先调用无参结构,在无参结构里调用有参构造函数。

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

public Builder(SecurityManager securityManager) {
    if (securityManager == null) {
        throw new NullPointerException("SecurityManager method argument cannot be null.");
    }
    this.securityManager = securityManager;
    this.subjectContext = newSubjectContextInstance();
    if (this.subjectContext == null) {
        throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                "cannot be null.");
    }
    this.subjectContext.setSecurityManager(securityManager);
}

在此处绑定了securityManager对象。

当然,他也对securityManager对象的空情况进行了解决,在getSecurityManager办法里。

public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
    SecurityManager securityManager = ThreadContext.getSecurityManager();
    if (securityManager == null) {
        securityManager = SecurityUtils.securityManager;
    }
    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这个对象。

SecurityManager

SecurityManager是一个接口,他继承了步骤里所谈到的AuthenticatorAuthorizer类以及用于Session治理的SessionManager

public interface SecurityManager extends Authenticator, Authorizer, SessionManager {

    Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;   

    void logout(Subject subject);

    Subject createSubject(SubjectContext context);
}

看一下它的实现。

且这些类和接口都有顺次继承的关系。

Relam

接下来理解一下另一个重要的概念Relam

Realm充当了Shiro与利用平安数据间的“桥梁”或者“连接器”。也就是说,当与像用户帐户这类平安相干数据进行交互,执行认证(登录)和受权(访问控制)时,Shiro会从利用配置的Realm中查找很多内容。

从这个意义上讲,Realm本质上是一个平安相干的DAO:它封装了数据源的连贯细节,并在须要时将相干数据提供给Shiro。当配置Shiro时,你必须至多指定一个Realm,用于认证和(或)受权。配置多个Realm是能够的,然而至多须要一个。

Shiro内置了能够连贯大量平安数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、相似INI的文本配置资源以及属性文件 等。如果缺省的Realm不能满足需要,你还能够插入代表自定义数据源的本人的Realm实现。

个别状况下,都会自定义Relam来应用。

先看一下实现。

以及自定义的一个UserRelam

看一下类图。

每个抽象类继承后所须要实现的办法都不一样。

public class UserRealm extends AuthorizingRealm

这里继承AuthorizingRealm,须要实现它的两个办法。

//给登录用户受权
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);

//这个形象办法属于AuthorizingRealm抽象类的父类AuthenticatingRealm类   登录认证,也是登录的DAO操作所在的办法
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

之后再来看看这个验证办法,在之前的步骤里提到了,验证用到了Authenticator ,也就是第五步。

Authenticator

Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异样示意身份验证失败了。此处能够配置多个 Realm,将依照相应的程序及策略进行拜访。

再回到之前登录办法上来看看。

subject.login(token)在第一步中调用了Subjectlogin办法,找到它的最终实现DelegatingSubject类。

外面有调用了securityManagerlogin办法,而最终实现就在DefaultSecurityManager这个类里。

Subject subject = securityManager.login(this, token);
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
    AuthenticationInfo info;
    try {
        info = authenticate(token);
    } catch (AuthenticationException ae) {
        try {
            onFailedLogin(token, ae, subject);
        } catch (Exception e) {
            if (log.isInfoEnabled()) {
                log.info("onFailedLogin method threw an " +
                        "exception.  Logging and propagating original AuthenticationException.", e);
            }
        }
        throw ae; //propagate
    }

之后就是验证流程,这里咱们会看到第四步,点进去会到抽象类AuthenticatingSecurityManager。再看看它的认真调用。

public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
    return this.authenticator.authenticate(token);
}

真正的调用Relam进行验证并不在这,而是在ModularRealmAuthenticator

他们之间是一个从左到右的过程。

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
    assertRealmsConfigured();
    Collection<Realm> realms = getRealms();
    if (realms.size() == 1) {
        return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
    } else {
        return doMultiRealmAuthentication(realms, authenticationToken);
    }
}

在这里咱们就看这个doSingleRealmAuthentication办法。

Relam验证。

protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
    if (!realm.supports(token)) {
        String msg = "Realm [" + realm + "] does not support authentication token [" +
                token + "].  Please ensure that the appropriate Realm implementation is " +
                "configured correctly or that the realm accepts AuthenticationTokens of this type.";
        throw new UnsupportedTokenException(msg);
    }
    //在此处调用你自定义的Relam的办法来验证。
    AuthenticationInfo info = realm.getAuthenticationInfo(token);
    if (info == null) {
        String msg = "Realm [" + realm + "] was unable to find account data for the " +
                "submitted AuthenticationToken [" + token + "].";
        throw new UnknownAccountException(msg);
    }
    return info;
}

再看看多Relam的。

protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {

    AuthenticationStrategy strategy = getAuthenticationStrategy();

    AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

    for (Realm realm : realms) {

        aggregate = strategy.beforeAttempt(realm, token, aggregate);

        if (realm.supports(token)) {

            AuthenticationInfo info = null;
            Throwable t = null;
            try {
                //调用自定义的Relam的办法来验证。
                info = realm.getAuthenticationInfo(token);
            } catch (Throwable throwable) {
                t = throwable;
            }

            aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

        } else {
            log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
        }
    }

    aggregate = strategy.afterAllAttempts(token, aggregate);

    return aggregate;
}

会发现调用的都是RelamgetAuthenticationInfo办法。

看到了相熟的UserRelam,此致,闭环了。

然而也只是理解了大略的流程,对每个类的具体作用并不是很理解,所以笔者还是有很多中央要去学习,不,应该说我原本就是菜鸡,就要学能力变带佬。

最初

大家看完有什么不懂的能够在下方留言探讨,也能够关注我私信问我,我看到后都会答复的。也欢送大家关注我的公众号:前程有光,马上金九银十跳槽面试季,整顿了1000多道将近500多页pdf文档的Java面试题材料放在外面,助你圆梦BAT!文章都会在外面更新,整顿的材料也会放在外面。谢谢你的观看,感觉文章对你有帮忙的话记得关注我点个赞反对一下!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理