Subject

毫无疑问,Subject是Shiro最重要的一个概念。

“Subject”只是一个安全术语,意味着应用程序用户的特定于安全性的“视图”。Shiro Subject实例代表单个应用程序用户的安全状态和相关操作。

创建

初次创建是在AbstractShiroFilter#doFilterInternal方法中:

final Subject subject = createSubject(request, response);

protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}

创建的时候传入安全管理器,Subject.Builder是这样操作的:

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);
} public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}

这个安全管理器还是我们指定的那个DefaultWebSecurityManager,一路传过去的。这个subjectContext参数是一个DefaultSubjectContext,子接口中的Builder覆盖了父类的方法,实际赋予的是一个DefaultWebSubjectContext。

protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}

subjectFactory是安全管理器DefaultWebSecurityManager中默认的DefaultWebSubjectFactory:

public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}

最终的创建又回到:

public class DefaultWebSubjectFactory extends DefaultSubjectFactory {

    public DefaultWebSubjectFactory() {
super();
} public Subject createSubject(SubjectContext context) {
if (!(context instanceof WebSubjectContext)) {
return super.createSubject(context);
}
WebSubjectContext wsc = (WebSubjectContext) context;
SecurityManager securityManager = wsc.resolveSecurityManager();
Session session = wsc.resolveSession();
boolean sessionEnabled = wsc.isSessionCreationEnabled();
PrincipalCollection principals = wsc.resolvePrincipals();
boolean authenticated = wsc.resolveAuthenticated();
String host = wsc.resolveHost();
ServletRequest request = wsc.resolveServletRequest();
ServletResponse response = wsc.resolveServletResponse(); return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
request, response, securityManager);
} //......
}

根据相关属性new出来一个WebDelegatingSubject。

shiro中很多都是这样的继承和组合关系:

DefaultSecurityManager -> DefaultSubjectFactory -> DelegatingSubject

DefaultWebSecurityManager -> DefaultWebSubjectFactory -> WebDelegatingSubject

再回到创建的方法:

final Subject subject = createSubject(request, response);

//noinspection unchecked
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});

这个execute方法是在DelegatingSubject中实现的:

public <V> V execute(Callable<V> callable) throws ExecutionException {
Callable<V> associated = associateWith(callable);
try {
return associated.call();
} catch (Throwable t) {
throw new ExecutionException(t);
}
} public <V> Callable<V> associateWith(Callable<V> callable) {
return new SubjectCallable<V>(this, callable);
}

SubjectCallable首先构造了一个ThreadState:

public SubjectCallable(Subject subject, Callable<V> delegate) {
this(new SubjectThreadState(subject), delegate);
}

associated.call()调用ThreadState.bind():

public V call() throws Exception {
try {
threadState.bind();
return doCall(this.callable);
} finally {
threadState.restore();
}
}

SubjectThreadState的bind方法:

public void bind() {
SecurityManager securityManager = this.securityManager;
if ( securityManager == null ) {
//try just in case the constructor didn't find one at the time:
securityManager = ThreadContext.getSecurityManager();
}
this.originalResources = ThreadContext.getResources();
ThreadContext.remove(); ThreadContext.bind(this.subject);
if (securityManager != null) {
ThreadContext.bind(securityManager);
}
}

这样就妥妥地把当前subject和线程绑定到了一起(还有securityManager)。

在这里遇到一个问题,记录下:
https://www.oschina.net/question/2275855_2273492

其实这只是每次进入核心过滤器时默认为我们创建的一个Subject,当调用subject.login方法之后会再次创建一个Subject,后面登录部分会做详细介绍。

获取

Subject subject = SecurityUtils.getSubject();

public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}

绑定是通过ThreadContext,获取当然也是从其取。

public abstract class ThreadContext {

    private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);

    public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY"; private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>(); 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 Subject getSubject() {
return (Subject) get(SUBJECT_KEY);
} }

最终是到ThreadLocal中拿,不过这个ThreadLocal是 InheritableThreadLocalMap 类型的(继承自InheritableThreadLocal)。

每个线程都有一个Map

SecurityManager

<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="casRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="shiroCacheManager" />
<!-- <property name="rememberMeManager" ref="rememberMeManager" /> -->
</bean>
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.ServletContainerSessionManager"/>
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {

    Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;

    void logout(Subject subject);

    Subject createSubject(SubjectContext context);

}

安全管理器继承了Authenticator, Authorizer, SessionManager三个接口。自顶向下第一个抽象类是CachingSecurityManager,接着是RealmSecurityManager,后面是AuthenticatingSecurityManager,AuthorizingSecurityManager,SessionsSecurityManager。

然后才是DefaultSecurityManager,DefaultWebSecurityManager。

由于是web项目,我们指定了DefaultWebSecurityManager,在构造器中会为我们设置相匹配的属性(都是和web相关的):

public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}

SessionsSecurityManager持有一个sessionManager对象,对sessionManager接口的实现是转移到这个对象上来的:

public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {

    private SessionManager sessionManager;

    public SessionsSecurityManager() {
super();
this.sessionManager = new DefaultSessionManager();
applyCacheManagerToSessionManager();
}
}

AuthenticatingSecurityManager,AuthorizingSecurityManager类似,这两个抽象管理器在无参构造函数中创建了默认的对象:

public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {

    private Authorizer authorizer;

    public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
} } public abstract class AuthenticatingSecurityManager extends RealmSecurityManager { private Authenticator authenticator; public AuthenticatingSecurityManager() {
super();
this.authenticator = new ModularRealmAuthenticator();
}
}

所以这个安全管理器几乎承担了所有的操作,然后转移到具体的对象。它的层次结构非常清晰,职责分明。对Subject所有操作最终都会转移到SecurityManager。

Shiro源码分析之Subject和SecurityManager的更多相关文章

  1. Shiro源码分析之SecurityManager对象获取

    目录 SecurityManager获取过程 1.SecurityManager接口介绍 2.SecurityManager实例化时序图 3.源码分析 4.总结 @   上篇文章Shiro源码分析之获 ...

  2. Shiro 源码分析

    http://my.oschina.net/huangyong/blog/215153 Shiro 是一个非常优秀的开源项目,源码非常值得学习与研究. 我想尝试做一次 不一样 的源码分析:源码分析不再 ...

  3. Shiro源码分析

    1.入口类:AbstractAuthenticator 用户输入的登录信息经过其authenticate方法: public final AuthenticationInfo authenticate ...

  4. Shiro源码分析-初始化-Realm

    在上一篇介绍SecurityManager的初始化过程中,也有realm的粗略介绍. realm的概念在安全领域随处可见: 各种中间件的realm.spring security的realm.shir ...

  5. Shiro入门学习之shi.ini实现认证及源码分析(二)

    一.Shiro.ini文件 1.文件说明 ①ini(InitializationFile)初始文件:Window系统文件扩展名 ②Shiro使用时可以连接数据库,也可以不连接数据库(可以使用shiro ...

  6. 源码分析shiro认证授权流程

    1. shiro介绍 Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能: 认证 - 用户身份识别,常被称为用户“登录”: 授权 - 访问控制: 密码加密 ...

  7. shiro实现无状态的会话,带源码分析

    转载请在页首明显处注明作者与出处 朱小杰      http://www.cnblogs.com/zhuxiaojie/p/7809767.html 一:说明 在网上都找不到相关的信息,还是翻了大半天 ...

  8. Shiro源码解析-Session篇

    上一篇Shiro源码解析-登录篇中提到了在登录验证成功后有对session的处理,但未详细分析,本文对此部分源码详细分析下. 1. 分析切入点:DefaultSecurityManger的login方 ...

  9. spark源码分析以及优化

    第一章.spark源码分析之RDD四种依赖关系 一.RDD四种依赖关系 RDD四种依赖关系,分别是 ShuffleDependency.PrunDependency.RangeDependency和O ...

随机推荐

  1. [洛谷P1404] 平均数

    洛谷题目链接:平均数 题目描述 给一个长度为n的数列,我们需要找出该数列的一个子串,使得子串平均数最大化,并且子串长度>=m. 输入输出格式 输入格式: N+1行, 第一行两个整数n和m 接下来 ...

  2. linux sh脚本异常:/bin/sh^M:bad interpreter: No such file or directory

    在Linux中执行.sh脚本,异常/bin/sh^M: bad interpreter: No such file or directory.这是不同系统编码格式引起的:在windows系统中编辑的. ...

  3. WPF 添加Adminstrator 权限

    在WPF应用开发中,需要WPF操作后台注册的Windows Service,可是WIX打包的安装程序不具备赋予WPF App默认管理员权限. 因此,需要我们手工在WPF项目中添加管理员权限: 1.右击 ...

  4. windows修改文件的修改或者创建时间

    https://www.online-tech-tips.com/computer-tips/how-to-change-the-last-modified-date-creation-date-an ...

  5. 【uva12232/hdu3461】带权并查集维护异或值

    题意: 对于n个数a[0]~a[n-1],但你不知道它们的值,通过逐步提供给你的信息,你的任务是根据这些信息回答问题: I P V :告诉你a[P] = V I P Q V:告诉你a[P] XOR a ...

  6. bzoj3671 [Noi2014]随机数生成器

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3671 [题解] 贪心从1...n*m取,开两个5000*5000的数组就够了,可以重复利用, ...

  7. 51nod1245 Binomial Coefficients Revenge

    题目来源: HackerRank 基准时间限制:2 秒 空间限制:131072 KB 分值: 640  C(M,N) = M! / N! / (M - N)! (组合数).给出M和质数p,求C(M,0 ...

  8. js_!和!!的使用

    js中有些特殊的数据(“” 0 null undefined NaN),请求后台返回的数据中往往都有一些这样的数据,需要对这些数据进行过滤. 过滤代码 var a = 0;//0 "&quo ...

  9. Android Studio 中引入Library

    启动AndroidStudio后,打开你需要接收Library的项目.比如有两个项目,项目A,和Library项目B,那么打开项目A.图中所示为项目的结构图,点击右上角的File菜单. 2 在下拉菜单 ...

  10. 探索ReactNative应用

    本篇文章是我看AC2016腾讯前端技术交流大会后写的.写的不好,大家见谅啊. 一,什么是ReactNative? 简单来说就是可以用javascript来写APP了,而且性能还不错. 用JS写的话已经 ...