1. What is Shiro?

  • Apache旗下一个开源的Java权限框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

2. Why is Shiro?

  • 使用Shiro可以快速完成认证、授权等功能的开发;
  • Shiro不依赖于任何容器,可以运行在web应用、非web应用、集群分布式等应用中。

3. How to use Shiro?

  • Shiro主要包括认证、授权、会话管理、加密、缓存等。整体架构图如下:

  

  • 重要名词解释:
    • Subject:应用代码直接交互的对象,代表当前“用户”。
    • SecurityManager:核心安全管理器,管理着所有Subject,负责与其它组件进行交互。
      • Authenticator:认证器接口,可以自定义实现,是 Shiro API 中身份验 证核心的入口点:验证成功将返回AuthenticationInfo 验证信息;此信息中包含了身份及凭证,若失败则抛出相应的 AuthenticationException 异常,同时可以配置认证策略即什么情况下认证通过。
      • Authorizer:授权器,控制着用户能够访问哪些资源。
      • Realm:Shiro从Realm获取安全数据(用户、角色、权限等),SecurityManager要验证用户身份必须从Realm获取相应的用户进行比较身份。一般继承 AuthorizingRealm(授权)即可,其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现)。
      • Session Manager:会话管理。
      • Cache Manager:缓存。

4. 在web应用中使用Shiro

  • 加入Shiro的配置文件 shiro.ini:
# -----------------------------------------------------------------------------
# Users and their assigned roles
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
# -----------------------------------------------------------------------------
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin

# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest

# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president

# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz

# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *

# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*

# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
# goodguy 对 winnebago 的 eagle5 有 drive 的权限
goodguy = winnebago:drive:eagle5
  • Shiro通过一个 DelegatingFilterProxy 过滤器作为入口,负责读取配置文件如 shiro.ini,然后判断 URL 是否需要登录/权限等工作,修改 web.xml 文件,添加配置如下:
<!-- DelegatingFilterProxy的作用是自动到 spring 容器查找名称为 shiroFilter 的 bean 并把所有 Filter 的操作委托于它 -->
<filter>
  <filter-name>shiroFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  <init-param>
    <param-name>targetFilterLifecycle</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>shiroFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
  • 修改Spring的配置文件 applicationContext.xml,添加如下配置:
<!-- Shiro's main business-tier object for web-enabled applications
     (use DefaultSecurityManager instead when there is no web environment)-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="cacheManager" ref="cacheManager"/>
    <!-- 配置单个的realm -->
    <!--<property name="realm" ref="MD5Realm"/>-->
    <!-- 配置authenticator,可以包含realm的list集合,还可以配置认证策略 -->
    <property name="authenticator" ref="realms"></property>
    <!-- 授权时需要从securityManager读取realms -->
    <property name="realms">
        <list>
            <ref bean="MD5Realm"></ref>
            <ref bean="SHA1Realm"></ref>
        </list>
    </property>

    <!-- 配置RememberMe的cookie的有效期,单位:秒 -->
    <property name="rememberMeManager">
        <bean class="org.apache.shiro.web.mgt.CookieRememberMeManager">
            <property name="cookie.maxAge" value="10"></property>
        </bean>
    </property>
</bean>

<!-- Let's use some enterprise caching support for better performance.  You can replace this with any enterprise
     caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
         will be creaed with a default config:
         <property name="cacheManager" ref="ehCacheManager"/> -->
    <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
         a specific Ehcache configuration to be used, specify that here.  If you don't, a default
         will be used.:
    <property name="cacheManagerConfigFile" value="classpath:some/path/to/ehcache.xml"/> -->
</bean>

<bean id="realms" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
    <property name="realms">
        <list>
            <ref bean="MD5Realm"></ref>
            <ref bean="SHA1Realm"></ref>
        </list>
    </property>
    <!--- 配置认证策略 -->
    <property name="authenticationStrategy">
        <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
    </property>
</bean>

<!-- Used by the SecurityManager to access security data (users, roles, etc).
     Many other realm implementations can be used too (PropertiesRealm,
     LdapRealm, etc. -->
<bean id="MD5Realm" class="cn.mbq.shiro.realm.MD5Realm">
    <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <!-- 使用MD5算法进行加密,并设置加密次数 -->
            <property name="hashAlgorithmName" value="MD5"></property>
            <property name="hashIterations" value="1024"></property>
        </bean>
    </property>
</bean>
<bean id="SHA1Realm" class="cn.mbq.shiro.realm.SHA1Realm">
    <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <!-- 使用SHA1算法进行加密 -->
            <property name="hashAlgorithmName" value="SHA1"></property>
            <property name="hashIterations" value="1024"></property>
        </bean>
    </property>
</bean>

<!-- =========================================================
     Shiro Spring-specific integration
     ========================================================= -->
<!-- Post processor that automatically invokes init() and destroy() methods
     for Spring-configured Shiro objects so you don't have to
     1) specify an init-method and destroy-method attributes for every bean
        definition and
     2) even know which Shiro objects require these methods to be
        called. -->
<!--  可以自动的来调用 配置在pring IOC 容器中 shiro bean的生命周期方法 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<!-- Enable Shiro Annotations for Spring-configured beans.  Only run after
     the lifecycleBeanProcessor has run: -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
      depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

<!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -
     web.xml uses the DelegatingFilterProxy to access this bean.  This allows us
     to wire things with more control as well utilize nice Spring things such as
     PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/shiro/tologin"/>
    <property name="successUrl" value="/shiro/success"/>
    <property name="unauthorizedUrl" value="/shiro/unauthorized"/>
    <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean
         defined will be automatically acquired and available via its beanName in chain
         definitions, but you can perform overrides or parent/child consolidated configuration
         here if you like: -->
    <!-- <property name="filters">
        <util:map>
            <entry key="aName" value-ref="someFilterPojo"/>
        </util:map>
    </property> -->
    <!--
    配置访问资源所需要的权限。
    url使用Ant风格:?匹配一个字符,*匹配零个或多个字符,**匹配路径中的零个或多个路径。
    采用第一次匹配优先的方式,配置的资源需要放在 /** 前。           默认的权限过滤器见枚举:org.apache.shiro.web.filter.mgt.DefaultFilter(下)。
    -->
    <!--<property name="filterChainDefinitions">
        <value>
            /shiro/login = anon
            /shiro/logout = logout
            /shiro/admin = roles[admin]
            # allow WebStart to pull the jars for the swing app:
            /*.jar = anon
            # everything else requires authentication:
            /** = authc
        </value>
    </property>-->
    <!-- 使用自定义filterChainDefinition的方式替换上面固定的过滤器 -->
    <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
</bean>

<!-- 配置工厂创建filterChainDefinitionMap的方法 -->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionFactory" factory-method="buildFilterChainDefinition"></bean>

<!-- 配置filterChainDefinition工厂 -->
<bean id="filterChainDefinitionFactory" class="cn.mbq.shiro.factory.FilterChainDefinitionFactory"></bean>
  • 默认的权限过滤器如下:
package org.apache.shiro.web.filter.mgt;

import org.apache.shiro.web.filter.authc.*;
import org.apache.shiro.web.filter.authz.*;
import org.apache.shiro.web.filter.session.NoSessionCreationFilter;

public enum DefaultFilter {

    /* 身份验证相关的 */
    // 可以匿名使用
    anon(AnonymousFilter.class),
    // 需要认证(登录)才能使用
    authc(FormAuthenticationFilter.class),
    // 需要通过 HttpBasic 验证
    authcBasic(BasicHttpAuthenticationFilter.class),
    // 注销登录,同时销毁所有 Session,web 应用程序中RembemberMe 的 cookie 也将被删除
    logout(LogoutFilter.class),
    // 必须存在用户
    user(UserFilter.class);

    /* 授权相关的 */
    // 判断当前用户是否具有指定权限,perms["user.add:*,user.modify:*"],多个参数必须全部通过才行
    perms(PermissionsAuthorizationFilter.class),
    // 指定请求访问的端口,port[8081]
    port(PortFilter.class),
    // 根据 restful 风格相应,rest[user.method](method为post、get、put、delete等)
    rest(HttpMethodPermissionFilter.class),
    // 判断当前用户是否指定角色,roles["admin,guest"]多个参数必须全部通过才行
    roles(RolesAuthorizationFilter.class),
    // 使用 HTTPS,表示安全的 URL 请求
    ssl(SslFilter.class),

    // 阻止在请求期间创建新的会话,以保证无状态的体验
    noSessionCreation(NoSessionCreationFilter.class),

    // ......
}

5. Authenticator:身份认证

  • 基本认证流程图如下:

    

    

    1. 首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager;
    2. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
    3. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证,它继承了Authenticator,另外还有一个 ModularRealmAuthenticator实现,其委托给多个Realm 进行 验证,验证规则通过 AuthenticationStrategy 接口指定;
    4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
    5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
  • AuthenticationStrategy:认证策略
    • FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略。
    • AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和 FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息。
    • AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有 Realm身份验证成功的认证信息,如果有一个失败就失败了。
    • ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy 策略。
  • 代码示例:
if (!currentUser.isAuthenticated()) {
    // 当前用户未认证,将用户名和密码封装成一个token进行登录
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
    token.setRememberMe(true);
    try {
        currentUser.login(token);
    } catch (UnknownAccountException uae) {
        System.out.println("用户名不存在:" + token.getPrincipal());
    } catch (IncorrectCredentialsException ice) {
        System.out.println("密码不正确: " + token.getPrincipal());
    } catch (LockedAccountException lae) {
        System.out.println("用户名被锁定:" + token.getPrincipal());
    }
    // other exceptions
    catch (AuthenticationException ae) {
        ae.printStackTrace();
    }
}

6. Authorizer:授权

  • 也叫访问控制,即在应用中控制谁访问哪些资源。
  • Subject:主体,代表用户,用户只有授权后才能访问资源。
  • Resource:资源,在应用中用户可以访问的URL。
  • Permission:权限,表示用户能不能访问某个资源。
  • Role:角色,权限的集合,一般情况下赋予用户角色而不是权限。
  • 授权流程:

    

    1. 首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
    2. Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过PermissionResolver 把字符串转换成相应的 Permission 实例;
    3. 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
    4. Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted*/hasRole* 会返回true,否则返回false表示授权失败。
  • 三种授权方式:
    1. 编程式,不推荐使用。
    2. 注解式,推荐
      • @RequiresAuthentication:表示当前 Subject 已经通过 login 进行了身份验证,即 Subject.isAuthenticated() 为 true。
      • @RequiresUser:表示当前 Subject 已经身份验证或者通过 RememberMe 登录的。

      • @RequiresGuest:表示当前 Subject 没有身份验证或通过 RememberMe 登录过,即是游客身份。

      • @RequiresRoles(value={“admin”,““user”}, logical=Logical.AND):表示当前 Subject 需要角色 admin 和 user。

      • @RequiresPermissions (value={“user:a”,“user:b”}, logical=Logical.OR):表示当前 Subject 需要权限 user:a 或 user:b。

    3. jsp标签,标准形式如:<shiro:xxx></shiro:xxx>
      • guest 标签:用户没有身份验证时显示相应信息,即游客访问信息。
      • user 标签:用户已经经过 认证/RememberMe 登录后显示相应的信息。

      • authenticated 标签:用户已经身份验证通过,即 Subject.login登录成功,不是 remerberMe 登录的。

      • notAuthenticated 标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括 RememberMe 自动登录的也属于未进行身份验证。

      • pincipal 标签:显示用户身份信息,默认调用 Subject.getPrincipal() 获取,即 Primary Principal。

      • hasRole 标签:如果当前 Subject 有角色将显示 body 体内容。

      • hasAnyRoles 标签:如果当前Subject有任意一个角色(或的关系)将显示body体内容。

      • lacksRole:如果当前 Subject 没有角色将显示 body 体内容。

      • hasPermission:如果当前 Subject 有权限 将显示 body 体内容。

      • lacksPermission:如果当前Subject没有权限将显示body体内容。

  • Permissions的配置:
    • 规则:资源标识符:操作:对象实例 ID 即对哪个资源的哪个实例可以进行什么操作. 其默认支持通配符权限字符串,: 表 示资源/操作/实例的分割;, 表示操作的分割,* 表示任意资 源/操作/实例。
    • 多层次管理:
      • 如:user:query、user:edit。
      • 冒号是一个特殊字符,它用来分隔权限字符串的下一部件:第一部分是权限被操作的领域(打印机),第二部分是被执行的操作。
      • 多个值:每个部件能够保护多个值。除了授予用户 user:query和 user:edit 权限外,也可以简单地授予他们一个 user:query, edit,还可以用 * 号代替所有的值,如:user:*,或:*:query,表示某个用户在所有的领域都有 query 的权限。
    • 实例级访问控制:
      • 这种情况通常会使用三个部件:域、操作、被付诸实施的实例。如 user:edit:manager。
      • 也可以使用通配符来定义,如 user:edit:*,user:*:*,user:*:manager。
      • 部分省略通配符:缺少的部件意味着用户可以访问所有与之匹配的值,比如 user:edit 等价于 user:edit :*,user 等价于 user:*:*。
      • 注意:通配符只能从字符串的结尾处省略部件,也就是说 user:edit 并不等价于 user:*:edit。

7. 会话 & 缓存

  • Shiro 内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如Realm)是否实现了 CacheManagerAware 并自动注入相应的 CacheManager。
  • Shiro 提供了 CachingRealm,其实现了 CacheManagerAware 接口,提供了缓存的一些基础实现。
  • AuthenticatingRealm 及 AuthorizingRealm 也分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓存。
  • Session,不依赖于 web 容器,会自动将 HttpSession 中的数据存放到 org.apache.shiro.session 中。
  • 如 SecurityManager 实现了 SessionSecurityManager,其会判断 SessionManager 是否实现了 CacheManagerAware 接口,如果实现了会把 CacheManager 设置给它。
  • SessionManager 也会判断相应的 SessionDAO(如继承自CachingSessionDAO)是否实现了 CacheManagerAware,如果实现了会把 CacheManager 设置给它。
  • 设置了缓存的 SessionManager,查询时会先查缓存,如果找不到才查数据库。

8. RememberMe

  • 将Cookie写入到客户端,但不记录重要的信息,如查看订单等仍需重新登录。
  • 认证和RememberMe:
    • subject.isAuthenticated() 表示用户进行了身份验证登录的,即使用 Subject.login() 进行了登录;
    • subject.isRemembered():表示用户是通过 RememberMe 登录的,存在 cookie 欺骗、窃取的风险。
    • 两者只能二选一,即 subject.isAuthenticated()==true,则 subject.isRemembered()==false;反之亦然。

Shiro学习小结的更多相关文章

  1. flex学习小结

    接触到flex一个多月了,今天做一个学习小结.如果有知识错误或者意见不同的地方.欢迎交流指教. 画外音:先说一下,我是怎么接触到flex布局的.对于正在学习的童鞋们,我建议大家没事可以逛逛网站,看看人 ...

  2. Python 学习小结

    python 学习小结 python 简明教程 1.python 文件 #!/etc/bin/python #coding=utf-8 2.main()函数 if __name__ == '__mai ...

  3. react学习小结(生命周期- 实例化时期 - 存在期- 销毁时期)

    react学习小结   本文是我学习react的阶段性小结,如果看官你是react资深玩家,那么还请就此打住移步他处,如果你想给一些建议和指导,那么还请轻拍~ 目前团队内对react的使用非常普遍,之 ...

  4. objective-c基础教程——学习小结

    objective-c基础教程——学习小结   提纲: 简介 与C语言相比要注意的地方 objective-c高级特性 开发工具介绍(cocoa 工具包的功能,框架,源文件组织:XCode使用介绍) ...

  5. pthread多线程编程的学习小结

    pthread多线程编程的学习小结  pthread 同步3种方法: 1 mutex 2 条件变量 3 读写锁:支持多个线程同时读,或者一个线程写     程序员必上的开发者服务平台 —— DevSt ...

  6. ExtJs学习笔记之学习小结LoginDemo

    ExtJs学习小结LoginDemo 1.示例:(登录界面) <!DOCTYPE html> <html> <head> <meta charset=&quo ...

  7. 点滴的积累---J2SE学习小结

    点滴的积累---J2SE学习小结 什么是J2SE J2SE就是Java2的标准版,主要用于桌面应用软件的编程:包括那些构成Java语言核心的类.比方:数据库连接.接口定义.输入/输出.网络编程. 学习 ...

  8. Shiro学习笔记(5)——web集成

    Web集成 shiro配置文件shiroini 界面 webxml最关键 Servlet 測试 基于 Basic 的拦截器身份验证 Web集成 大多数情况.web项目都会集成spring.shiro在 ...

  9. Shiro学习

    Shiro学习资源 Shiro官网,http://shiro.apache.org/index.html 学习网站链接,http://blog.java1234.com/blog/articles/4 ...

随机推荐

  1. 【计算机网络】HTTP协议详解

    详见:http://blog.csdn.net/gueter/article/details/1524447 不让转载,但写得很好  

  2. logrotate 学习使用

    logrotate Summary : Rotates, compresses, removes and mails system log files Description : The logrot ...

  3. C# 代码注释生成代码提示和帮助文档

    C#文档注释格式: /// <summary> /// function description /// </summary> /// <param name=" ...

  4. 深入分析CVE-2016-5195 Dirty Cow

    前面一段时间,这个编号为CVE-2016-5195的漏洞刷爆了各个安全相关的博客和网站,这个漏洞可以对任意可读文件进行写操作从而导致提权,通杀了包括Android在内的绝大多数linux版本,,影响不 ...

  5. Ext分区文件恢复工具extundelete

     Ext分区文件恢复工具extundelete Ext是延伸文件系统(Extended system)的缩写.它是为Linux内核开发的第一个文件系统.它有多个版本.现在常见的是Ext3和Ext4.由 ...

  6. POJ 3041 Asteroids (二分图匹配)

    [题目链接] http://poj.org/problem?id=3041 [题目大意] 一个棋盘上放着一些棋子 每次操作可以拿走一行上所有的棋子或者一列上所有的棋子 问几次操作可以拿完所有的棋子 [ ...

  7. c#异步线程:同步调用,异步调用,异步回调

    定义一个异步线程类: public class AsyEventClass { private static ILog logger = LogManager.GetLogger(MethodBase ...

  8. 【java】java反射机制,动态获取对象的属性和对应的参数值,并属性按照字典序排序,Field.setAccessible()方法的说明【可用于微信支付 签名生成】

    方法1:通过get()方法获取属性值 package com.sxd.test.controller; public class FirstCa{ private Integer num; priva ...

  9. etcd集群日常维护

    配置文件和启动参数说明 命令行 | 配置文件 | 说明 data-dir | ETCD_DATA_DIR | 指定节点的数据存储目录,包括节点ID,集群ID,集群初始化配置,Snapshot文件,若未 ...

  10. Cocos2dx 粒子销毁问题

    Cocos2dx    粒子销毁问题 DionysosLai(906391500@qq.com) 2014-7-3 之前在调试粒子特效时,在粒子编辑器有个选项是用来调整粒子的生命时间,当粒子存在的时间 ...