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

31次阅读

共计 7619 个字符,预计需要花费 20 分钟才能阅读完成。

前言

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!文章都会在外面更新,整顿的材料也会放在外面。谢谢你的观看,感觉文章对你有帮忙的话记得关注我点个赞反对一下!

正文完
 0