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. ABP问题速查表

    如果你领导要让你一夜之间掌握ABP,并且用ABP撸一个项目出来,你很可能很快速的过了一遍ABP文档就马上动手干活了.那么这篇文章就很适合你. 这篇文章列出了很多ABP新手问的问题和解答.注:有些同学问 ...

  2. 通过 python ssh库连接并发送命令给设备

    import paramiko import time hostname = '192.168.248.156' port = 22 user = 'zhou' passwd = ' paramiko ...

  3. Android 如何解决dialog弹出时无法捕捉Activity的back事件

    Android 如何解决dialog弹出时无法捕捉Activity的back事件 在一些情况下,我们需要捕捉back键事件,然后在捕捉到的事件里写入我们需要进行的处理,通常可以采用下面三种办法捕捉到b ...

  4. (转)Android 之生成图形验证码

    import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; impor ...

  5. Spark数据倾斜及解决方案

    一.场景 1.绝大多数task执行得都非常快,但个别task执行极慢.比如,总共有100个task,97个task都在1s之内执行完了,但是剩余的task却要一两分钟.这种情况很常见. 2.原本能够正 ...

  6. [20180814]慎用查看表压缩率脚本.txt

    [20180814]慎用查看表压缩率脚本.txt --//最近看exadata方面书籍,书中提供1个脚本,查看某些表采用那些压缩模式压缩比能达到多少.--//通过调用DBMS_COMPRESSION. ...

  7. 极致精简的webservice集成例子

    极致精简的webservice例子   看了网上好多关于webservice的例子,基本上对初学者来说都是模棱两可云里雾里,现在,我将网上关于webservice的讲解提炼出来,通过一个最简单使用并且 ...

  8. Q2Day81

    性能相关 在编写爬虫时,性能的消耗主要在IO请求中,当单进程单线程模式下请求URL时必然会引起等待,从而使得请求整体变慢. import requests def fetch_async(url): ...

  9. BM:EOS的创造者

    2018年6月EOS的主网即将上线,EOS到底是全球骗局,还是技术创? EOS币到底能涨到几何,现在还适合不适合入手...我们暂且不说.先了解一下EOS的创造者BM,以及BM的传奇经历. BM BM是 ...

  10. window scoop 修改默认安装路径

    1.运行powershell  [environment]::setEnvironmentVariable('SCOOP_GLOBAL','F:\GlobalScoopApps','Machine') ...