文档章节

Shrio源码分析(7) - 安全管理器(SecurityManager)

xiaoqiyiye
 xiaoqiyiye
发布于 2018/02/06 18:41
字数 1867
阅读 158
收藏 1

在Shiro中SecurityManager是最核心的组件,Shiro架构的心脏,用来协调内部各安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务。当Shiro与一个Subject进行交互时,实质上是幕后的SecurityManager处理所有繁重的Subject安全操作。

SecurityManager继承了3个核心接口Authenticator, Authorizer, SessionManager,这3个接口我们在前面都已经详细的分析过了。另外SecurityManager还提供了用于登入登出和创建Subject的方法。

// 使用用户提供的AuthenticationToken进行登录
Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;

// 退出当前Subject用户
void logout(Subject subject);

// 创建Subject实例
Subject createSubject(SubjectContext context);

在SecurityManager的实现类中,逐一实现了缓存功能(CachingSecurityManager),Realm功能(RealmSecurityManager),认证功能(AuthenticatingSecurityManager),授权功能(AuthorizingSecurityManager)和Session功能(SessionsSecurityManager)。上面的这些类都是抽象类,真正实现的是DefaultSecurityManager类。下面我们先逐一对这些抽象类进行分析,最后分析DefaultSecurityManager。

缓存功能的实现

CachingSecurityManager实现SecurityManager接口的同时,还实现了Destroyable和CacheManagerAware。引用缓存管理器CacheManager来管理缓存,在设置缓存管理器时提供了一个钩子方法afterCacheManagerSet()用于做设置后的操作。另外,实现Destroyable的destroy(),用于当对象销毁时,清除缓存管理器中管理的缓存。

在Shiro 1.3版本之后还引入了事件总线(EventBus)的概念。

Realm功能的实现

RealmSecurityManager继承自CachingSecurityManager,在原有功能上添加了对Realm的操作管理。RealmSecurityManager主要是对Realm的设置,但设置Realm时关联已存在的缓存管理器到Realm对象(如果Realm实现了CacheManagerAware接口)。下面是相关方法。

public void setRealms(Collection<Realm> realms) {
	if (realms == null) {
		throw new IllegalArgumentException("Realms collection argument cannot be null.");
	}
	if (realms.isEmpty()) {
		throw new IllegalArgumentException("Realms collection argument cannot be empty.");
	}
	this.realms = realms;
	afterRealmsSet();
}

protected void afterRealmsSet() {
    //关联缓存管理器到Realms中去 
	applyCacheManagerToRealms();
	applyEventBusToRealms();
}

protected void applyCacheManagerToRealms() {
	CacheManager cacheManager = getCacheManager();
    // 判断Realm是否实现了CacheManagerAware接口,如果实现则设置到Realm中
	Collection<Realm> realms = getRealms();
	if (cacheManager != null && realms != null && !realms.isEmpty()) {
		for (Realm realm : realms) {
			if (realm instanceof CacheManagerAware) {
				((CacheManagerAware) realm).setCacheManager(cacheManager);
			}
		}
	}
}

另外,还重写了destroy()方法。如果Realm实现了Destroyable接口在销毁时就处理。

public void destroy() {
    LifecycleUtils.destroy(getRealms());
    this.realms = null;
    super.destroy();
}

认证功能的实现

在AuthenticatingSecurityManager抽象类中实现了SecurityManager的认证功能。虽然说SecurityManager实现了Authenticator接口,但是在AuthenticatingSecurityManager中并没有真正地对Authenticator接口进行实现,而是通过组合的形式委派给一个Authenticator类型的属性来处理。很明显,这个委派的对象就是我们前面分析过的ModularRealmAuthenticator。

    public AuthenticatingSecurityManager() {
        super();
        this.authenticator = new ModularRealmAuthenticator();
    }

在AuthenticatingSecurityManager中需要注意,需要将设置realms信息设置到ModularRealmAuthenticator中去。

protected void afterRealmsSet() {
	super.afterRealmsSet();
    // 判断是否为ModularRealmAuthenticator认证接口
	if (this.authenticator instanceof ModularRealmAuthenticator) {
		((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms());
	}
}

真实的认证过程由Authenticator接口来处理,也就是ModularRealmAuthenticator实例对象。

    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

授权功能的实现

AuthorizingSecurityManager的实现方式和AuthenticatingSecurityManager是一样的,授权处理工作都委托给Authorizer去实现。

public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {

    // 授权器
    private Authorizer authorizer;

    public AuthorizingSecurityManager() {
        super();
        // 默认提供的授权器
        this.authorizer = new ModularRealmAuthorizer();
    }

    protected void afterRealmsSet() {
        super.afterRealmsSet();
        // 关联realms到授权器
        if (this.authorizer instanceof ModularRealmAuthorizer) {
            ((ModularRealmAuthorizer) this.authorizer).setRealms(getRealms());
        }
    }

    public void destroy() {
        // 销毁授权器相关资源
        LifecycleUtils.destroy(getAuthorizer());
        this.authorizer = null;
        super.destroy();
    }
    
    // 省略了Authorizer接口的实现方法,所有实现都委派给authorizer去处理。
}

Session功能的实现

SessionsSecurityManager的实现和上面也是一样的,具体的实现交由SessionManager来实现。

public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {

    // 实现Session管理功能
    private SessionManager sessionManager;

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

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
        afterSessionManagerSet();
    }

    protected void afterSessionManagerSet() {
        applyCacheManagerToSessionManager();
    }

    protected void afterCacheManagerSet() {
        super.afterCacheManagerSet();
        applyCacheManagerToSessionManager();
    }

    protected void applyCacheManagerToSessionManager() {
        // 关联缓存管理器到Session管理器
        if (this.sessionManager instanceof CacheManagerAware) {
            ((CacheManagerAware) this.sessionManager).setCacheManager(getCacheManager());
        }
    }

    public void destroy() {
        LifecycleUtils.destroy(getSessionManager());
        this.sessionManager = null;
        super.destroy();
    }
}

总的来说,上面的分析都很简单,SecurityManager虽然实现了Authenticator, Authorizer, SessionManager接口,但都是通过委派的方式来实现的。这样可以更好的扩展各个接口的功能实现。在Shiro中有两个实现类DefaultSecurityManager和DefaultWebSecurityManager是我们使用的实例对象。下面我们看看DefaultSecurityManager是如何作为Shiro的核心协调整个服务的。

安全管理器(DefaultSecurityManager)

我们还是从属性和构造方法来了解在DefaultSecurityManager想要做哪些功能。

// rememberMe管理器
protected RememberMeManager rememberMeManager;
// 用来存储Subject对象的,和SessionDAO类似。
protected SubjectDAO subjectDAO;
// 从名称上看就知道是创建Subject实例的工厂对象
protected SubjectFactory subjectFactory;

public DefaultSecurityManager() {
	super();
	this.subjectFactory = new DefaultSubjectFactory();
	this.subjectDAO = new DefaultSubjectDAO();
}

public DefaultSecurityManager(Realm singleRealm) {
	this();
	setRealm(singleRealm);
}

public DefaultSecurityManager(Collection<Realm> realms) {
	this();
	setRealms(realms);
}

从上面的代码中我们会发现,由SubjectFactory来创建Subject,由SubjectDAO来存储Subject。使用RememberMeManager来管理rememberMe功能。

登录流程

登录流程很简单。首先进行认证,然后创建一个登录的Subject实例返回,登录成功后会处理rememberMe。我们把思绪返回到Shrio源码分析(5) - 用户主体(Subject)中的如下代码片段中。

// 委派给SecurityManager去执行登录,如果登录成功会返回一个
// 携带有认证成功数据的Subject对象
Subject subject = securityManager.login(this, token);

下面就开始分析securityManager.login(this, token)方法。

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
    AuthenticationInfo info;
    try {
        // 认证获得AuthenticationInfo信息,如果异常,处理失败情况
        info = authenticate(token);
    } catch (AuthenticationException ae) {
        try {
            // 登录失败后处理rememberMe
            onFailedLogin(token, ae, subject);
        } catch (Exception e) {
        }
        // 注意,异常会继续抛出
        throw ae;
    }

    // 认证成功就创建一个已经登录的Subject实例
    // 注意: subject就是用户登录时调用subject.login(token)的那个实例
    Subject loggedIn = createSubject(token, info, subject);

    // 登录成功后处理rememberMe
    onSuccessfulLogin(token, info, loggedIn);

    // 返回登录成功后的Subject实例,这个实例的信息会设置到subject中去
    return loggedIn;
}

protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
    // 创建Subject上下文,设置相关的认证信息
    SubjectContext context = createSubjectContext();
    context.setAuthenticated(true);
    context.setAuthenticationToken(token);
    context.setAuthenticationInfo(info);
    // 把登录的Subject也关联到上下文
    if (existing != null) {
        context.setSubject(existing);
    }
    // 真实的去创建一个Subject实例
    return createSubject(context);
}

public Subject createSubject(SubjectContext subjectContext) {
    // 拷贝一份SubjectContext,不去修改传进来的参数
    SubjectContext context = copy(subjectContext);

    // 确保存在SecurityManager实例,如果没有就从SecurityUtils.getSecurityManager()获取
    context = ensureSecurityManager(context);

    // 为context设置Session
    context = resolveSession(context);

    // 为context设置身份信息
    context = resolvePrincipals(context);

    // 创建Subject,是由SubjectFactory去创建的
    Subject subject = doCreateSubject(context);

    // 使用SubjectDAO保存subject
    save(subject);

    return subject;
}

在上面,我们还有一些细节没有分析到。在创建Subject时,SubjectContext中的上下文数据很重要,特别是身份信息是怎么获取的?rememberMe是怎么实现的?感兴趣的可以自己去分析。

登出流程

登出流程主要是做一些数据的清除销毁。包括rememberMe登出处理,认证器的登出处理,删除Subject,停止Session等工作。

public void logout(Subject subject) {

    if (subject == null) {
        throw new IllegalArgumentException("Subject method argument cannot be null.");
    }

    // 登出前置处理,处理rememberMe功能的登出
    beforeLogout(subject);

    PrincipalCollection principals = subject.getPrincipals();
    if (principals != null && !principals.isEmpty()) {
        // 认证器登出处理,只要是清理缓存的一些身份等信息,发起监听器监听登出操作
        Authenticator authc = getAuthenticator();
        if (authc instanceof LogoutAware) {
            ((LogoutAware) authc).onLogout(principals);
        }
    }

    try {
        // 从SubjectDAO中删除Subject
        delete(subject);
    } catch (Exception e) {
        if (log.isDebugEnabled()) {
            String msg = "Unable to cleanly unbind Subject.  Ignoring (logging out).";
            log.debug(msg, e);
        }
    } finally {
        try {
            // 将Subject关联的Session停用
            stopSession(subject);
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                String msg = "Unable to cleanly stop Session for Subject [" + subject.getPrincipal() + "] " +
                        "Ignoring (logging out).";
                log.debug(msg, e);
            }
        }
    }
}

© 著作权归作者所有

xiaoqiyiye
粉丝 17
博文 44
码字总数 80350
作品 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
shrio教程初级(八)shiro验证码与记住登录

一、前言 前面通过注解和缓存做了权限验证,这里增加验证码与记住登录功能。注意:shiro缓存是权限授权的缓存。 二、验证码 2.1编写继承FormAuthenticationFilter的权限验证自定义类 重写一个...

ycy蓝码
2015/10/20
0
0
SpringMVC +shrio 不执行userRealm方法??求解

配置文件如下: ............ userRealm方法如下: public class UserRealm extends AuthorizingRealm { @Autowired private IAdminService adminService; //授权方法 @Override protected ......

咖啡加糖
2017/01/04
410
1
Shiro源码分析-初始化-SecurityManager

开涛的《跟我学Shiro》系列已即将完成,该系列囊括了shiro的绝大部分实用功能,并且在讲解如何用的过程中,也添加了其内部实现的原理。开涛主要以Shiro的使用者为角度,所以其原理部分是穿插...

Dead_knight
2014/04/08
2K
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
672
10
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
38
0
spring cloud

一、从面试题入手 1.1、什么事微服务 1.2、微服务之间如何独立通讯的 1.3、springCloud和Dubbo有哪些区别 1.通信机制:DUbbo基于RPC远程过程调用;微服务cloud基于http restFUL API 1.4、spr...

榴莲黑芝麻糊
今天
25
0
Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
昨天
71
0
maven 环境隔离

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

之渊
昨天
69
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部