原文:http://blog.csdn.net/boonya/article/details/8233303

可能大家早先会见过 J-security,这个是 Shiro 的前身。在 2009 年 3 月初之前,这个安全框架叫做 J-security,由于某些原因,更名为 Shiro(或者 Ki,意为 Fortress),是 Apache 的孵化项目,鉴于本文编写时 Shiro 的还没有正式发布的版本,本文使用的是 Jsecurity 的稳定版本 0.9,本文中 Shiro 等同于 Jsecurity。

本文代码的开发环境:

    Jsecurity 0.9

  • Grails 1.2.0
  • Grails Shiro Plugin 1.0.1
  • SpringSource Tool Suite 2.3
  • 易于理解的 Java Security API;
  • 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
  • 对角色的简单的签权(访问控制),支持细粒度的签权;
  • 支持一级缓存,以提升应用程序的性能;
  • 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
  • 异构客户端会话访问;
  • 非常简单的加密 API;
  • 不跟任何的框架或者容器捆绑,可以独立运行。

JAAS —面世的时间最早,但是鉴于其在使用上有很大的限制,很少有人真正的使用它。可以说它不是一个好的应用程序级别的安全框架;

下面就开始我们的 Shiro 之旅吧!


1. 整体架构

  • SecurityManager

    典型的 Facade,Shiro 通过它对外提供安全管理的各种服务。

  • Authenticator

    对“Who are you ?”进行核实。通常涉及用户名和密码。

    这个组件负责收集 principals 和 credentials,并将它们提交给应用系统。如果提交的 credentials
    跟应用系统中提供的 credentials 吻合,就能够继续访问,否则需要重新提交 principals 和
    credentials,或者直接终止访问。

  • Authorizer

    身份份验证通过后,由这个组件对登录人员进行访问控制的筛查,比如“who can do what”, 或者“who can do which
    actions”。Shiro 采用“基于 Realm”的方法,即用户(又称 Subject)、用户组、角色和 permission 的聚合体。

  • Session Manager

    这个组件保证了异构客户端的访问,配置简单。它是基于 POJO/J2SE 的,不跟任何的客户端或者协议绑定。

从 Shiro 的框架图,已经能够体会到这个工具的简单了。下面让我们来看看 Shiro 是如何工作的。先了解一下它的安全模型吧!见下图:


2. 安全模型

  • Subject 是安全领域术语,除了代表人,它还可以是应用。在单应用中,可将其视为 User 的同义词。
  • Principal 是 Subject 的标识,一般情况下是唯一标识,比如用户名。
  • Role 和 Permission 分别代表了不同粒度的权限,从上图中可以看出 Role 的粒度更大些,Permission 代表了系统的原子权限,比如数据的修改、删除权限。对于简单的权限应用,可以不需要 Permission。
  • Realm 是一个执行者,负责真正的认证和鉴权。
  • role 没有实质内容,只是代表一组 permission,目的是为了管理的方便,一般都是动态定义;
  • permission 一般都是预先定义好的,不允许动态改变,除非源代码改动,它才会变化,它是整个安全模块的基础;
  • 要使 permission 也能动态定义,并非不可能,但是这将使鉴权非常复杂,甚至可能导致鉴权语句遍布整个程序,得不偿失;
  • 当然有一个例外:如果知道 permission 动态定义的规则和鉴权规则,如 Grails 的 fileter 中“${controllerName}:${actionName}:${params.id}”也可实现 permission 的动态定义


3. 关键类

AuthenticationToken 和 AuthenticationInfo

RememberMe

Credentials 和 CredentialsMatcher


4. CredentialsMatcher 的作用

PAM= Pluggable Authentication Modules

AuthorizationInfo

PermissionResolver 和 Permission


5. PermissionResolver 和 Permission 的关系

内置的权限有 2 个:

    AllPermission,总是返回 true

  • WildcardPermission,权限字符串的表示方式。

如果要比较权限字符串,可以使用 permission1.implies(permission2),它分别比较对应位置的字符串,在如下情况中,结果会返回 true:

    permission1 中的子串有 * 或 permission1 子串 ==permission2 子串;

  • permission1 无子串,permission2 有;
  • permission1 有子串,permission2 无,permission1 的所有子串都是 *。
  • 权限的比较实际是字符串的比较,只不过是考虑到了字符串的分级
  • 字符串的分级划分完全由使用者自己决定,Shiro 的惯例是 3 级:资源 : 操作 : 实例。
  • 字符串的使用必须一致,分隔符之间不要有空格,避免无意间引入的不一致。如:定义使用“file : create, update :
    1”,而验证使用“file : update”,那么分解之后一个是“ update ”,一个是“ update”,因空格而引起不等。

Realm


6. JdbcRealm 的继承链

Session

SecurityManager

Filter


7. Filter 的作用

  • web.xml 中只需配置 JSecurityFilter。对于 Spring 应用,则使用 SpringJSecurityFilter;
  • 子 filter 作为主 filter 的配置参数值出现,特点是:顺序相关
      • 对于多个 URL,验证顺序是由上至下,类似 Exception 的匹配。因此,使用顺序应该是由细到粗。
      • 对于同一 URL,子 filter 的验证顺序是从左至右的 AND 操作。
  • 如果配置值中含有分隔符,如 Permission,就需要使用引号来转义。

Subject

Configuration


8. 配置关系

  • JSecurityFilter 创建 Configuration 实例,并将 ini 参数值传给 Configuation。在 Spring
    环境中,分别使用 SpringJSecurityFilter 和 SpringIniWebConfiguration。
  • Configuration 实际就是 SecurityManager 的 Factroy,对 SpringIniWebConfiguration
    而言,它需要知道 SecurityManager 的 BeanName,该值由 SpringJSecurityFilter
    的初始化参数“securityManagerBeanName”值决定。即 SpringJSecurityFilter,实际有两个初始化参数:
    • config,是 ini 配置文件内容
    • securityManagerBeanName,是 SecurityManager 的 BeanName

SecurityUtils

杂项

    AOP,提供 AOP 方面的支持,实现对某个类某个方法的拦截,从而使权限控制延伸至类的方法。

  • Cache,提供缓存支持
  • Codec,提供编码方面的支持
  • Crypto,提供加密支持
  • IO,从多个资源位置读写原始数据
  • JNDI,提供 jndi 支持
  • util,工具类支持
  • 标签类,用于 Web 页面

在开始案例的学习之前,先作好准备工作 -- 获得 Shiro 相关的 jar 包,获取途径有两种:

      直接到 J-security 的网站上


    ,本文用到的就是这个;

  1. 由于 Shiro 目前是 Apache 的孵化项目,还没有发布正式的版本,但是我们可以到 Subversion 上下载代码,之后使用 Maven 构建

    mkdir shiro

    cd shiro

    svn co https://svn.apache.org/repos/asf/incubator/shiro/trunk/

    mvn install

示例一:让
Shiro 为你的应用服务

  • 用户登录后方可进入系统;
  • 假定一个 message 的安全内容,用户可以创建 message 的内容,但是如果需要修改 / 删除 message 的内容就必须具有相应的权限;
  • Admin 具有所有的权限;
  • message 的权限跟角色关联。


9 程序执行的流程

好了,开始程序的编写啦!

创建安全领域类

清单
1. User/Role/Permission 的 Domain class

 class User { 
String username
String password
static hasMany= [roles: Role]
static belongsTo= Role
……
}
class Role {
String rolename
static hasMany= [users: User, permissions: Permission]
……
}
class Permission {
String permission static hasMany= [roles: Role]
static belongsTo= Role
……
}

这里使用了最简单的情形,即权限传递结构为:Permission -> Role -> User。通常情况下,Permission 也可以分配给单个 User。

创建一个安全实体

清单
2. message 的 Domain class

 class Message { 
String details
User user
static constraints = {
}
}

配置 web.xml

清单 3. 在 web.xml 加入 SecurityFilter 的内容:

 <filter> 
<filter-name>SecurityFilter</filter-name>
<filter-class>
org.jsecurity.spring.SpringJSecurityFilter
</filter-class>
<init-param>
<param-name>securityManagerBeanName</param-name>
<param-value>jsecSecurityManager</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SecurityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • 这个 Filter 应该在 Grails 的 web.xml 中所有缺省的 Filter 最后;
  • url-pattern 不要使用“/**”,因为这样会造成登录页的 css 和图片无法访问。解决办法,可以通过遵循“只能通过 Controller/Action 访问”这个规则,并使用 Grails 的 Filter 机制,可以保证所有安全 URL 不被非法访问。

创建 realm

清单 4. conf/spring/resources.groovy

 beans = { 
credentialMatcher(
org.jsecurity.authc.credential.Sha1CredentialsMatcher) {
storedCredentialsHexEncoded = true
} permissionResolver(
org.jsecurity.authz.permission.WildcardPermissionResolver) realm(org.jsecurity.realm.jdbc.JdbcRealm){
permissionResolver = ref("permissionResolver")
dataSource = ref("dataSource")
permissionsLookupEnabled= true
permissionsQuery= "select permission from
permission, role_permissions, role where
permission.id= permission_id and role_id= role.id and rolename= ?"
userRolesQuery= "select rolename from role, role_users, user
where role.id=role_id and user_id= user.id and username=?"
authenticationQuery= "select password from user where username=?"
} jsecSecurityManager(
org.jsecurity.web.DefaultWebSecurityManager) {
bean ->bean.destroyMethod = "destroy"
realms = [ ref("realm") ]
}
}

接分配给 User,即 Permission 和 User 之间是多对多关系,那么 permissionsQuery 应该使用 union,即“role 相关 permission union user 相关 permission”。对于 User 有多个 Role 的情况,JdbcRealm 会循环得出总的结果。

安全守护神:SecurityFilters

清单
5. SecurityFilters

 import org.jsecurity.SecurityUtils 

 class SecurityFilters { 
def filters = {
authc(controller:'*', action:'*', ) {
before = {
if(controllerName!='auth'){
def subject = SecurityUtils.subject
if (!subject.authenticated) {
redirect(
controller: 'auth',
action: 'login',
params: [
targetUri: request.forwardURI - request.contextPath
])
return false
}
}
}
} admin(controller: 'user|role|permission', action: '*'){
before = {
def subject= SecurityUtils.subject
if(!subject.hasRole('admin')){
redirect(controller: 'auth', action: 'unauthorized')
return false
}
}
} editmessage(controller: 'message', action: 'update|delete'){
before = {
def subject= SecurityUtils.subject
if(!subject.isPermitted(
"${controllerName}:${actionName}:${params.id}")){
redirect(controller: 'auth', action: 'unauthorized')
return false
}
}
}
}
}
  • authc 表示的是所有用户对应用系统的任何访问都要经过 auth 认证,对于没有认证的用户的访问会重定向到登录界面;
  • admin 表示的是属于 admin 角色的用户对 user/role/permission 具有所有权限,对于非 admin 角色的用户会对其提示没有 user/role/permission 的访问权限;
  • editmessage 表示当用户对 message 进行修改或者删除的时候对其进行鉴权,只有具有 message 的 update/delete 权限的用户才能对 message 进行修改。

认证的代码

清单 6. 认证代码

 def signIn = { 
// 创建 AuthenticationToken
def authToken = new UsernamePasswordToken(
params.username,
params.password)
if (params.rememberMe) {
authToken.rememberMe = true
}
try{
// 使用 SecurityManager 的 login 登录
this.jsecSecurityManager.login(authToken)
flash.message = message(code: "$params.username")
def targetUri = params.targetUri ?: "/"
log.info "Redirecting to '${targetUri}'."
redirect(uri: targetUri)
}
catch (AuthenticationException ex){
// 如果出现异常,显示出错信息
log.info "Authentication failure for user '${params.username}'."
flash.message = message(code: "login.failed")
def m = [ username: params.username ]
if (params.rememberMe) {
m['rememberMe'] = true
}
if (params.targetUri) {
m['targetUri'] = params.targetUri
}
redirect(action: 'login', params: m)
}
}
      • 如果对所有 message 具有修改权限,权限字符串可以为:message:delete,update:*;
      • 如果针对某一个 message 具有修改权限,权限字符串可以为:message:update,delete:messageid。

示例二:使用 Shiro Plugin 快速构建安全模块

使用步骤如下:

      安装 Shiro Plugin,在你的 grails 项目中运行:

grails install-plugin shiro

    , 会创建 grails-app/realms 目录,并提供如下新的 Grails 命令:

    • grails create-auth-controller,创建 AuthController 以及登录窗口,Controller 提供了登录、登出和权限验证失败处理等 Action。
    • grails create-db-realm,创建一个访问数据库的 Realm
    • grails create-ldap-realm,创建一个访问 ldap 的 Realm
    • grails create-wildcard-realm,创建一个访问数据库的 Realm,但使用的是 Shiro 的 WildcardPermission。
    • grails quick-start,它是 create-auth-controller 和 create-db-realm 的集合体,是 Shiro 的快速入门,在接下来的内容中将详细介绍它的使用。
  1. 下面进行 Shiro 快速入门。在 grails 项目的目录下执行:grails quick-start,这个命令会创 建如下内容:
    • 在 grails-app/realms 下创建 ShiroDbRealm,这个 realm 是访问控制的信息库,用来决定一个用户有权访问哪些内容;
    • 在 grails-app/domain 创建 ShiroRole.groovy 和 ShiroUser.groovy;
    • 在 grails-app/controllers 下创建 AuthController,这里提供了登录和退出的 action;
    • 在 grails-app/views 下创建 auth 文件夹以及 login.gsp;
    • 在 grails-app/conf/ 下创建 SecurityFilters.groovy,这里管理着对所有 controller 和 action 的访问控制。
  2. 启动程序,当访问 controller 的时候,页面就会重定向到登录页面。但是这个时候是无法登录,因为我们没有添加用户。
  3. 进入到 grails-app/conf/ 修改 BootStrap.groovy, 

    清单
    7. 修改后的 BootStrap.groovy

     def init = { servletContext -> 
    def user = new User(username: "user",
    passwordHash: new Sha1Hash("user").toHex())
    user.save()
    def role= new Role(name:"admin").addToUsers(user).save()
    }

    重新启动程序,就能使用 user/user 登陆了。

  4. 在 plugin 缺省创建的 SecurityFilters 中使用了对所有 URL 进行 before 拦截,但是我们根据实际情况进行修改,例如我们要示例一的 filter 内容,就可以做如下更改: 

    清单
    8. 更改后的 SecurityFilters

     auth(controller: "*", action: "*"){ 
    before={
    accessControl{true}
    }
    }
        management(controller: "user|role|permission", action: "*"){
    before={
    accessControl{role("admin")}
    }
    }
    message(controller: "message", action: "delete|update"){
    before={
    accessControl{   
            permission("message:${actionName}:${params.id}")
    }
    }
    }

    看到这里,读者可能已经注意到了,这里的代码比示例一中的 Filter 的代码简单的许多。对,Shiro 插件已经将示例一中的类似代码封装到 ShiroGrailsPlugin.groovy 中了,我们使用的时候只需要:

    调用 accessControl 方法,该方法的参数可以是 Map、Closure 或者 Map+Closure;

    使用 role( …… ),验证访问对象是否具有相应的角色;

    使用 permission( …… ),验证访问对象是否具有相应的 Permission。

  5. 授权部分内容参见上一示例,这里不做冗述。
  6. Shiro Plugin 中除了为我们提供了上述方便之外,还提供了一些常用的 taglib 来增强用户界面 , 这些 taglib 都在
    pluginPath/grails-app/taglib /ShiroTagLib.groovy 中定义,我们可以直接在 gsp 中使用它们。

    比如,只有具有修改权限的用户才能够看到一些修改类的按钮显示,可以这样写:

    清单
    9. Taglib 的使用

     <shiro:hasPermission permission="message:update,delete"> 
    <span class="button">
    <g:actionSubmit class="edit" value="Edit" />
    </span>
    <span class="button">
    <g:actionSubmit class="delete"
    onclick="return confirm('Are you sure?');"
    value="Delete" />
    </span>
    </shiro:hasPermission>

    如下是经常使用到的 Tag:

    • principal
      输出当前用户的标识
    • hasRole,判断当前用户是否属于给定的角色,参数:name
    • hasPermission, 判断当前用户是否具有指定的权限,参数:type,action 或者 permission
    • isLoggedIn
      判断当前用户是否已经登录
    • hasAnyRole,判断当前用户是否属于给定的某个角色,参数:in

如果您已经非常了解了 Shiro,可以采用示例一的方式自己写所有的代码,但是对于初学者,作者还建议使用 Shiro plugin,这个插件帮助我们生成了安全相关的基本内容,以及提供了方便的 Tag。

这里给大家举的示例只是抛砖引玉。Shiro 真正的精髓还需要在项目中慢慢的体会。本文是引领我们走向 Shiro 的第一步。在这里要感谢胡键对本文编写的大力支持。

下载本文中的示例代码

shiro安全框架的更多相关文章

  1. thymeleaf模板引擎shiro集成框架

    shiro权限框架.前端验证jsp设计.间tag它只能用于jsp系列模板引擎. 使用最近项目thymeleaf作为前端模板引擎,采用HTML档,未出台shiro的tag lib,假设你想利用这段时间s ...

  2. shiro权限框架(一)

    不知不觉接触shiro安全框架都快三个月了,这中间配合项目开发踩过无数的坑.现在回想总结下,也算是一种积累,一种分享.中间有不够完美的地方或者不好的地方,希望大家指出来能一起交流.在这里谢谢开涛老师的 ...

  3. Shiro安全框架【快速入门】就这一篇!

    Shiro 简介 照例又去官网扒了扒介绍: Apache Shiro™ is a powerful and easy-to-use Java security framework that perfo ...

  4. SpringBoot集成Shiro安全框架

    跟着我的步骤:先运行起来再说 Spring集成Shiro的GitHub:https://github.com/yueshutong/shiro-imooc 一:导包 <!-- Shiro安全框架 ...

  5. Shiro权限框架简介

    http://blog.csdn.net/xiaoxian8023/article/details/17892041   Shiro权限框架简介 2014-01-05 23:51 3111人阅读 评论 ...

  6. Springboot整合Shiro安全框架

    最近在学习Springboot,在这个过程中遇到了很多之前都没有技术知识,学习了一阵子,稍微总结一些. ---- Shiro框架 shiro框架,是一个相对比较简便的安全框架,它可以干净利落地处理身份 ...

  7. 在前后端分离的SpringBoot项目中集成Shiro权限框架

    参考[1].在前后端分离的SpringBoot项目中集成Shiro权限框架 参考[2]. Springboot + Vue + shiro 实现前后端分离.权限控制   以及跨域的问题也有涉及

  8. (转) shiro权限框架详解06-shiro与web项目整合(上)

    http://blog.csdn.net/facekbook/article/details/54947730 shiro和web项目整合,实现类似真实项目的应用 本文中使用的项目架构是springM ...

  9. Shiro安全框架入门使用方法

    详见:https://blog.csdn.net/qq_32651225/article/details/77199464 框架介绍Apache Shiro是一个强大且易用的Java安全框架,执行身份 ...

随机推荐

  1. 零基础学通C语言,福利来啦!!!!zfhl.ke.qq.com

  2. 03day2

    03day1 不说了,图论题因为没有把加边的过程放到循环里导致只有 10 分.(不要吐槽我啊...)   竞赛排名 排序 [问题描述] [输入] 文件的第一行为参赛总人数 N(1≤N≤1000),从第 ...

  3. LeetCode: Reverse Words in a String && Rotate Array

    Title: Given an input string, reverse the string word by word. For example,Given s = "the sky i ...

  4. liunx安装qq

    http://www.07net01.com/电脑玩物 http://www.07net01.com/2014/09/68186.html 安装qq 一开始,我在ubuntu14.04下安装的QQ版本 ...

  5. 【转】This version of the rendering library is more recent than your version of ADT plug-in. Please update ADT plug-in

    原文网址:http://1982106a.blog.163.com/blog/static/8436495620149239361692/ 预览layout.xml文件时提示: This versio ...

  6. Eclipse “Invalid Project Description” when creating new project from existing source

    1) File>Import>General>Existing Project into Workspace2) File>Import>Android>Exist ...

  7. Maximum Product Subarray JAVA实现

    题目描述: Find the contiguous subarray within an array (containing at least one number) which has the la ...

  8. IOS 屏幕截图 UIScrollview

    //截图UIView:截全图 -(UIImage*)captureView:(UIView *)theView{ CGRect rect = theView.frame; if ([theView i ...

  9. [Irving]WPF Invalid character in the given encoding. Line xx, position xx.' XML is not valid.

    WPF开发中发现Xaml界面中突然抽风似的提示错误 Invalid character in the given encoding. Line xx, position xx.' XML is not ...

  10. 从表中随机返回n条记录

    创建测试用表: CREATE OR REPLACE VIEW V AS SELECT 'a' AS c FROM dual UNION ALL SELECT 'b' AS c FROM dual UN ...