最近在学习shiro的源码,所以记录下来自己的心得。
学习源码查看网上教程一直都是很迷,源码这种东西需要自己不断去定位去看才能看明白。
那么我们现在开始:这次我们主要讲解SecurityUtils.getSubject()并从这个方法入手看看到底怎么获取到我们要的subject。
private static SecurityManager securityManager;
/**
* Returns the currently accessible {@code Subject} available to the calling code depending on
* runtime environment.
* <p/>
* This method is provided as a way of obtaining a {@code Subject} without having to resort to
* implementation-specific methods. It also allows the Shiro team to change the underlying implementation of
* this method in the future depending on requirements/updates without affecting your code that uses it.
*
* @return the currently accessible {@code Subject} accessible to the calling code.
* @throws IllegalStateException if no {@link Subject Subject} instance or
* {@link SecurityManager SecurityManager} instance is available with which to obtain
* a {@code Subject}, which which is considered an invalid application configuration
* - a Subject should <em>always</em> be available to the caller.
*/
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
1.1
这个类中只有一个securityManager对象,我们知道shiro所有的认证,授权,以及session管理都通过securityManager来进行控制,而SecurityUtils只是对其的在一层代理罢了。
从getSubject()我们能够看到,先从ThreadContext中拿取,如果没有则(new Subject.Builder()).buildSubject(); 并且绑定到当前线程中。
我们先假设当前线程中存在。这时候我们是怎么去获取我们要的subject呢。进入方法查看:
//我直接把相关ThreadContext类的相关方法全部拿出来了
//一个静态字符串
public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
//一个线程变量map
private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
//我们进入的方法
public static Subject getSubject() {
return (Subject) get(SUBJECT_KEY);
}
//get方法
public static Object get(Object key) {
if (log.isTraceEnabled()) {
String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
//只要看这一行就可以了
Object value = getValue(key);
if ((value != null) && log.isTraceEnabled()) {
String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
return value;
}
//如何获取value
private static Object getValue(Object key) {
Map<Object, Object> perThreadResources = resources.get();
return perThreadResources != null ? perThreadResources.get(key) : null;
}
1.2
可以看出来,我们进入get(SUBJECT_KEY) 也就是get一个静态字符串。get方法中只是为了调用这一句Object value = getValue(key);
这里的key就是这个静态字符串。也就是说我们所有用户访问时候其实都只是get(SUBJECT_KEY),而这个getValue方法中是使用resouce的get方法。resouce是一个线程变量,也就是所有线程都维护自己的变量不共享且互斥的。
我们进入这个resource.get() 来看看
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
1.3
这里我们简单讲解一下ThreadLocal怎么维护各线程都只访问自己的变量不干扰,若想了解,可以深入去研究下。
首先获取当前线程,之后get(t)得到 t.threadLocals; 也就是一个ThreadLocalMap对象 这个ThreadLocalMap是ThreadLocal一个内部类
这个内部类中有一个Entry内部类 以及一个Entry[] table 数组,根据hash算法获取对应的value;那么问题来了
map.getEntry(this); 这个this指的是啥。 这个this指的就是1.1中的resource变量。
所以我们大概理解下就是,各线程去访问ThreadLocal对象 resouce (其实只有一个resouce)这时候进来先获取自己线程对象,然后在线程中get得到ThreadLocalMap,若存在则使用这个resouce作为key来找并使用hash算法来算,若不存在则初始化,并set一下。那么我们明白了我们登陆的Subject对象保存在哪里以及为什么多个用户访问能够分别区分,其实就是使用了线程变量。
/我是分割线/
接下来我们来看一下如果 在ThreadContext中没有获取到Subject会去哪里取呢?
private static SecurityManager securityManager;
/**
* Returns the currently accessible {@code Subject} available to the calling code depending on
* runtime environment.
* <p/>
* This method is provided as a way of obtaining a {@code Subject} without having to resort to
* implementation-specific methods. It also allows the Shiro team to change the underlying implementation of
* this method in the future depending on requirements/updates without affecting your code that uses it.
*
* @return the currently accessible {@code Subject} accessible to the calling code.
* @throws IllegalStateException if no {@link Subject Subject} instance or
* {@link SecurityManager SecurityManager} instance is available with which to obtain
* a {@code Subject}, which which is considered an invalid application configuration
* - a Subject should <em>always</em> be available to the caller.
*/
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
进入new Subject.Builder() 以及buildSubject() 方法来看
public Builder() {
this(SecurityUtils.getSecurityManager());
}
/**
* Constructs a new {@link Subject.Builder} instance which will use the specified {@code SecurityManager} when
* building the {@code Subject} instance.
*
* @param securityManager the {@code SecurityManager} to use when building the {@code Subject} instance.
*/
public Builder(SecurityManager securityManager) {
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
}
//主要看这里设置securityManager
this.securityManager = securityManager;
//创建一个subjectContext对象
this.subjectContext = newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
"cannot be null.");
}
//在这个subjectContext中设置SecurityManager进去
this.subjectContext.setSecurityManager(securityManager);
}
public Subject buildSubject() {
//使用securityManager来创建Subject并且把这个subjectConext传进去
return this.securityManager.createSubject(this.subjectContext);
}
这个
this.subjectContext = newSubjectContextInstance();这个subject上线文就是一个map里面存放了相关信息
newSubjectContextInstance() = this.backingMap = new HashMap<String, Object>();也就是父类中的backingMap。里面为null
最后把securityManager设置进去也就是把这个安全管理器放到这个备份的map中。
然后开始创建subject把这个map传进去。
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), and 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方法的实现。 首先把这个map备份一下,采用它的副本context来处理。
之后来检测是否有securityManager,再来检测是否有session,再来检测是否能解析Principals 最后创建Subject 并且保存一下。
这个检测是否有session等等这些方法逻辑处理都是一样的,检测是否有,有就返回context没有就创建并加入到context中再返回,我们以session为例一些逻辑处理最后定位到
protected Session createSession(SessionContext sessionContext) throws AuthorizationException {
if (!WebUtils.isHttp(sessionContext)) {
String msg = "SessionContext must be an HTTP compatible implementation.";
throw new IllegalArgumentException(msg);
}
HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);
HttpSession httpSession = request.getSession();
//SHIRO-240: DO NOT use the 'globalSessionTimeout' value here on the acquired session.
//see: https://issues.apache.org/jira/browse/SHIRO-240
String host = getHost(sessionContext);
return createSession(httpSession, host);
}
这就很明白了,session其实也就是得到httpServletRequest中的getSession();进行一层包装代理。所以到此Subject对象也就创建好了回到代码1.1我们再进行和当前线程绑定一下,则就缓存到当前线程变量resource中了,这样当我们随时想要获取Subject对象都可以获得这个Subject。
都是一个一个码的,不容易啊。希望能有所帮助,很多地方可能不足请多指教。