Shiro 学习应用

和 Spring Security 一样,Shiro 也属于权限安全框架。和 Spring Security 相比,Shiro 更简单,学习曲线更低。关于 Shiro 的一系列特征及优点,很多文章已有列举,这里不再逐一赘述。这里记下学习 Spring 4.x + Shiro  1.2 的过程,可能有水平不够的地方,敬请指正。

一点概念

所有操作其实离不开理论、基础概念。虽然有点啰嗦、晦涩,但出于真正掌握的目的,仍是要强调其价值的。Shiro 为 Java 程序提供了认证(Authentication)、授权(Authorization)、加密(Encryption)和会话(Session)等等诸多功能。这里所提的话题若展开来说一个个那都是宏大的命题,因此本文将会蜻蜓点水般点出概念。

  • 所谓“认证”,就是搞清楚“我是谁”的过程:在认证过程中,用户需要提交实体信息(Principals)和凭据信息(Credentials)以检验用户是否合法。最常见的“实体/凭证”组合便是“用户名/密码”组合;
  • “授权”就是搞清楚我是谁之后,确定我能够做什么的问题:一般情况下用户通过了身份验证可以登录到某系统,但是没有特定的权限,或者根本未经过授权,不准做任何事情(虽然登录了)。有时一种可能是用户虽然具有了某种程度的授权,却并未经过身份验证(典型如“游客”)。
  • 加密就是不是明文报文的意思,得给人家看不出和破解不出那是啥的意思。
  • 会话与 HTTP 服务器的“会话”有点贴近却不尽相同。

归根到底,最后结果是我到底能不能做某样事情,可以对该命题作出 true 或 false 的结果。若展开来讲里面又分几个层次,首先的是“用户”,用户有用户名和密码,显然那是自然而然要存在的事物,没有用户便没有余下的操作。用户于 Shiro 框架中所对应的概念是 Subject;然后我们把“能不能做事情”的操作分为权限 Permission 和角色 Role 两大抽象概念。Permission 可以理解为对一个资源的操作,典型的如 CRUD 操作,可以是多个的。但是这里务必强调,我们用户不能直接和权限 Permission 打交道,而是必须经过 Role。角色 Role 实质是“包着”权限的,等于是权限的集合。——为什么要“如此费劲”呢?其中之要义比较难一时半刻说清楚。随着理解的深入我们会渐渐明白其用心的。这里我们要清楚,用户信息与角色 Role 之间构成了多对多关系,表示同一个用户可以拥有多个 Role,一个 Role 可以被多个用户所拥有,而 Role 又与 Permission 之间构成多对多关系,如下面类图所示。

大概是这几种逻辑过程了,我们要好好懂得 Shiro 具体是怎么做的,以及学会运用它。

调用 & 配置 Shiro

程序第一步的仍然是使用 Servlet 的过滤器,相当于“入口”。不过不是直接指定 Shiro 的类,而是通过 Spring MVC 的代理过滤器和 Spring IOC 两者合力加载 Shiro。这里发挥了 Spring 依赖注射的威力,使得配置 Shiro 变得简单(无须很多教程所使用的 ini 文件)。我们先看看 web.xml 的配置。

<!-- 通过过滤代理类与 Spring 集成 -->
<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>
<!-- // -->

明显,我们定义了该 web 项目所有的 url 路径均受 Shiro 过问并加以控制,于是定义了 <url-pattern>/*</url-pattern> 全部路径。

其中注意尽量把这个过滤器放在其他过滤器之前,保证安全检验为“第一道板斧”;另外过滤器的名字(该例是 shiroFilter) 要与 Spring 里面配置的 bean 名字一致,方能正确调用。其中 init-param 声明的参数有何作用呢?原来是说明生命周期由 ServletContainer 管理(true 情况下如此,如果是 false 则是由 SpringApplicationContext 管理)。

上述是结合 Spring 的情形,如果没有 Spring 而是原生 Servlet 开发,那是这样的

<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
...
<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping

程序第二步是 MVC 的配置文件。既然上述提到 filter 的名字与 MVC 里面配置的一致,那么 Shiro 的配置在哪里呢?详见下面的 springMVC-servlet.xml。

ShiroFilterFactoryBean 是 Shiro 与 Spring 进行对接的工厂类,Spring 会在容器中查找名字为 shiroFilter(filter-name)的 bean 并将所有 Filter 的操作委托给它。Web 应用中 Shiro 控制的 Web 请求都必须经过 Shiro 主过滤器的拦截。关于过滤器的深入理解,可以参见这文章《ShiroFilterFactoryBean 源码及拦截原理深入分析》

接着的工作就是如上图所示,一步步查找依赖的 bean。紧接着是 SecurityManager,为 Shiro 的核心类(典型的 Facade 模式),Shiro 通过 SecurityManager 来管理内部组件实例,处理了大部分认证授权会话的关键工作。这里我们是 Web 环境,使用了默认的 WebSecurityManager。Shrio 支持 Servlet 的 session 和其自身的 session,后者用于脱离 Web 的环境。WebSecurityManager 默认使用 Servlet 的 session。我们可通过 sessionMode 属性来指定使用 Shiro 原生 Session,即 <property name="sessionMode" value="native" />。

SecurityManager 中出现了一个必填的属性: Realm,它到底是什么呢?前面提到“我是谁”的一个问题,置于 Shiro 语境中就是 Realm 负责要解决的问题。也就是说,Shiro 获取所需要的用户信息,从 Realm 获取。用户信息包括用户账号名称、密码这一类信息。Realm 又从哪里获取这些信息呢?就是数据源——当然此处的数据源是个抽象的、广泛的概念。具体数据源可以是 JDBC(一般实际编码中就是 UserServcie 类提供)、LDAP 甚至 Shiro 默认的 ini 也可以。总之,我们可以说 Realm 是专用于安全框架的 DAO(Data Access Object)。Realm 在Shiro 具体对应的类是 AuthorizingRealm,另外还有现成的子类供我们使用:JdbcRealm、InitRealm、PropertiesRealm 等。如果不满足我们可以继承 AuthorizingRealm,并重写认证授权方法。

值得一提的是,配置多个 Realm 是可以的。若有多个 Realm,可用 'realms' 属性代替。如下例子所示。

<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
	<property name="credentialsMatcher" ref="credentialsMatcher"></property>
	<property name="authenticationQuery" value="select password from user where username = ?"></property>
	<property name="dataSource" ref="dataSource"></property>
</bean>

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	<property name="realms">
		<list>
			<ref bean="jdbcRealm" />
		</list>
	</property>
</bean>

图中的最后一步,我们定义了 shiroDbRealm 的 bean。这就是继承 AuthorizingRealm 的自定义 bean,由此我们可以看到 Shiro 是怎么认证和授权的工作的。

认证和授权

假设有一 url 正在受 Shiro 保护,用户访问的时候,Shiro 首先会对其身份进行识别,如果该身份通过验证,则接着进行权限的校验,否则跳到登录页面。这个过程就是代码中 AuthenticatingRealm.doGetAuthenticationInfo() 的逻辑。然后的权限校验(也称作 授权校验),需要的用户权限信息包括 Role 或 Permission,可以是其中任何一种或同时两者,具体取决于受保护资源的配置。如果用户权限信息未包含 Shiro 需要的 Role 或 Permission,则授权不通过。只有授权通过,才可以访问受保护 URL 对应的资源,否则跳转到“未经授权页面”。这个过程就是代码中 AuthenticatingRealm.doGetAuthorizationInfo() 的逻辑。值得注意的是 Authentication 和 Authorization 虽然字面贴近,但千万不要傻傻分不清,它们存在着微妙的不同。

下面用代码来说明上述过程。首先接收到请求的,仍然是控制器。

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    @Controller("loginAction")
    @RequestMapping("/login")
    public class LoginAction  {
        @RequestMapping("")
           //登录
        public ModelAndView execute(HttpServletRequest request,
                HttpServletResponse response,String username,String password) {
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
            //记录该令牌
            token.setRememberMe(false);
            //subject权限对象
            Subject subject = SecurityUtils.getSubject();
            try {
                subject.login(token);
            } catch (UnknownAccountException ex) {//用户名没有找到
                ex.printStackTrace();
            } catch (IncorrectCredentialsException ex) {//用户名密码不匹配
                ex.printStackTrace();
            }catch (AuthenticationException e) {//其他的登录错误
                e.printStackTrace();
            }  

            //验证是否成功登录的方法
            if (subject.isAuthenticated()) {
                return new ModelAndView("/main/index.jsp");
            }
            return new ModelAndView("/login/login.jsp");
        }  

            //退出
        @RequestMapping("/logout")
        public void logout() {
            Subject subject = SecurityUtils.getSubject();
            subject.logout();
        }
    }  

控制器代码中用到了 UsernamePasswordToken。这里增加一点 Shiro 的令牌概念。在 Shiro 术语中,令牌 Token 指的是一个键,可用它登录到一个系统。最基本和常用的令牌是 UsernamePasswordToken,表示指定用户的用户名和密码。UsernamePasswordToken 类实现了 AuthenticationToken 接口,它提供了一种获得凭证和用户的主体(帐户身份)的方式。UsernamePasswordToken 适用于大多数应用程序,并且您还可以在需要的时候扩展 AuthenticationToken 接口来将您自己获得凭证的方式包括进来。例如验证码的应用就需要扩展这个 UsernamePasswordToken。

控制器中没有进行身份判断,该工作交到ShiroDbRealm 类完成。自定义的 Realm 如下代码,实现了 doGetAuthenticationInfo 和 doGetAuthorizationInfo,比较简单。

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class ShiroDbRealm extends AuthenticatingRealm {
	/**
	 *
	 * 认证回调函数,登录时调用.
	 * 授权方法,在配有缓存的情况下,只加载一次。
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;

        String userName = token.getUsername();

        if (user != null) {
            return new SimpleAuthenticationInfo(userName, token.getPassword(), getName());
        } else {
            return null;
        }
	}

	/**
	 *
	 * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
	 *
	 */
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		String loginName = (String) principals.fromRealm(getName()).iterator().next();

		Object user = "";

		if (user != null) {
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			info.addStringPermission("common-user");
			return info;
		} else {
			return null;
		}
	}

}

上述 doGetAuthenticationInfo(AuthenticationToken authcToken) 也有 UsernamePasswordToken。一般 MVC 的做法是在从 LoginController 里面 currentUser.login(token) 设置令牌,传到这里变成 authcToken,实际两个 token 的引用都是一样的。

这里为了简单起见,没有复杂的业务判断,实际过程还是需要一些控制的,例如 user 是否 null 等等。Shiro 为我们提供了丰富的异常准备。

  • DisabledAccountException (禁用的帐号)    
  • LockedAccountException (锁定的帐号)    
  • UnknownAccountException(错误的帐号)   
  • ExcessiveAttemptsException(登录失败次数过多)   
  • IncorrectCredentialsException (错误的凭证)   
  • ExpiredCredentialsException (过期的凭证)   
  • ……

若身份验证成功的话,会直接跳转到之前的访问地址或是 successfulUrl 去。相关 url 在 MVC 配置文件中定义。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	<property name="securityManager" ref="securityManager" />
	<property name="loginUrl" value="/common/security/login" />
	<property name="successUrl" value="/common/security/welcome" />
	<property name="unauthorizedUrl" value="/common/security/unauthorized" />
	……
</bean>

doGetAuthorizationInfo(PrincipalCollection principals) 代码中用到了 Principal。Principal 是安全领域术语,即用户 Subject 之标识,一般情况下是唯一标识,比如用户名。doGetAuthorizationInfo 具体作用就是获取用户权限信息,也就是“授权”就是搞清楚我是谁之后,确定我能够做什么的问题。

URL过滤器的规则

一个 Web 程序下面的 URL 的权限肯定不会都相同的,因此我们需要配置 Shiro,声明不同 url 对应的权限。我们仍旧回看 springMVC-servlet.xml 配置文件。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	<property name="securityManager" ref="securityManager" />
	<property name="loginUrl" value="/common/security/login" />
	<property name="successUrl" value="/common/security/welcome" />
	<property name="unauthorizedUrl" value="/common/security/unauthorized" />
	<property name="filterChainDefinitions">
		<value>
			/resources/** = anon
			/manageUsers = perms[user:manage]
			/** = authc
		</value>
	</property>
</bean>

其中 filterChainDefinitions 配置了 url 对应的过滤器。Filter Chain 定义说明:URL目录是基于 HttpServletRequest.getContextPath() 此目录设置,也就是 web 网站的根目录;URL 可使用通配符,** 代表任意子目录;Shiro 验证 URL 时,URL 匹配成功便不再继续匹配查找。所以要注意配置文件中的 URL 顺序,尤其在使用通配符时;一个 URL 可以配置多个 Filter,使用逗号分隔,当全部 Filter 验证通过时方能通过 。

Filter Name Class
anon 匿名 org.apache.shiro.web.filter.authc.AnonymousFilter
authc 表单 org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

一些例子如下。

anon:例子 /admins/**=anon 没有参数,表示可以匿名使用。
authc:例如 /admins/user/**=authc 表示需要认证(登录)才能使用,没有参数。
authcBasic:例如 /admins/user/**=authcBasic没 有参数表示 httpBasic 认证。
roles:例子 /admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如 admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于 hasAllRoles() 方法。
perms:例子 /admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于 isPermitedAll() 方法。
rest:例子 /admins/user/**=rest[user],根据请求的方法,相当于 /admins/user/**=perms[user:method] ,其中 method 为post,get,delete 等。
port:例子 /admins/user/**=port[8081],当请求的 url 的端口不是 8081 是跳转到 schemal://serverName:8081?queryString,其中 schmal 是协议 http 或 https 等,serverName 是你访问的host,8081是url配置里port的端口,queryString 是你访问的 url 里的?后面的参数。
ssl:例子/admins/user/**=ssl没有参数,表示安全的 url 请求,协议为 https
user:例如 /admins/user/**=user 没有参数表示必须存在用户,当登入操作时不做检查

注:这些过滤器中 anon,authcBasic,auchc,user 是认证过滤器,perms,roles,ssl,rest,port 是授权过滤器。

小结

本文的例子不是一个完整实用的例子,旨在围绕 Shiro 各个知识点来阐述一下。接着我将会写关于 Shiro 更“接地气”的应用。

时间: 2024-09-22 08:34:02

Shiro 学习应用的相关文章

Shiro 学习应用(续)

在前面的文章中为大家介绍了 Shrio 的基础概念,可能比较笼统,没有深入到开发过程的一些问题.现在集中在本帖中归纳一下有关问题. FormAuthenticationFilter 表单过滤器 表单过滤器的问题,是本人在实现验证码组件时候遇到的,亦曾经一度让我"抓狂".虽然有便捷的方法实现验证码,例如在控制器中就可以判断验证码逻辑了,但是那有违 Shiro 结构体系的思想:本人也想籍此了解 Shiro 扩展实现方法,--如果都走捷径,那么学习的目的就达不到了.于是,本人就把遇到问题逐一

SpringBoot学习:整合shiro(身份认证和权限认证),使用EhCache缓存

项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 (一)在pom.xml中添加依赖:   [html] view plain copy   <properties>       <shiro.version>1.3.2</shiro.version>   </properties>   [html] view plain copy   <!--shiro start-->       

分布式框架简介SSM组合+ springmvc+mybatis+shiro+restful+bootstrap

摘要: 服务框架:Dubbo.zookeeper.Rest服务 缓存:Redis.ehcache 消息中间件:ActiveMQ 负载均衡:Nginx 分布式文件:FastDF... 开发工具 1.Eclipse IDE:采用Maven项目管理,模块化. 2.代码生成:通过界面方式简单配置,自动生成相应代码,目前包括三种生成方式(增删改查):单表.一对多.树结构.生成后的代码如果不需要注意美观程度,生成后即可用. 技术选型(只列了一部分技术) 1.后端 服务框架:Dubbo.zookeeper.R

安全框架shiro

1.1  简介 Apache Shiro是Java的一个安全框架,目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了.对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了.本教程只介绍基本的Shiro使用,不会过多分析源码等,重在使用. Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE

java HTML5 学习资料汇总

目前JAVA可以说是产业界和学术界最热门的语言,许多人都很急切想把JAVA学好. 但学习是需要步骤的,除非像电影中演的那样,能够把需要的专业技巧下载到脑海:主角只花了几秒下载资料,就马上具备飞行员的技巧,或是武侠小说中的运功传送内力的方式,否则花上一段时间苦学是少不了的.花时间,不打紧,就怕方法错误,事倍功半. java 学习文章推荐.java学习线路.java 知识图谱. HTML5 微数据 RDFa/微格式 使用 jQuery 的 Autocomplete 插件实现input输入提示功能 创

shiro 集成 spring struts mybatis 权限查看不同的页面

问题描述 shiro 集成 spring struts mybatis 权限查看不同的页面 我搭了个框架,然后也判断了跳转,,现在我想要就是,分前台和后台,,前台 user和admin 权限能看,后台 admin 才能进去数据库改如何设计?简单点的吧,我在学习shiro ,最好能给我个demo ,谢谢了

shiro @RequiresPermissions() 不区分大小写

问题描述 shiro @RequiresPermissions() 不区分大小写 最近在学习shiro,但是却遇到这样的问题 @RequiresPermissions(value = "securityuser:list") 与 @RequiresPermissions(value = "SECURITYUSER:LIST") 居然都可以认证通过,而当前用户仅具备[securityuser:list]这个权限 值.权限值不区分大写,这不合理啊.请各位大侠帮忙分析分析

Shiro与web的集成

与spring集成 全面学习shiro相关知识:http://jinnianshilongnian.iteye.com/blog/2018398点击打开链接

【Shiro】Shiro从小白到大神(一)-Shiro入门

本系列是我在学习Shiro的路上的笔记,第一篇是属于非常入门级别的. 首先是介绍了下shiro,然后进行了一个小例子进行实际的操作 本节操作不涉及数据库,只是文本字符操作认证 Shiro简介: 百度百科上的介绍: Apache Shiro(日语"堡垒(Castle)"的意思)是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用.移动应用到大型网络及企业应用. Shiro为解决下列问题(我喜欢称它们为应用安全的四要素)提供了保