Subject反正就好像呈现的视图。所有Subject 都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
对于上面这句话的理解呢?怎么去理解这个很重要,看看别人的代码设计的流程也是比较的清楚的,Subject都绑定到了SecurityManager,因此我们在创建Subject的时候,必须给框架的内部绑定了一个SecurityManager,在前一个博客,我们已经基本的看了SecurityManager,大致的主要的架构,现在来看看Subject的主要的源码,学习一下别人这么写的用意何在?自己也是多多的总结很有很好,看看别人的优秀代码。
和上一个一样的
shrio.ini

[users]
zhang=123
wang=123
//得到Factory对象
Factory<org.apache.shiro.mgt.SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");

//2、得到SecurityManager实例 并绑定给SecurityUtils
org.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 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的时候怎么去处理的。一步步的解开谜底。
之前已经绑定总管家了

//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的参数设置,你可以自己设置,也可以使用默认的参数。

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 是不允许直接引用的哦

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 分析的更多相关文章

  1. Shiro源代码分析之两种Session的方式

    1.Shiro默认的Session处理方式 <!-- 定义 Shiro 主要业务对象 --> <bean id="securityManager" class=& ...

  2. shiro+springboot分析思路

    文章目录 前言 一.为什么要使用shiro 二.使用步骤 1.如何认证和授权 2.如何获取数据 总结 前言 shiro和spring security等安全框架可以用户管理和权限认证 一.为什么要使用 ...

  3. Shiro源码分析之Subject和SecurityManager

    Subject 毫无疑问,Subject是Shiro最重要的一个概念. “Subject”只是一个安全术语,意味着应用程序用户的特定于安全性的“视图”.Shiro Subject实例代表单个应用程序用 ...

  4. shiro过滤器详解分析

    (原) shiro最核心的2个操作,一个是登录的实现,一就是过滤器了.登录有时间再补录说明,这里分析下shiro过滤器怎样玩的. 1.目标 这里会按如下顺序逐一看其实原理,并尽量找出其出处. 先看一下 ...

  5. Shiro源码分析

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

  6. Shiro框架 (原理分析与简单实现)

    Shiro框架(原理分析与简单实现) 有兴趣的同学也可以阅读我之前分享的:Java权限管理(授权与认证)CRM权限管理   (PS : 这篇博客里面的实现方式没有使用框架,完全是手写的授权与认证,可以 ...

  7. Shiro RememberMe 1.2.4远程代码执行漏洞-详细分析

    本文首发于先知: https://xz.aliyun.com/t/6493 0x01.漏洞复现 环境配置 https://github.com/Medicean/VulApps/tree/master ...

  8. Spring整合Shiro做权限控制模块详细案例分析

    1.引入Shiro的Maven依赖 <!-- Spring 整合Shiro需要的依赖 --> <dependency> <groupId>org.apache.sh ...

  9. shiro实战系列(十)之Subject

    毫无疑问,在 Apache Shiro 中最重要的概念就是 Subject.'Subject'仅仅是一个安全术语,是指应用程序用户的特定 安全的“视图”.一个 Shiro Subject 实例代表了一 ...

  10. shiro错误:Subject does not have permission [user:select]

    使用的是jdbcRealm,在数据库中有相应的权限 错误日志: org.apache.shiro.authz.UnauthorizedException: Subject does not have ...

随机推荐

  1. EXTJS4 Grid Filter 插件的使用 与后台数据解析------Extjs 查询筛选功能的实现

    先汗一个,一个小功能又踢腾了一天.本来这个带Demo的,但是上面介绍的不是很详细.用的时候问题不大,主要问题在文件导入方面.以为这个插件的使用和其他的不一样. 1.首先是需要引入文件的位置:如图 需要 ...

  2. windows 安装 keras

    pip install keras 报错了,看报错信息是卡在scipy上了,查了一下 https://stackoverflow.com/questions/42240720/python-scipy ...

  3. 菜鸟入门【ASP.NET Core】4:在CentOS上安装.NET Core运行时、部署到CentOS

    下载.NET Core SDK 下载地址:https://www.microsoft.com/net/download/windows 第一步:Add the dotnet product feed( ...

  4. C#7.2——编写安全高效的C#代码

    原文地址:https://docs.microsoft.com/zh-cn/dotnet/csharp/write-safe-efficient-code?view=netcore-2.1 值类型的优 ...

  5. [android] 手机卫士设备管理权限锁屏

    设备管理员 Device Admin 获取DevicePolicyManager对象,通过getSystemService(DEVICE_POLICY_MANAGER),设备策略管理器 调用Devic ...

  6. MyBatis学习笔记(三) Configuration类

    一.初探Configuration类 我们先来看一下MyBatis的XML配置文件的结构,(摘自mybatis.org) 下面这个是Configuration类的部分变量 一点不一样是不是??? 其实 ...

  7. 百度网盘满速下载器:pandownload

    http://pandownload.com/index.html 作者提示:1.软件使用 C++ 编写2.大文件需要等待一段时间才会有下载速度3.文件名含有特殊字符可能会下载出错,请重命名再下载4. ...

  8. 随机生成n个不重复的数,范围是2-32,并让其在新页面打开

    var n = 5 var timer; function suiji(){ var arr = [] // 循环生成n个随机数 for(var i=0;i<n;i++){ var num = ...

  9. windows使用笔记-安装64位windows7家庭普通版的方法

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 从msdn网站:http://msdn.itellyou.cn/ 下载<cn_windows_7_ultimate_wit ...

  10. 洛谷P3721 [AH2017/HNOI2017]单旋(线段树 set spaly)

    题意 题目链接 Sol 这题好毒瘤啊.. 首先要观察到几个性质: 将最小值旋转到根相当于把右子树变为祖先的左子树,然后将原来的根变为当前最小值 上述操作对深度的影响相当于右子树不变,其他的位置-1 然 ...