shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户受权。
spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖过于紧密,没有shiro使用简单。
shiro不依赖于spring,shiro不只能够实现 web应用的权限管理,还能够实现c/s系统,分布式系统权限管理,shiro属于轻量框架,愈来愈多企业项目开始使用shiro。web
项目中使用到了shiro
,因此对shiro
作一些比较深的了解。面试
也不知从何了解起,先从shiro
的运行流程开始。spring
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)
方法。apache
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
对象。app
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
是一个接口,他继承了步骤里所谈到的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
。
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
会把相应的 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!文章都会在里面更新,整理的资料也会放在里面。谢谢你的观看,以为文章对你有帮助的话记得关注我点个赞支持一下!