Subject反正就好像呈现的视图。全部Subject 都绑定到SecurityManager,与Subject的全部交互都会委托给SecurityManager;能够把Subject认为是一个门面;SecurityManager才是实际的执行者;
对于上面这句话的理解呢?怎么去理解这个很重要,看看别人的代码设计的流程也是比较的清楚的,Subject都绑定到了SecurityManager,所以咱们在建立Subject的时候,必须给框架的内部绑定了一个SecurityManager,在前一个博客,咱们已经基本的看了SecurityManager,大体的主要的架构,如今来看看Subject的主要的源码,学习一下别人这么写的用意何在?本身也是多多的总结颇有很好,看看别人的优秀代码。
和上一个同样的
shrio.iniandroid
[users] zhang=123 wang=123
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //二、获得SecurityManager实例 并绑定给SecurityUtils org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //三、获得Subject及建立用户名/密码身份验证Token(即用户身份/凭证) Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123"); try { //四、登陆,即身份验证 subject.login(token); } catch (AuthenticationException e) { //五、身份验证失败 } Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登陆 //六、退出 subject.logout();
SecurityUtils:是一个很是关键的类,这里能够获取到咱们的全局的资源,和当前的线程相关的,放置在ThreadLocal里面的,Subject也是如此哦,和当前的环境相关apache
package org.apache.shiro; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; /** * Accesses the currently accessible {@code Subject} for the calling code depending on runtime environment. *获取Subject,和当前的环境相关 * @since 0.2 */ public abstract class SecurityUtils { /** *ThreadContext 这里保存的是和线程相关的东西,这里只是个备份 *感受做用不是很大,这里只是用做在单线程的环境中 * ONLY used as a 'backup' in VM Singleton environments (that is, standalone environments), since the * ThreadContext should always be the primary source for Subject instances when possible. */ private static SecurityManager securityManager; public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; } //这里通常都是只在单线程中使用的, //获取这个通常在ThreadLoacal中获取,而不是这里哦 public static void setSecurityManager(SecurityManager securityManager) { SecurityUtils.securityManager = securityManager; } //每次都是先去找线程相关的,而后没有在去在备份的static public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException { SecurityManager securityManager = ThreadContext.getSecurityManager(); if (securityManager == null) { securityManager = SecurityUtils.securityManager; } if (securityManager == null) { throw new UnavailableSecurityManagerException(msg); } return securityManager; } }
如咱们所知道的,设置securityManager,以后才能绑定到.子进程共享父进程的信息 ThreadLoacl http://blog.csdn.net/jiafu1115/article/details/7548605 这里讲的还不错。http://blog.csdn.net/feier7501/article/details/19088905 这里的例子 笔者也去试了一会儿,这种用法过高级了。设计模式
public abstract class ThreadContext { // 这种惟一的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"; //这里使用了InheritableThreadLocalMap //子线程会接收全部可继承的线程局部变量的初始值, //以得到父线程所具备的值。一般,子线程的值与父线程的值是一致的 //这个就是比较高级的用法了,让子线程也能够获取到 private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>(); protected ThreadContext() { } //这个每次获取的都是新的哦,线程安全的。 public static Map<Object, Object> getResources() { return resources != null ? new HashMap<Object, Object>(resources.get()) : null; } private static Object getValue(Object key) { return resources.get().get(key); } public static Object get(Object key) { Object value = getValue(key); return value; } public static void put(Object key, Object value) { if (key == null) { throw new IllegalArgumentException("key cannot be null"); } if (value == null) { remove(key); return; } resources.get().put(key, value); } public static Object remove(Object key) { Object value = resources.get().remove(key); return value; } public static void remove() { resources.remove(); } //获取总管家 public static SecurityManager getSecurityManager() { return (SecurityManager) get(SECURITY_MANAGER_KEY); } public static void bind(SecurityManager securityManager) { if (securityManager != null) { put(SECURITY_MANAGER_KEY, securityManager); } } public static SecurityManager unbindSecurityManager() { return (SecurityManager) remove(SECURITY_MANAGER_KEY); } public static Subject getSubject() { return (Subject) get(SUBJECT_KEY); } public static void bind(Subject subject) { if (subject != null) { put(SUBJECT_KEY, subject); } } private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> { protected Map<Object, Object> initialValue() { return new HashMap<Object, Object>(); } /** * This implementation was added to address a * <a href="http://jsecurity.markmail.org/search/?q=#query:+page:1+mid:xqi2yxurwmrpqrvj+state:results"> * user-reported issue</a>. * @param parentValue the parent value, a HashMap as defined in the {@link #initialValue()} method. * @return the HashMap to be used by any parent-spawned child threads (a clone of the parent HashMap). */ @SuppressWarnings({"unchecked"}) protected Map<Object, Object> childValue(Map<Object, Object> parentValue) { if (parentValue != null) { return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone(); } else { return null; } } } }
上面的当前线程的值,保存了总管家了,和Subject的信息。Subject和总管家之间的关系如何呢?这个看看建立Subject的时候怎么去处理的。一步步的解开谜底。
以前已经绑定总管家了安全
//三、获得Subject及建立用户名/密码身份验证Token(即用户身份/凭证) Subject subject = SecurityUtils.getSubject();
–>下一步从当前线程中获取Subject有没有?没有建立一个,经过Subject本身的Build设计模式,建立一个Subject,此时咱们跟进Subject里面去看看。public interface Subject,Subject是个接口,Builder是一个内部静态类。这种用法你不会使用吧session
public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; }
Subject内部结构图能够看到Builder中和管家绑定有关系吧!并且这个接口有不少的权限的查看信息这个和管家里面的继承结构那有关系的,哈哈,代理的模式估计应该就是那样的。这种定义build能够值得学习,用起来比较爽,好比Okhttp好像也是这样的,模式哦不少的默认的参数,也能够本身设置本身喜欢的模式,进行处理。这个就是优势,好比android里面的Dialog的参数设置,你能够本身设置,也可使用默认的参数。 架构
public static class Builder { /** * Hold all contextual data via the Builder instance's method invocations to be sent to the * {@code SecurityManager} during the {@link #buildSubject} call. 数据保持器,在最后调用buildSubject的时候被使用。 */ private final SubjectContext subjectContext; private final SecurityManager securityManager; /** * Constructs a new {@link Subject.Builder} instance, using the {@code SecurityManager} instance available */ //这里使用了管家 SubjectContext 保存数据?被 // sent to the {@code SecurityManager} to create a new {@code Subject} instance. public Builder() { this(SecurityUtils.getSecurityManager()); } public Builder(SecurityManager securityManager) { if (securityManager == null) { throw new NullPointerException("null."); } this.securityManager = securityManager; this.subjectContext = newSubjectContextInstance(); if (this.subjectContext == null) { throw new IllegalStateException("newSubjectContextInstance' " + "cannot be null."); } //这个有点意思了,保存当前管家的一个引用。 this.subjectContext.setSecurityManager(securityManager); } /** * Creates a new {@code SubjectContext} instance to be used to populate with subject contextual data that * will then be sent to the {@code SecurityManager} to create a new {@code Subject} instance. * @return a new {@code SubjectContext} instance */ //这个有点意思,放置在管家中去建立一个Subject protected SubjectContext newSubjectContextInstance() { return new DefaultSubjectContext(); } //让后代使用 protected SubjectContext getSubjectContext() { return this.subjectContext; } public Builder sessionId(Serializable sessionId) { if (sessionId != null) { this.subjectContext.setSessionId(sessionId); } return this; } public Builder host(String host) { if (StringUtils.hasText(host)) { this.subjectContext.setHost(host); } return this; } ...... //这里才是真正的返回实例,这里调用了管家建立的方法 //SubjectContext 建立的信息,反应到当前的信息当中去处理 public Subject buildSubject() { return this.securityManager.createSubject(this.subjectContext); } }
DefaultSubjectContext的结构又是如何的?
public class DefaultSubjectContext extends MapContext implements SubjectContext
DefaultSubjectContext 中的信息字段是由MapContext这个类型安全的来维护的,DefaultSubjectContext 中的全部的字段的信息都是放置在Map中的去维护的,且能够指定返回类型的安全性,若是非法,触发异常。MapContext中主要是维护DefaultSubjectContext 中定义的字段的信息。 框架
简单介绍 DefaultSubjectContext 中的信息维护都是这样的类型学习
//这样能够指定返回的类型哦,不对的话,触发异常 public SecurityManager getSecurityManager() { return getTypedValue(SECURITY_MANAGER, SecurityManager.class); } //非空插入哦 public void setSecurityManager(SecurityManager securityManager) { nullSafePut(SECURITY_MANAGER, securityManager); }
MapContext设置得也是比较的精巧,获取的成员变量backingMap 是不容许直接引用的哦ui
private final Map<String, Object> backingMap; public MapContext() { this.backingMap = new HashMap<String, Object>(); }
不让外面直接的就引用,修改值。this
public Set<String> keySet() { return Collections.unmodifiableSet(backingMap.keySet()); } public Collection<Object> values() { return Collections.unmodifiableCollection(backingMap.values()); } public Set<Entry<String, Object>> entrySet() { return Collections.unmodifiableSet(backingMap.entrySet()); }
非空检查
protected void nullSafePut(String key, Object value) { if (value != null) { put(key, value); } }
检查获得的结果,是否是期待的呢?类型安全
isAssignableFrom()方法是从类继承的角度去判断,instanceof()方法是从实例继承的角度去判断。
isAssignableFrom()方法是判断是否为某个类的父类,instanceof()方法是判断是否某个类的子类。
Class.isAssignableFrom()是用来判断一个类Class1和另外一个类Class2是否相同或是另外一个类的子类或接口。
我记得好像是在Java神书上面说过的。
protected <E> E getTypedValue(String key, Class<E> type) { E found = null; Object o = backingMap.get(key); if (o != null) { if (!type.isAssignableFrom(o.getClass())) { String msg = "Invalid object found in SubjectContext“; throw new IllegalArgumentException(msg); } found = (E) o; } return found; }
说彪了,其实都是学习不要紧的…
继续以前的Subject的内部类建立Subject的过程最后是
这个时候和咱们的管家扯上关系了,咱们知道管家的继承结构很是的复杂,里面的处理流程很是的多,最后的实现是在
public Subject buildSubject() { return this.securityManager.createSubject(this.subjectContext); }
最后的一个管理者实现了创造subject的方法
DefaultSecurityManager,这里作了一些乱七八糟的东西很难懂,跟着业务..
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: context = ensureSecurityManager(context); //Resolve an associated Session (usually based on a referenced session ID), //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 = 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; }
获得建立Subject的工厂,建立Subject
protected SubjectFactory subjectFactory; public DefaultSecurityManager() { super(); this.subjectFactory = new DefaultSubjectFactory(); this.subjectDAO = new DefaultSubjectDAO(); } //调用的这里哦 protected Subject doCreateSubject(SubjectContext context) { return getSubjectFactory().createSubject(context); }
DefaultSubjectFactory 惟一的实现了SubjectFactory
SubjectContext 这个运输信息的,终于被弄出来了,而后呢,建立一个Subject的实现,这个是最终的目的。 DelegatingSubject 建立一个Subject的实现了
public Subject createSubject(SubjectContext context) { SecurityManager securityManager = context.resolveSecurityManager(); Session session = context.resolveSession(); boolean sessionCreationEnabled = context.isSessionCreationEnabled(); PrincipalCollection principals = context.resolvePrincipals(); boolean authenticated = context.resolveAuthenticated(); String host = context.resolveHost(); return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager); }
而后就是subjectDao保存,这个不在去看了…
可是subject.login->使用的是实现类DelegatingSubject 中的总管家的的方法,而后总管家在调用内部的实现。调用内部的验证,在调用….这样的关系就拉上了。
一、首先调用Subject.login(token)进行登陆,其会自动委托给Security Manager,调用以前必 须经过SecurityUtils. setSecurityManager()设置; 二、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证; 三、Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处能够自 定义插入本身的实现; 四、Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认 ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证; 五、Authenticator 会把相应的token 传入Realm,从Realm 获取身份验证信息,若是没有返 回/抛出异常表示身份验证失败了。此处能够配置多个Realm,将按照相应的顺序及策略进 行访问。 哈哈,这里这么多的东西,我还没开始了解呢!————————————————版权声明:本文为CSDN博主「汪小哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处连接及本声明。原文连接:https://blog.csdn.net/u012881904/article/details/53726407