0、写在前面的话

最近在考虑权限相关的东西,于是就找到了Shiro,开涛老师的Shiro教程博客(《跟我学Shiro》)写得实在很好还带所有源码,所以我也就没有自己再总结各个阶段的笔记,只在这里对整个框架的核心类和部分执行过程进行了梳理和概述,以作备忘。


1、Shiro的主要特性


Shiro提供了如上图所示的特性,其中主要特性(其开发团队称之为应用安全的四大基石)如下:
  • Authentication - 身份认证 (与登陆相关,确定用户是谁)
  • Authorization - 确认权限 (确定用户能访问什么)
  • Session Management - 会话管理
  • Cryptography - 数据加密


2、Shiro如何工作

2.1 从外部看Shiro


应用代码的交互对象是 “Subject”,该对象代表了当前 “用户”,而所有用户的安全操作都会交给 SecurityManager 来管理,而管理过程中会从 Realm 中获取用户对应的角色和权限,可以把 Realm 堪称是安全数据源。

也就是说,我们要使用最简单的 Shiro 应用:
  • 通过 Subject 来进行认证和授权,而 Subject 又委托给了 SecurityManager 进行管理
  • 我们需要给 SecurityManager 注入 Realm 以便其获取用户和权限进行判断
  • (也即,Shiro 不提供用户和权限的维护,需要由开发者自行通过 Realm 注入)

2.2 从内部看Shiro

如上所述,也就可以明白 Shiro 内部的架构如下:



3、Shiro身份认证概述

先来看一段简单的代码,shiro.ini为配置文件,类为用于说明流程的代码测试类:
#shiro.ini
[users]
zhang=123
wang=123
4
 
1
#shiro.ini
2
[users]
3
zhang=123
4
wang=123

@Test
public void testHelloWorld() {
//获取SecurityManager工厂,使用shiro.ini配置文件进行初始化
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //得到SecurityManager实例,并绑定给SecurityUtils
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager); //得到Subject及创建用户名/密码身份验证token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken("zhang", "123"); try {
//登陆
subject.login(token);
} catch (AuthenticationException e) {
//身份验证失败
e.printStackTrace();
} //断言用户已经登陆
Assert.assertEquals(true, subject.isAuthenticated()); //退出
subject.logout();
}
27
 
1
@Test
2
public void testHelloWorld() {
3
    //获取SecurityManager工厂,使用shiro.ini配置文件进行初始化
4
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
5

6
    //得到SecurityManager实例,并绑定给SecurityUtils
7
    SecurityManager securityManager = factory.getInstance();
8
    SecurityUtils.setSecurityManager(securityManager);
9

10
    //得到Subject及创建用户名/密码身份验证token(即用户身份/凭证)
11
    Subject subject = SecurityUtils.getSubject();
12
    AuthenticationToken token = new UsernamePasswordToken("zhang", "123");
13

14
    try {
15
        //登陆
16
        subject.login(token);
17
    } catch (AuthenticationException e) {
18
        //身份验证失败
19
        e.printStackTrace();
20
    }
21

22
    //断言用户已经登陆
23
    Assert.assertEquals(true, subject.isAuthenticated());
24

25
    //退出
26
    subject.logout();
27
}

可以看到,SecurityManager通过配置文件进行实例化(通过工厂类产出),该配置文件中简单配置了用户名和密码,实际上配置文件上还有很多内容可以配置诸如自定义的各种类等,都可以注入到Shiro中进行替换,此处就不再展开详述。

SecurityManager 是 Shiro 的核心,这里把 SecurityManager 绑定到 SecurityUtils 中,只是为了方便后续调用一些方法。比如登陆方法 login(),看似是 subject.login() 在调用,实际上其内部也是调用了 SecurityManager 的 login() 方法。

用户的登陆信息是封装到 AuthenticationToken 实现类中进行传递的,这里使用了 Shiro 中内置的一个简单实现类 UsernamePasswordToken,然后通过 login() 方法层层调用,最终用这个 token 做了下面的事情:
  • 确定 Realm 的数量,根据 Realm 是否单一来确定执行方法 doSingleRealmAuthentication() 或 doMultiRealmAuthentication()
  • 不论哪个方法都会要求通过 Realm 和 token(getAuthenticationInfo(AuthenticationToken token)) 返回认证信息 AuthenticationInfo
  • 而如何确定并返回这个信息,也即是确认用户登录信息和授权信息的过程,是由开发者自定义(如通过数据库抓取信息对比判断等)
  • 自定义 Realm 必须实现 Realm 接口,更简单快捷的方式是继承抽象类 AuthenticatingRealm
  • 继承 AuthenticatingRealm 则需要分别实现认证方法doGetAuthenticationInfo() 和 授权方法 doGetAuthorizationInfo()

在 doGetAuthenticationInfo() 中还可以自定义密码匹配策略 CredentialsMatcher,将会进一步调用 assertCredentialsMatch() 进行密码匹配判定。当然,这些都是自行扩展,也由此 Shiro 的灵活程度可见一斑。


4、Shiro 的权限控制

Shiro 的权限控制是通过过滤器来实现的,所以其核心对象 ShiroFilter 就是整个 Shiro Web 中的门户,所有请求都会被 ShiroFilter 过滤并进行相应的链式处理。

这个处理流程是这样的:
  • ShiroFilter 执行过滤器链
  • 通过原始过滤器链获取新的过滤器链
    • FilterChainResolver 解析 url,找到对应的新的 FilterChain 过滤器链
  • 执行新的过滤器链

AbstractShiroFilter //如ShiroFilter/ SpringShiroFilter都继承该Filter
  • doFilter //Filter的doFilter
    • doFilterInternal //转调doFilterInternal
      • executeChain(request, response, chain) //执行过滤器链
        • FilterChain chain = getExecutionChain(request, response, origChain) //使用原始过滤器链获取新的过滤器链
          • chain.doFilter(request, response) //执行新组装的过滤器链

        • getExecutionChain(request, response, origChain) //获取过滤器链流程
          • FilterChainResolver resolver = getFilterChainResolver(); //获取相应的FilterChainResolver
          • FilterChain resolved = resolver.getChain(request, response, origChain); //通过FilterChainResolver根据当前请求解析到新的FilterChain过滤器链

注:FilterChainResolver 的实现类中往往通过 FilterChainManager (核心属性 filters / filterChains)维护过滤器关系链

4.1 默认过滤器

Shiro 内部提供了一个路径匹配的 FilterChainResolver 实现:PathMatchingFilterChainResolver,它会解析 shiro.ini 配置文件中 [urls] 的url模式:
[urls]
#authc 需要通过身份验证
#anon 匿名访问(即不需要登陆)
#roles 有角色限制,如roles[admin]表示需要admin角色才能访问
#perms 有权限限制,如perms["user:create"]表示要有"user:create"权限才能访问
/login=anon
/unauthorized=anon
/static/**=anon
/authenticated=authc
/role=authc,roles[admin]
/permission=authc,perms["user:create"]
11
 
1
[urls]
2
#authc 需要通过身份验证
3
#anon  匿名访问(即不需要登陆)
4
#roles 有角色限制,如roles[admin]表示需要admin角色才能访问
5
#perms 有权限限制,如perms["user:create"]表示要有"user:create"权限才能访问
6
/login=anon
7
/unauthorized=anon
8
/static/**=anon
9
/authenticated=authc
10
/role=authc,roles[admin]
11
/permission=authc,perms["user:create"]

而 PathMatchingFilterChainResolver 内部通过 FilterChainManager 维护着过滤器链,比如 DefaultFilterChainManager 实现维护着url模式与过滤器链的关系。因此我们可以通过 FilterChainManager 进行动态增加url模式与过滤器链的关系。

DefaultFilterChainManager 在实例化时会通过构造函数默认添加 DefaultFilter 中声明的过滤器:
public DefaultFilterChainManager(FilterConfig filterConfig) {
this.filters = new LinkedHashMap<String, Filter>();
this.filterChains = new LinkedHashMap<String, NamedFilterList>();
setFilterConfig(filterConfig);
//默认添加 DefaultFilter 中声明的过滤器
addDefaultFilters(true);
}
7
 
1
public DefaultFilterChainManager(FilterConfig filterConfig) {
2
    this.filters = new LinkedHashMap<String, Filter>();
3
    this.filterChains = new LinkedHashMap<String, NamedFilterList>();
4
    setFilterConfig(filterConfig);
5
    //默认添加 DefaultFilter 中声明的过滤器
6
    addDefaultFilters(true);
7
}

public enum DefaultFilter {

    anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class); }
15
 
1
public enum DefaultFilter {
2

3
    anon(AnonymousFilter.class),
4
    authc(FormAuthenticationFilter.class),
5
    authcBasic(BasicHttpAuthenticationFilter.class),
6
    logout(LogoutFilter.class),
7
    noSessionCreation(NoSessionCreationFilter.class),
8
    perms(PermissionsAuthorizationFilter.class),
9
    port(PortFilter.class),
10
    rest(HttpMethodPermissionFilter.class),
11
    roles(RolesAuthorizationFilter.class),
12
    ssl(SslFilter.class),
13
    user(UserFilter.class);
14
    
15
}

4.2 自定义过滤器

如果要注册自定义过滤器,IniSecurityManagerFactory / WebIniSecurityManagerFactory 在启动时会自动扫描ini配置文件中的 [filters] / [main] 部分并注册这些过滤器到 DefaultFilterChainManager;且创建相应的url模式与其过滤器关系链。

在 DefaultFilterChainManager 中有两个属性 Map<String, Filter> filters 和 Map<String, NamedFilterList> filterChains,这意味着我们即可注入自定义的 “过滤器” 和 “过滤器关系链(匹配链,即什么url对应执行什么filter)”

而我们自定义过滤器要做的两件事:
  • 完成自定义过滤器的编写
  • 将自定义过滤器注入到 filterChains 中去

显然在读取配置文件 shiro.ini 时就将其中的 [filters] 部分的自定义过滤器载入了:
[filters]
myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter
myFilter2=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAdviceFilter
[urls]
/**=myFilter1,myFilter2
1
 
1
[filters]  
2
myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter  
3
myFilter2=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAdviceFilter  
4
[urls]  
5
/**=myFilter1,myFilter2   

实际上你会发现,基于Spring或者SpringBoot,这种套路也是类似的,不过是面向对象(面向Bean)而已:
<!-- spring -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/security/login.html" />
<property name="successUrl" value="/home.html" />
<property name="unauthorizedUrl" value="/security/unauthorized.html" />
<property name="filters">
<map>
<entry key="anyRoles" value-ref="anyRolesAuthorizationFilter" />
</map>
</property>
<property name="filterChainDefinitions">
<value>
/admin = anyRoles[admin1,admin2]
/** = anon
</value>
</property>
</bean>
1
18
 
1
<!-- spring -->
2
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
3
    <property name="securityManager" ref="securityManager" />
4
    <property name="loginUrl" value="/security/login.html" />
5
    <property name="successUrl" value="/home.html" />
6
    <property name="unauthorizedUrl" value="/security/unauthorized.html" />
7
    <property name="filters">
8
        <map>
9
            <entry key="anyRoles" value-ref="anyRolesAuthorizationFilter" />
10
        </map>
11
    </property>
12
    <property name="filterChainDefinitions">
13
        <value>
14
            /admin = anyRoles[admin1,admin2]
15
            /** = anon
16
        </value>
17
    </property>
18
</bean>

@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//自定义过滤器
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
filtersMap.put("myAccessControlFilter", new MyAccessControlFilter());
shiroFilterFactoryBean.setFilters(filtersMap); //过滤器
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); filterChainDefinitionMap.put("/createPermission", "anon");
filterChainDefinitionMap.put("/**", "myAccessControlFilter"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
 
1
@Bean
2
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
3
    ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
4

5
    //设置SecurityManager
6
    shiroFilterFactoryBean.setSecurityManager(securityManager);
7
    //自定义过滤器
8
    Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
9
    filtersMap.put("myAccessControlFilter", new MyAccessControlFilter());
10
    shiroFilterFactoryBean.setFilters(filtersMap);
11

12
    //过滤器
13
    Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
14

15
    filterChainDefinitionMap.put("/createPermission", "anon");
16
    filterChainDefinitionMap.put("/**", "myAccessControlFilter");
17

18
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
19
    return shiroFilterFactoryBean;
20
}


5、参考链接



Shiro核心概述的更多相关文章

  1. Shiro 核心功能案例讲解 基于SpringBoot 有源码

    Shiro 核心功能案例讲解 基于SpringBoot 有源码 从实战中学习Shiro的用法.本章使用SpringBoot快速搭建项目.整合SiteMesh框架布局页面.整合Shiro框架实现用身份认 ...

  2. Apache Shiro 核心概念

    转自:http://blog.csdn.net/peterwanghao/article/details/8015571 Shiro框架中有三个核心概念:Subject ,SecurityManage ...

  3. Shiro——认证概述

    认证流程 身份认证流程 首先调用 Subject.login(token) 进行登录,其会自动委托给SecurityManager SecurityManager 负责真正的身份验证逻辑:它会委托给A ...

  4. Javascript核心概述 - 深入了解javascript

    /* 一.执行上下文:堆栈(底部全局上下文+顶部当前活动上下文) */ /* 二.变量对象: 变量根据执行上下文,找到数据存储位置,这种机制叫变量对象 1. 变量都要var定义,且都不能delete ...

  5. shiro 核心单词

    subject             [ˈsʌbdʒekt]      主体principal           [ˈprɪnsəpəl]      身份信息credential          ...

  6. 编程从入门到放弃(Java)

      1.Java入门篇 1.1 基础入门和面向对象 1.1.1 编程基础 [01] Java语言的基本认识 [02] 类和对象 [03] 类的结构和创建对象 [04] 包和访问权限修饰符 [05] 利 ...

  7. springboot+shiro

    作者:纯洁的微笑 出处:http://www.ityouknow.com/ 这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公 ...

  8. spring boot(十四)shiro登录认证与权限管理

    这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在Java领域一般有Spring Security ...

  9. Spring Cloud之路:(七)SpringBoot+Shiro实现登录认证和权限管理

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sage_wang/article/details/79592269一.Shiro介绍1.Shiro是 ...

随机推荐

  1. java集合继承关系图

    面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式. 数组虽然也可以存储对象,但长度是固定的:集合长度是可变的,数组中可以存储基 ...

  2. Linux 中提高的 SSH 的安全性

    SSH 是远程登录 Linux 服务器的最常见的方式.且 SSH 登录的时候要验证的,相对来讲会比较安全.那只是相对,下面会介绍一些方式提高 SSH 的安全性 SSH 的验证 而SSH 登录时有两种验 ...

  3. 《React与Redux开发实例精解》读书笔记

    第五章 JSX语法 class属性改为className for属性改为htmlFor jsx中javascript表达式必须要有返回值,使用三元操作符 所有的标签必须闭合 input img等 re ...

  4. 根据class判断

    一.生命不息,代码不止

  5. java传输文件的简单方法

    假设现在已经打包了一个文件(1233444333),要将这个文件传输给另一方: package file; import java.io.*; public class F_PasswordUnPas ...

  6. C#-继承(十一)

    继承概念 承用于创建可重用.扩展和修改在其他类中定义的行为的新类 创建一个类的时候,不是要写全新的数据成员和成员函数,可以指定新的类继承一个已经存在的类的成员.已有的类称为基类,新的类称为派生类 派生 ...

  7. 自动化测试基础篇--Selenium iframe定位问题

    摘自https://www.cnblogs.com/sanzangTst/p/7473437.html 有时候我们在定位的途中发现一个现象,元素就在那儿,不离不去,但是我们怎么整就是定不了位,这个时候 ...

  8. Oracle 锁机制探究

    以前虽然在网上看到很多关于Oracle锁机制的描述,但总感觉哪里有缺陷不适合自己,因此花了点时间参考官网以及Tom Tyke的<Oracle 9i/10g/11g编程艺术>一书整理了一下O ...

  9. [Hive_4] Hive 插入数据

    0. 说明 Hive 插入数据的方法 && Hive 插入数据的顺序 && 插入复杂数据的方法 && load 命令详解 1. Hive 插入数据的方法 ...

  10. Linux下memcache编译安装与基本使用

    memcache是一套分布式的高速缓存系统,特点为key-value 存储 一.在 linux 编译安装memcache.redis等,需要 gcc,make,cmake,autoconf,libto ...