前言

shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。

spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖过于紧密,没有shiro使用简单。

shiro不依赖于spring,shiro不仅可以实现 web应用的权限管理,还可以实现c/s系统,分布式系统权限管理,shiro属于轻量框架,越来越多企业项目开始使用shiro。

Shiro运行流程学习笔记

项目中使用到了shiro,所以对shiro做一些比较深的了解。

也不知从何了解起,先从shiro的运行流程开始。

运行流程

  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,将按照相应的顺序及策略进行访问。

绑定线程

这里从看项目源码开始。

看第一步,Subject.login(token)方法。

UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
subject.login(token);

出现了一个UsernamePasswordToken对象,它在这里会调用它的一个构造函数。

public UsernamePasswordToken(final String username, final String password, final boolean rememberMe) {
this(username, password != null ? password.toCharArray() : null, rememberMe, null);
}

据笔者自己了解,这是shiro的一个验证对象,只是用来存储用户名密码,以及一个记住我属性的。

之后会调用shiro的一个工具类得到一个subject对象。

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

通过getSubject方法来得到一个Subject对象。

这里不得不提到shiro的内置线程类ThreadContext,通过bind方法会将subject对象绑定在线程上。

public static void bind(Subject subject) {
if (subject != null) {
put(SUBJECT_KEY, subject);
}
}
public static void put(Object key, Object value) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} if (value == null) {
remove(key);
return;
} ensureResourcesInitialized();
resources.get().put(key, value); if (log.isTraceEnabled()) {
String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
}

shirokey都是遵循一个固定的格式。

public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";

经过非空判断后会将值以KV的形式put进去。

当你想拿到subject对象时,也可以通过getSubject方法得到subject对象。

在绑定subject对象时,也会将securityManager对象进行一个绑定。

而绑定securityManager对象的地方是在Subject类的一个静态内部类里(可让我好一顿找)。

getSubject方法中的一句代码调用了内部类的buildSubject方法。

subject = (new Subject.Builder()).buildSubject();

PS:此处运用到了建造者设计模式,可以去菜鸟教程仔细了解

进去观看源码后可以看见。

首先调用无参构造,在无参构造里调用有参构造函数。

public Builder() {
this(SecurityUtils.getSecurityManager());
} 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);
}

在此处绑定了securityManager对象。

当然,他也对securityManager对象的空状况进行了处理,在getSecurityManager方法里。

public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
SecurityManager securityManager = ThreadContext.getSecurityManager();
if (securityManager == null) {
securityManager = SecurityUtils.securityManager;
}
if (securityManager == null) {
String msg = "No SecurityManager accessible to the calling code, either bound to the " +
ThreadContext.class.getName() + " or as a vm static singleton. This is an invalid application " +
"configuration.";
throw new UnavailableSecurityManagerException(msg);
}
return securityManager;
}

真正的核心就在于securityManager这个对象。

SecurityManager

SecurityManager是一个接口,他继承了步骤里所谈到的AuthenticatorAuthorizer类以及用于Session管理的SessionManager

public interface SecurityManager extends Authenticator, Authorizer, SessionManager {

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

    void logout(Subject subject);

    Subject createSubject(SubjectContext context);
}

看一下它的实现。

且这些类和接口都有依次继承的关系。

Relam

接下来了解一下另一个重要的概念Relam

Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当与像用户帐户这类安全相关数据进行交互,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找很多内容。

从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件 等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

一般情况下,都会自定义Relam来使用。

先看一下实现。

以及自定义的一个UserRelam

看一下类图。

每个抽象类继承后所需要实现的方法都不一样。

public class UserRealm extends AuthorizingRealm

这里继承AuthorizingRealm,需要实现它的两个方法。

//给登录用户授权
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals); //这个抽象方法属于AuthorizingRealm抽象类的父类AuthenticatingRealm类 登录认证,也是登录的DAO操作所在的方法
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

之后再来看看这个验证方法,在之前的步骤里提到了,验证用到了Authenticator ,也就是第五步。

Authenticator

Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。

再回到之前登录方法上来看看。

subject.login(token)在第一步中调用了Subjectlogin方法,找到它的最终实现DelegatingSubject类。

里面有调用了securityManagerlogin方法,而最终实现就在DefaultSecurityManager这个类里。

Subject subject = securityManager.login(this, token);

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}

之后就是验证流程,这里我们会看到第四步,点进去会到抽象类AuthenticatingSecurityManager。再看看它的仔细调用。

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

真正的调用Relam进行验证并不在这,而是在ModularRealmAuthenticator

他们之间是一个从左到右的过程。

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}

在这里咱们就看这个doSingleRealmAuthentication方法。

Relam验证。

protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" +
token + "]. Please ensure that the appropriate Realm implementation is " +
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
//在此处调用你自定义的Relam的方法来验证。
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " +
"submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
}
return info;
}

再看看多Relam的。

protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {

    AuthenticationStrategy strategy = getAuthenticationStrategy();

    AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

    for (Realm realm : realms) {

        aggregate = strategy.beforeAttempt(realm, token, aggregate);

        if (realm.supports(token)) {

            AuthenticationInfo info = null;
Throwable t = null;
try {
//调用自定义的Relam的方法来验证。
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
} aggregate = strategy.afterAttempt(realm, token, info, aggregate, t); } else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
} aggregate = strategy.afterAllAttempts(token, aggregate); return aggregate;
}

会发现调用的都是RelamgetAuthenticationInfo方法。

看到了熟悉的UserRelam,此致,闭环了。

但是也只是了解了大概的流程,对每个类的具体作用并不是很了解,所以笔者还是有很多地方要去学习,不,应该说我本来就是菜鸡,就要学才能变带佬。

最后

大家看完有什么不懂的可以在下方留言讨论,也可以关注我私信问我,我看到后都会回答的。也欢迎大家关注我的公众号:前程有光,马上金九银十跳槽面试季,整理了1000多道将近500多页pdf文档的Java面试题资料放在里面,助你圆梦BAT!文章都会在里面更新,整理的资料也会放在里面。谢谢你的观看,觉得文章对你有帮助的话记得关注我点个赞支持一下!

厉害啊!第一次见到把Shiro运行流程写的这么清楚的,建议收藏起来慢慢看的更多相关文章

  1. shiro原理及其运行流程介绍

    shiro原理及其运行流程介绍 认证执行流程 1.通过ini配置文件创建securityManager 2.调用subject.login方法主体提交认证,提交的token 3.securityMan ...

  2. 使用 paramsPrepareParamsStack 拦截器栈后的运行流程

    2. 使用 paramsPrepareParamsStack 拦截器栈后的运行流程 1). paramsPrepareParamsStack 和 defaultStack 一样都是拦截器栈. 而 st ...

  3. SpringMVC 运行流程以及与Spring 整合

    1. 运行流程 2. Spring 和 SpringMVC 整合 // 1. 导入 jar 包 // 2. 配置 web.xml <!-- 配置 Spring 的核心监听器 --> < ...

  4. 宜信开源|分布式任务调度平台SIA-TASK的架构设计与运行流程

    一.分布式任务调度的背景 无论是互联网应用或者企业级应用,都充斥着大量的批处理任务.我们常常需要一些任务调度系统来帮助解决问题.随着微服务化架构的逐步演进,单体架构逐渐演变为分布式.微服务架构.在此背 ...

  5. 身份认证系统(四)OAuth2运行流程

    上一节介绍过什么是OAuth2,这节准备用生动的事例来告诉大家OAuth2运行的流程. 我们来想这样一个场景:假设我们有一个叫做万方网盘的服务是用来帮助用户存储论文文档的,我们向外提供了符合OAuth ...

  6. 【转】 Python生成器generator之next和send运行流程

    原文链接:https://blog.csdn.net/pfm685757/article/details/49924099 对于普通的生成器,第一个next调用,相当于启动生成器,会从生成器函数的第一 ...

  7. react-native start 运行流程

    在CMD下键入 C:\Node_JS\MyAwesomeProject>react-native start 运行流程: C:\Users\Grart\AppData\Roaming\npm\r ...

  8. 1、CC2541蓝牙4.0芯片中级教程——基于OSAL操作系统的运行流程了解+定时器和串口例程了解

    本文根据一周CC2541笔记汇总得来—— 适合概览和知识快速索引—— 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 ...

  9. java里的分支语句--程序运行流程的分类(顺序结构,分支结构,循环结构)

    JAVA里面的程序运行流程分三大类: 1,顺序结构:顺序结构就是依次执行每一行代码 2,分支结构:分支结构就是按不同的条件进行分支 3,循环结构:一段代码依条件进行循环执行. 其中,分支结构有两大类: ...

随机推荐

  1. Python基础及爬虫入门

    **写在前面**我们在学习任何一门技术的时候,往往都会看很多技术博客,很多程序员也会写自己的技术博客.但是我想写的这些不是纯技术博客,我暂时也没有这个能力写出 Python 或者爬虫相关的技术博客来. ...

  2. node.js操作MySQL数据库

    MySQL数据库作为最流行的开源数据库.基本上是每个web开发者必须要掌握的数据库程序之一了. 基本使用 node.js上,最受欢迎的mysql包就是mysql模块. npm install mysq ...

  3. Docker学习—概念及基本应用

    1.Doker基本概念: Docker架构: Docker使用客户端-服务器架构.Docker客户端与Docker守护进程进行对话,该守护进程完成了构建,运行和分发Docker容器的繁重工作  相关描 ...

  4. 梯度vs Jacobian矩阵vs Hessian矩阵

    梯度向量 定义: 目标函数f为单变量,是关于自变量向量x=(x1,x2,-,xn)T的函数, 单变量函数f对向量x求梯度,结果为一个与向量x同维度的向量,称之为梯度向量: 1. Jacobian 在向 ...

  5. 一站式Web开发套件BeetleX.WebFamily

    BeetleX.WebFamily是一款前后端分离的Web开发套件,但它并不依赖于nodejs/npm/webpack等相关工具:而使用自身实现的方式来完成前后端分离的Web应用开发:套件以组件的方式 ...

  6. oracle 查询数据库锁及锁处理

    1.数据库锁表查询语句: SELECT SESS.SID, SESS.SERIAL#, LO.ORACLE_USERNAME, LO.OS_USER_NAME, AO.OBJECT_NAME 被锁对象 ...

  7. 手把手教你使用 Prometheus 监控 MySQL 与 MariaDB.md

    概述 MySQL 是常用的关系型数据库,MariaDB 作为 MySQL 的分支版本,兼容 MySQL 协议,也越来越流行.在 Kubernetes 环境中如何使用 Prometheus 来对它们进行 ...

  8. 4G模块是什么 4G模块的工作原理

    4G模块是什么 4G模块,又叫4G传输模块.4G通信模块.4G LTE模块.它是一种硬件加载到指定频段,软件支持标准的LTE协议,且软硬件高度集成模组化的产品,具有兼容性好.通信速度快.通信数据量大. ...

  9. 企业级docker-registry原生镜像仓库高可用部署

    简介: 私有镜像仓库可以方便企业,或个人开发者共享内部镜像而不会泄漏私有代码,而且可以加速镜像的拉取.能更加方便得集成到容器化的 CI/CD 中去.也可建立自己的公共镜像仓库. 优势: Docker ...

  10. Spring Boot API 统一返回格式封装

    今天给大家带来的是Spring Boot API 统一返回格式封装,我们在做项目的时候API 接口返回是需要统一格式的,只有这样前端的同学才可对接口返回的数据做统一处理,也可以使前后端分离 模式的开发 ...