本文主要是介绍Shiro-Subject 分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Subject反正就好像呈现的视图。所有Subject 都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
对于上面这句话的理解呢?怎么去理解这个很重要,看看别人的代码设计的流程也是比较的清楚的,Subject都绑定到了SecurityManager,因此我们在创建Subject的时候,必须给框架的内部绑定了一个SecurityManager,在前一个博客,我们已经基本的看了SecurityManager,大致的主要的架构,现在来看看Subject的主要的源码,学习一下别人这么写的用意何在?自己也是多多的总结很有很好,看看别人的优秀代码。
和上一个一样的
shrio.ini
[users]
zhang=123
wang=123
Factory<org.apache.shiro.mgt.SecurityManager> factory =new IniSecurityManagerFactory("classpath:shiro.ini");//2、得到SecurityManager实例 并绑定给SecurityUtilsorg.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");try {//4、登录,即身份验证subject.login(token);} catch (AuthenticationException e) {//5、身份验证失败}Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录//6、退出subject.logout();
SecurityUtils:是一个非常关键的类,这里可以获取到我们的全局的资源,和当前的线程相关的,放置在ThreadLocal里面的,Subject也是如此哦,和当前的环境相关
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 codedepending 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 primarysource 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;}//每次都是先去找线程相关的,然后没有在去在备份的staticpublic 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的时候怎么去处理的。一步步的解开谜底。
之前已经绑定总管家了
//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)Subject subject = SecurityUtils.getSubject();
–>下一步从当前线程中获取Subject有没有?没有创建一个,通过Subject自己的Build设计模式,创建一个Subject,此时我们跟进Subject里面去看看。public interface Subject,Subject是个接口,Builder是一个内部静态类。这种用法你不会使用吧
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 tobe 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*///这个有点意思,放置在管家中去创建一个Subjectprotected 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 是不允许直接引用的哦
private final Map<String, Object> backingMap;public MapContext() {this.backingMap = new HashMap<String, Object>();}
不让外面直接的就引用,修改值。
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 中的总管家的的方法,然后总管家在调用内部的实现。调用内部的验证,在调用….这样的关系就拉上了。
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,将按照相应的顺序及策略进
行访问。
哈哈,这里这么多的东西,我还没开始了解呢!
这篇关于Shiro-Subject 分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!