共计 7619 个字符,预计需要花费 20 分钟才能阅读完成。
前言
shiro 是 apache 的一个开源框架,是一个权限治理的框架,实现 用户认证、用户受权。
spring 中有 spring security (原名 Acegi),是一个权限框架,它和 spring 依赖过于严密,没有 shiro 应用简略。
shiro 不依赖于 spring,shiro 不仅能够实现 web 利用的权限治理,还能够实现 c / s 零碎,分布式系统权限治理,shiro 属于轻量框架,越来越多企业我的项目开始应用 shiro。
Shiro 运行流程学习笔记
我的项目中应用到了 shiro
,所以对shiro
做一些比拟深的理解。
也不知从何理解起,先从 shiro
的运行流程开始。
运行流程
- 首先调用
Subject.login(token)
进行登录,其会主动委托给Security Manager
,调用之前必须通过SecurityUtils.setSecurityManager()
设置; SecurityManager
负责真正的身份验证逻辑;它会委托给Authenticator
进行身份验证;Authenticator
才是真正的身份验证者,Shiro API
中外围的身份认证入口点,此处能够自定义插入本人的实现;Authenticator
可能会委托给相应的AuthenticationStrategy
进行多 Realm 身份验证,默认ModularRealmAuthenticator
会调用AuthenticationStrategy
进行多 Realm 身份验证;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);
}
}
且 shiro
的key
都是遵循一个固定的格局。
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
是一个接口,他继承了步骤里所谈到的 Authenticator
,Authorizer
类以及用于 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)
在第一步中调用了 Subject
的login
办法,找到它的最终实现 DelegatingSubject
类。
外面有调用了 securityManager
的login
办法,而最终实现就在 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;
}
会发现调用的都是 Relam
的getAuthenticationInfo
办法。
看到了相熟的UserRelam
,此致,闭环了。
然而也只是理解了大略的流程,对每个类的具体作用并不是很理解,所以笔者还是有很多中央要去学习,不,应该说我原本就是菜鸡,就要学能力变带佬。
最初
大家看完有什么不懂的能够在下方留言探讨,也能够关注我私信问我,我看到后都会答复的。也欢送大家关注我的公众号:前程有光,马上金九银十跳槽面试季,整顿了 1000 多道将近 500 多页 pdf 文档的 Java 面试题材料放在外面,助你圆梦 BAT!文章都会在外面更新,整顿的材料也会放在外面。谢谢你的观看,感觉文章对你有帮忙的话记得关注我点个赞反对一下!