文档章节

Shrio源码分析(6) - 用户主体(Subject)

xiaoqiyiye
 xiaoqiyiye
发布于 2018/02/06 17:45
字数 1793
阅读 177
收藏 1
本文在于分析Shiro源码,对于新学习的朋友可以参考
[开涛博客](http://jinnianshilongnian.iteye.com/blog/2018398)进行学习。

Subject接口

在Shiro中Subject表示系统进行交互的用户或某一个第三方服务,所有Subject实例都被绑定到(且这是必须的)一个SecurityManager上。Subject接口提供了很多方法,主要包括进行认证的登录登出方法、进行授权判断的方法和Session访问的方法。在Shiro中获取当前运行的Subject要使用SecurityUtils.getSubject()方法来获取一个Subject实例。

下面将Subject接口方法进行分类:

  1. 和认证的相关的方法
// 返回可以鉴定Subject唯一性的身份信息,例如:用户名、用户ID等等
Object getPrincipal();

// 以PrincipalCollection形式返回身份信息
PrincipalCollection getPrincipals();

// 是否已经被认证通过
boolean isAuthenticated();

// 登录认证
void login(AuthenticationToken token) throws AuthenticationException;

// 认证登出 
void logout();
  1. 和授权相关的方法
// 是否有指定的权限
boolean isPermitted(String permission);

// 是否有指定的权限
boolean isPermitted(Permission permission);

// 是否有指定的权限
boolean[] isPermitted(String... permissions);

// 是否有指定的权限
boolean[] isPermitted(List<Permission> permissions);

// 是否有所有指定的权限
boolean isPermittedAll(String... permissions);

// 是否有所有指定的权限
boolean isPermittedAll(Collection<Permission> permissions);

// 检测是否有权限,如果没有将抛出异常
void checkPermission(String permission) throws AuthorizationException;

// 检测是否有权限,如果没有将抛出异常
void checkPermission(Permission permission) throws AuthorizationException;

// 检测是否有权限,如果没有将抛出异常
void checkPermissions(String... permissions) throws AuthorizationException;

// 检测是否有权限,如果没有将抛出异常
void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;

// 是否有指定角色
boolean hasRole(String roleIdentifier);

// 是否有指定角色
boolean[] hasRoles(List<String> roleIdentifiers);

// 是否有所有指定角色
boolean hasAllRoles(Collection<String> roleIdentifiers);

// 检测是否有指定角色,如果没有将抛出异常
void checkRole(String roleIdentifier) throws AuthorizationException;

// 检测是否有所有指定角色,如果没有将抛出异常
void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;

// 检测是否有所有指定角色,如果没有将抛出异常
void checkRoles(String... roleIdentifiers) throws AuthorizationException;
  1. 和Session相关方法
// 获取关联的Session,如果没有将会创建新Session
Session getSession();

// 获取关联的Session,如果Session不存在并且create=false,将不会创建新Session
Session getSession(boolean create);
  1. runAs相关的方法
void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;

boolean isRunAs();

PrincipalCollection getPreviousPrincipals();

PrincipalCollection releaseRunAs();

在Subject接口中还提供了一个静态内部类Builder,这个类辅助创建Subject实例。在Builder中需要引用SubjectContext和SecurityManager,SubjectContext负责收集Subject的上下文信息,SecurityManger才是真实创建Subject的对象,通过createSubject(SubjectContext subjectContext)方法来创建,SubjectContext和SessionContext类是,我们可以将它看作一个Map对象。创建Subject已经在Shiro源码分析(1) - Shiro开篇中分析过了,这里不再赘述。

Shiro登录

Shiro源码分析(1) - Shiro开篇中我们已经了解了Subject是如何创建的,这里我们将分析Shiro是如何处理登录和登出的。为了方便大家看明白些,还是贴出之前使用的代码块。

Subject subject = SecurityUtils.getSubject(); 
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
subject.login(token);

在下面的分析中我们以DelegatingSubject类来说明,DelegatingSubject是Subject接口的直接实现类。我们先看看DelegatingSubject的属性和构造方法。

// 身份凭证集合对象
protected PrincipalCollection principals;
// 是否被认证,表示该Subject是否已经被认证通过
protected boolean authenticated;
// 主机
protected String host;
// 关联的Session
protected Session session;

// 是否创建Session
protected boolean sessionCreationEnabled;

// 安全管理器,DelegatingSubject中所有的操作都是委派给SecurityManager来处理
protected transient SecurityManager securityManager;

public DelegatingSubject(SecurityManager securityManager) {
    this(null, false, null, null, securityManager);
}

public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
                         Session session, SecurityManager securityManager) {
    this(principals, authenticated, host, session, true, securityManager);
}

public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
                         Session session, boolean sessionCreationEnabled, SecurityManager securityManager) {
    // securityManager 是必须存在的,前面已经多次强调
    if (securityManager == null) {
        throw new IllegalArgumentException("SecurityManager argument cannot be null.");
    }
    this.securityManager = securityManager;
    this.principals = principals;
    this.authenticated = authenticated;
    this.host = host;
    if (session != null) {
        // 包装Session为StoppingAwareProxiedSession
        // session销毁时会进行资源释放
        this.session = decorate(session);
    }
    this.sessionCreationEnabled = sessionCreationEnabled;
}

在创建好Subject实例后,就可以调用login(AuthenticationToken token)方法操作登录了。具体分析如下:

public void login(AuthenticationToken token) throws AuthenticationException {
    
    // 清除runAs身份
    clearRunAsIdentitiesInternal();
    
    // 委派给SecurityManager去执行登录,如果登录成功会返回一个
    // 携带有认证成功数据的Subject对象
    Subject subject = securityManager.login(this, token);

    PrincipalCollection principals;

    String host = null;

    // 获取登录后的身份和主机信息
    if (subject instanceof DelegatingSubject) {
        DelegatingSubject delegating = (DelegatingSubject) subject;
        principals = delegating.principals;
        host = delegating.host;
    } else {
        principals = subject.getPrincipals();
    }

    // 如果没有身份,抛出异常
    if (principals == null || principals.isEmpty()) {
        String msg = "Principals returned from securityManager.login( token ) returned a null or " +
                "empty value.  This value must be non null and populated with one or more elements.";
        throw new IllegalStateException(msg);
    }
    
    // 设置身份到当前这个Subject实例中
    this.principals = principals;
    // 标记为已经认证过
    this.authenticated = true;
    
    // 获取主机
    if (token instanceof HostAuthenticationToken) {
        host = ((HostAuthenticationToken) token).getHost();
    }
    if (host != null) {
        this.host = host;
    }
    
    // 获取Session(就算认证成功了,Session也不一定存在)
    // false参数表示在Session不存在的情况下不会主动创建新Session
    Session session = subject.getSession(false);
    if (session != null) {
        this.session = decorate(session);
    } else {
        this.session = null;
    }
}

整理一下Subject的登录流程:

  • 需要清除runAs身份
  • 委派给SecurityManager做登录认证操作
  • 将认证成功的Subject信息设置到当前的Subject中去,标记已经被认证。

Shiro登出

Shiro登出过程很简单,代码如下,不用详细分析。

public void logout() {
    try {
        // 清除runAs身份
        clearRunAsIdentitiesInternal();
        // 委派给SecurityManager做登出操作
        this.securityManager.logout(this);
    } finally {
        // 重置属性
        this.session = null;
        this.principals = null;
        this.authenticated = false;
    }
}

从上面的分析看,登录登出都是由SecurityManager来做的,后续我们会对SecurityManager进行分析。以及对授权的分析也在SecurityManager中进行说明。

runAs

runAs是指当前用户以一个给定的身份进行认证。给定的身份信息是存放在Session中的,也就是是如果要进行runAs操作,必须开启创建Session参数sessionCreationEnabled=true,否则会抛异常。

public void runAs(PrincipalCollection principals) {
    // 首先Subject本身需要存在唯一身份
    if (!hasPrincipals()) {
        String msg = "This subject does not yet have an identity.  Assuming the identity of another " +
                "Subject is only allowed for Subjects with an existing identity.  Try logging this subject in " +
                "first, or using the " + Subject.Builder.class.getName() + " to build ad hoc Subject instances " +
                "with identities as necessary.";
        throw new IllegalStateException(msg);
    }
    // 将身份信息存放到Session属性中去
    pushIdentity(principals);
}

public boolean isRunAs() {
    // 判断runAs栈中是否有身份
    List<PrincipalCollection> stack = getRunAsPrincipalsStack();
    return !CollectionUtils.isEmpty(stack);
}

/**
 * 从runAs栈中获取身份信息
 */ 
public PrincipalCollection getPreviousPrincipals() {
    PrincipalCollection previousPrincipals = null;
    List<PrincipalCollection> stack = getRunAsPrincipalsStack();
    int stackSize = stack != null ? stack.size() : 0;
    if (stackSize > 0) {
        if (stackSize == 1) {
            previousPrincipals = this.principals;
        } else {
            //always get the one behind the current:
            assert stack != null;
            previousPrincipals = stack.get(1);
        }
    }
    return previousPrincipals;
}

public PrincipalCollection releaseRunAs() {
    return popIdentity();
}

// 从Session中获取runAs栈
private List<PrincipalCollection> getRunAsPrincipalsStack() {
    Session session = getSession(false);
    if (session != null) {
        return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
    }
    return null;
}

private void clearRunAsIdentities() {
    Session session = getSession(false);
    if (session != null) {
        session.removeAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
    }
}

private void pushIdentity(PrincipalCollection principals) throws NullPointerException {
    if (CollectionUtils.isEmpty(principals)) {
        String msg = "Specified Subject principals cannot be null or empty for 'run as' functionality.";
        throw new NullPointerException(msg);
    }
    // 获取runAs栈,如果不存在就创建
    List<PrincipalCollection> stack = getRunAsPrincipalsStack();
    if (stack == null) {
        stack = new CopyOnWriteArrayList<PrincipalCollection>();
    }
    // 保存身份到最前面
    stack.add(0, principals);
    // 获取Session,将runAs栈保存到Session中
    Session session = getSession();
    session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
}

private PrincipalCollection popIdentity() {
    PrincipalCollection popped = null;

    List<PrincipalCollection> stack = getRunAsPrincipalsStack();
    if (!CollectionUtils.isEmpty(stack)) {
        popped = stack.remove(0);
        Session session;
        if (!CollectionUtils.isEmpty(stack)) {
            session = getSession();
            session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
        } else {
            //stack is empty, remove it from the session:
            clearRunAsIdentities();
        }
    }

    return popped;
}

© 著作权归作者所有

xiaoqiyiye
粉丝 17
博文 44
码字总数 80350
作品 0
杭州
私信 提问
shrio教程初级(五)shiro基础(授权)

一、前言 回忆过去,shiro的认证忘不了: 0:通过securityFactory建立securityManager,加入securityUtil加入环境 1、subject(主体)请求认证,调用login(token) 2、securityManager开始认...

ycy蓝码
2015/10/15
0
0
Apache Shiro Java安全架构详解

目录 Apache Shiro架构详解... 1 1、高层视图... 2 2、详细架构... 4 3、Shrio设计... 8 关于作者:... 9 Apache Shiro™是一个功能强大且易于使用的Java安全框架,可执行身份验证,授权,加...

尘_竹
2018/05/22
0
0
SpringBoot集成Shiro三个渐进式项目以及Shiro功能介绍

版权声明:本文为谙忆原创文章,转载请附上本文链接,谢谢。 https://blog.csdn.net/qq_26525215/article/details/82499114 首先,本篇博客的目的的重点是这里介绍的三个SpringBoot集成Shiro...

谙忆
2018/09/07
0
0
Apache Shiro框架认识

1.1 简介 Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不...

tsmyk0715
2016/11/23
89
0
第一章 Shiro简介——跟我学习springmvc shiro mybatis

1.1 简介 Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不...

CPU滴
2017/06/14
0
0

没有更多内容

加载失败,请刷新页面

加载更多

maven 环境隔离

解决问题 即 在 resource 文件夹下面 ,新增对应的资源配置文件夹,对应 开发,测试,生产的不同的配置内容 <resources> <resource> <directory>src/main/resources.${deplo......

之渊
今天
8
0
Linux创建yum仓库

第一步、搞定自己的光盘 #创建文件夹 mkdir -p /media/cdrom #挂载光盘 mount /dev/cdrom /media/cdrom #编辑配置文件使其永久生效 vim /etc/fstab 第二步,编辑yun源 vim /ect yum.repos.d...

究极小怪兽zzz
今天
6
0
jar 更新部分文件

C:\Program Files (x86)\Java\jdk1.8.0_102\bin>jar -hIllegal option: hUsage: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...Options: -c c......

圣洁之子
今天
9
0
OSChina 周六乱弹 —— 感谢女装红薯开办了这个网站

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @胖达panda:分享歌词: 我有一只小毛驴我从来也不骑,有一天我心血来潮骑着去赶集,我手里拿着小皮鞭我心里正得意,不知怎么哗啦啦,我摔了一...

小小编辑
今天
2.6K
13
DDD(四)

1,引言 软件开发者大多趋向于将关注点放在数据上,而不是领域上。这对于刚入门的DDD的新手而言也是如此。以我目前的思考方式,数据库依然占据主要的地位。开发一个功能,首先我就会考虑我会...

MrYuZixian
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部