首页 > 代码库 > Shiro权限控制框架入门1:Shiro的认证流程以及基本概念介绍

Shiro权限控制框架入门1:Shiro的认证流程以及基本概念介绍

前言:我在最开始学习Shiro这个框架时,在网上搜索到的一个介绍比较全面的教程是:《跟我学Shiro》系列教程。但是在我看了他写的前几篇文章后,我发现虽然他在这个系列教程中把shiro的一些特性介绍地非常全面详细,但是整个教程的叙述方式还是有很大缺陷的。文章与文章之间并没有很好地串联起来,每篇文章介绍的东西都过于分散了,如果是对shiro完全不了解的新手来看的话完全是一场噩梦。就像一个网友评价的这样:

看了看这个教程,看完之后都想放弃shiro了,完全看不懂,后来百度了很多别的资料才理解了shiro的真正内涵,才发现这是一套好用的框架。
这个教程每一点都没讲细,初学者看完了就是一大堆问题,而且得不到解答,我觉着想尝试使用shiro的用户,看过这个教程之后多半会放弃shiro了


因此,鉴于这个原因,在我对shiro稍微了解一点之后,不敢独享shiro的学习经验,希望能够以一种更加浅显的语言来介绍shiro的入门以及如何在基于Spring的web项目中的集成。也就是说关于Shiro框架的入门我会用两篇文章来介绍,分别是:

  1. Shiro权限控制框架入门1:Shiro的认证流程以及基本概念介绍

  2. Shiro权限控制框架入门2:如何将Shiro非入侵地整合到SpringMVC等Web项目中

第一篇文章也就是本篇文章,通过配置文件配置的形式介绍shiro的认证和授权的一些基本流程,以及后面需要用到的一些基本概念;第二篇文章介绍如何将shiro非入侵地整合到SpringMVC等Web项目中。关于“非入侵”也就是:不破坏、不改写项目原有的登录和注销流程,不将shiro的登录流程硬编码到原有的登录流程中

一 简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序

Shiro的三个核心组件:Subject,、SecurityManager 以及 Realms

  • Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作

  • SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务

  • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

注:上面的介绍参考至百度百科

二 Shiro的认证流程以及基本概念介绍

(1)第一个入门实例:

	/**
	 * 第一个实例
	 * 参考:http://jinnianshilongnian.iteye.com/blog/2019547
	 * */
	@Test
	public void testHello(){
		//1 获取SecurityManager工厂
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:cn/zifangsky/test/shiro/base/shiro.ini");
		
		//2  获取SecurityManager实例,并绑定给SecurityUtils
		SecurityManager securityManager = factory.getInstance();
		SecurityUtils.setSecurityManager(securityManager);
		
		//3 获取Subject,创建用户名/密码验证Token
		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken("admin", "admin");
		
		try {
			//4 登录,即:身份认证
			subject.login(token);
			System.out.println("登录认证成功");
		} catch (AuthenticationException e) {
			//5 认证失败
			System.err.println("认证失败");
		}
		
		//6 退出登录
		subject.logout();
	}

注:这里为了方便使用了JUnit单元测试。也就是说需要在此次测试的项目中引入 junit-4.10.jar 这个jar包,同时需要在你的类上添加@RunWith(JUnit4.class)这个注解,比如说这样:

@RunWith(JUnit4.class)
public class Helloworld {

同时,上面用到的shiro.ini这个配置文件是这样的:

[users]  
admin=admin
test=123456

可以看出,这个配置文件中的代码很简单,就是定义了两个用户名以及它们的明文密码

关于shiro的大致认证流程就像上面的测试代码那样了,代码中有详细的注释,因此我就不多做解释了

关于这个例子,选中这个测试方法的方法名使用:Run As –> JUnit Test 运行,最后的输出结果如下:

登录认证成功

(2)自定义Realm:

关于Realm这个类,当需要对用户执行认证(登录)和授权(访问控制)验证时,Shiro都会从应用配置的Realm中查找用户信息以及权限信息。因此,我们可以自定义一个Realm以实现自定义登录认证和权限控制,下面我将用一个简单例子介绍通过自定义Realm实现自定义认证(PS:关于自定义授权相关内容放到下一篇文章中叙述):

i)自定义的CustomRealm.java:

package cn.zifangsky.test.shiro.base;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.Realm;

public class CustomRealm implements Realm {
	/**
	 * 返回一个唯一的Realm名称
	 * */
	@Override
	public String getName() {
		return "CustomRealm";
	}

	/**
	 * 判断此Realm是否支持此Token
	 * */
	@Override
	public boolean supports(AuthenticationToken token) {
		//仅支持UsernamePasswordToken类型的Token
		return token instanceof UsernamePasswordToken;
	}
	
	/**
	 * 自定义认证
	 * */
	@Override
	public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//根据token获取需要认证的信息(登录时输入的信息)
		String username = (String) token.getPrincipal();  //获取用户名
		String password = String.valueOf((char[])token.getCredentials());  //获取密码
		
		if(!"test".equals(username))
			throw new UnknownAccountException();
		if(!"123456".equals(password))
			throw new IncorrectCredentialsException();
		
		return new SimpleAuthenticationInfo(username, password, getName());
	}
}

ii)对应的配置文件shiro-realm.ini:

#申明一个自定义的Realm
customRealm=cn.zifangsky.test.shiro.base.CustomRealm
#指定securityManager的realms实现
securityManager.realms=$customRealm

iii)测试方法:

	/**
	 * 测试自定义Realm
	 * */
	@Test
	public void testCustomRealm(){
		//1 获取SecurityManager工厂
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:cn/zifangsky/test/shiro/base/shiro-realm.ini");
		
		//2  获取SecurityManager实例,并绑定给SecurityUtils
		SecurityManager securityManager = factory.getInstance();
		SecurityUtils.setSecurityManager(securityManager);
		
		//3 获取Subject,创建用户名/密码验证Token
		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken("test", "123456");
		try {
			//4 登录,即:身份认证
			subject.login(token);
			System.out.println("登录认证成功");
		} catch (AuthenticationException e) {
			//5 认证失败
			System.err.println("认证失败");
		}
		
		//6 退出登录
		subject.logout();
	}

关于这个测试方法,除了使用了一个不同的ini配置文件之外,其余部分都跟上面那个例子是一样的,因此这里就不多说了

(3)使用数据库的shiro登录认证:

在上面的两个例子中都没有使用到数据库,同时登录认证的账号密码都硬编码地配置到ini配置文件中了。但是实际的开发中是不可能将用户信息这样硬编码的,因此接下来我将介绍基于数据库的shiro登录认证:

i)测试使用的SQL语句:

我在这里测试时使用的数据库是MySQL,测试使用的SQL语句是:

-- ----------------------------
-- Table structure for roles_permissions
-- ----------------------------
DROP TABLE IF EXISTS `roles_permissions`;
CREATE TABLE `roles_permissions` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(100) DEFAULT NULL,
  `permission` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_roles_permissions` (`role_name`,`permission`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of roles_permissions
-- ----------------------------
INSERT INTO `roles_permissions` VALUES (‘1‘, ‘admin‘, ‘/‘);

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  `password_salt` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_users_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (‘1‘, ‘admin‘, ‘123456‘, null);

-- ----------------------------
-- Table structure for user_roles
-- ----------------------------
DROP TABLE IF EXISTS `user_roles`;
CREATE TABLE `user_roles` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL,
  `role_name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_roles` (`username`,`role_name`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user_roles
-- ----------------------------
INSERT INTO `user_roles` VALUES (‘1‘, ‘admin‘, ‘admin‘);

注:在基于ini配置文件的配置时,如果不手动改写shiro的查询语句,那么shiro在进行认证和授权时默认会查询上面的这几个表

ii)配置文件shiro-jdbc-realm.ini:

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
dataSource.password=root
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm

这里的配置也很简单,先是定义了一个JDBC的Realm,接着定义了一个数据源以及它的一些基本配置。需要注意的是我这里使用的是druid连接池,如果要使用这个连接池的话需要引入druid-1.0.26.jar这个jar包。当然,这里也可以使用其他的一些连接池或者基本的jdbc数据源

iii)测试方法:

	/**
	 * 测试JDBC Realm
	 * */
	@Test
	public void testJdbcRealm(){
		//1 获取SecurityManager工厂
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:cn/zifangsky/test/shiro/base/shiro-jdbc-realm.ini");
		
		//2  获取SecurityManager实例,并绑定给SecurityUtils
		SecurityManager securityManager = factory.getInstance();
		SecurityUtils.setSecurityManager(securityManager);
		
		//3 获取Subject,创建用户名/密码验证Token
		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
		try {
			//4 登录,即:身份认证
			subject.login(token);
			System.out.println("登录认证成功");
		} catch (AuthenticationException e) {
			//5 认证失败
			System.err.println("认证失败");
		}
		
		//6 退出登录
		subject.logout();
	}

很显然,运行这个方法也是可以登录成功的,输出结果略

(4)用户拥有的角色、权限判断:

package cn.zifangsky.test.shiro.func;

import java.util.Arrays;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class RoleTest {
	/**
	 * 粒度大,如果某种角色不存在了则需要删除所有的用户对应的角色信息
	 * */
	@Test
	public void testHasRole() {
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:cn/zifangsky/test/shiro/func/shiro-role.ini");

		SecurityManager securityManager = factory.getInstance();
		SecurityUtils.setSecurityManager(securityManager);

		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken("admin", "admin");

		subject.login(token);

		System.out.println(subject.hasRole("role1"));
		System.out.println(subject.hasAllRoles(Arrays.asList("role1", "role2")));
		boolean[] results = subject.hasRoles(Arrays.asList("role1", "role2", "role3"));
		System.out.println("results[0]: " + results[0]);
		System.out.println("results[1]: " + results[1]);
		System.out.println("results[2]: " + results[2]);
		
//		subject.checkRole("role3");
	}
	
	/**
	 * 粒度小,如果某种角色不存在了只需要删除该角色即可
	 * */
	@Test
	public void testIsPermitted(){
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:cn/zifangsky/test/shiro/func/shiro-permission.ini");

		SecurityManager securityManager = factory.getInstance();
		SecurityUtils.setSecurityManager(securityManager);

		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken("admin", "admin");

		subject.login(token);
		
		System.out.println(subject.isPermitted("user:create"));
		System.out.println(subject.isPermittedAll("user:create","user:delete"));
	}

}

这里测试所用的两个ini配置文件分别是:

i)shiro-role.ini:

[users]  
admin=admin,role1,role2
test=123456,role1

其格式是:

用户名=密码,角色1,角色2 …

ii)shiro-permission.ini:

[users]  
admin=admin,role1,role2
test=123456,role1
[roles]
role1=user:create
role2=user:create,user:update
role3=user:create,update,delete
role4=user:*
role5=*:view

这里的格式跟上面的差不多,不同的是星号表示任意,例如:role4=user:*  表示role4这个角色拥有user的所有权限

上面的例子最后输出如下:

方法一:

true
true
results[0]: true
results[1]: true
results[2]: false

方法二:

true
false

三 基于角色的访问控制(Role-Based Access Control)

基于角色的访问控制简称:RBAC。在本篇文章的末尾补充介绍一点目前最常用也是最基本的权限控制模型,也就是基于角色的访问控制。

基于RBAC的权限管理,其实体关系大致是这样的:

技术分享

因为用户与角色之间的关系是多对多的,角色与权限之间的关系也是多对多的。因此分别建立了“用户角色关联表”、“角色权限关联表”分别存放用户与角色之间、角色与权限之间的对应关系。测试的数据库表如下:

DROP TABLE IF EXISTS `usr_func`;
CREATE TABLE `usr_func` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `description` varchar(100) DEFAULT NULL,
  `code` varchar(100) DEFAULT NULL,
  `url` varchar(200) DEFAULT NULL,
  `status` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of usr_func
-- ----------------------------
INSERT INTO `usr_func` VALUES (‘1‘, ‘用户管理-查询‘, null, ‘YHGL:CX‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘2‘, ‘用户管理-新增‘, null, ‘YHGL:XZ‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘3‘, ‘用户管理-编辑‘, null, ‘YHGL:BJ‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘4‘, ‘用户管理-停用‘, null, ‘YHGL:TY‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘5‘, ‘用户管理-启用‘, null, ‘YHGL:QY‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘6‘, ‘用户管理-删除‘, null, ‘YHGL:SC‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘7‘, ‘文章管理-查询‘, null, ‘WZGL:CX‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘8‘, ‘文章管理-新增‘, null, ‘WZGL:XZ‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘9‘, ‘文章管理-编辑‘, null, ‘WZGL:BJ‘, null, ‘enable‘);
INSERT INTO `usr_func` VALUES (‘10‘, ‘文章管理-删除‘, null, ‘WZGL:SC‘, null, ‘enable‘);

-- ----------------------------
-- Table structure for usr_role
-- ----------------------------
DROP TABLE IF EXISTS `usr_role`;
CREATE TABLE `usr_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `roleName` varchar(100) DEFAULT NULL,
  `description` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of usr_role
-- ----------------------------
INSERT INTO `usr_role` VALUES (‘1‘, ‘manager‘, ‘管理员‘);
INSERT INTO `usr_role` VALUES (‘2‘, ‘editor‘, ‘编辑‘);
INSERT INTO `usr_role` VALUES (‘3‘, ‘author‘, ‘作者‘);
INSERT INTO `usr_role` VALUES (‘4‘, ‘subscriber‘, ‘订阅者‘);
INSERT INTO `usr_role` VALUES (‘5‘, ‘contributor‘, ‘投稿者‘);

-- ----------------------------
-- Table structure for usr_role_func
-- ----------------------------
DROP TABLE IF EXISTS `usr_role_func`;
CREATE TABLE `usr_role_func` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `roleId` int(11) DEFAULT NULL,
  `funcId` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `roleId` (`roleId`),
  CONSTRAINT `roleId` FOREIGN KEY (`roleId`) REFERENCES `usr_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of usr_role_func
-- ----------------------------
INSERT INTO `usr_role_func` VALUES (‘1‘, ‘1‘, ‘1‘);
INSERT INTO `usr_role_func` VALUES (‘2‘, ‘1‘, ‘2‘);
INSERT INTO `usr_role_func` VALUES (‘3‘, ‘1‘, ‘3‘);
INSERT INTO `usr_role_func` VALUES (‘4‘, ‘1‘, ‘4‘);
INSERT INTO `usr_role_func` VALUES (‘5‘, ‘1‘, ‘5‘);
INSERT INTO `usr_role_func` VALUES (‘6‘, ‘1‘, ‘6‘);
INSERT INTO `usr_role_func` VALUES (‘7‘, ‘1‘, ‘7‘);
INSERT INTO `usr_role_func` VALUES (‘8‘, ‘1‘, ‘8‘);
INSERT INTO `usr_role_func` VALUES (‘9‘, ‘1‘, ‘9‘);
INSERT INTO `usr_role_func` VALUES (‘10‘, ‘1‘, ‘10‘);
INSERT INTO `usr_role_func` VALUES (‘11‘, ‘2‘, ‘7‘);
INSERT INTO `usr_role_func` VALUES (‘12‘, ‘2‘, ‘8‘);
INSERT INTO `usr_role_func` VALUES (‘13‘, ‘2‘, ‘9‘);
INSERT INTO `usr_role_func` VALUES (‘14‘, ‘2‘, ‘10‘);
INSERT INTO `usr_role_func` VALUES (‘15‘, ‘3‘, ‘7‘);
INSERT INTO `usr_role_func` VALUES (‘16‘, ‘3‘, ‘8‘);
INSERT INTO `usr_role_func` VALUES (‘17‘, ‘3‘, ‘9‘);
INSERT INTO `usr_role_func` VALUES (‘18‘, ‘4‘, ‘7‘);
INSERT INTO `usr_role_func` VALUES (‘19‘, ‘5‘, ‘8‘);

-- ----------------------------
-- Table structure for usr_user
-- ----------------------------
DROP TABLE IF EXISTS `usr_user`;
CREATE TABLE `usr_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL,
  `password` varchar(256) DEFAULT NULL,
  `mobile` varchar(30) DEFAULT NULL,
  `email` varchar(100) DEFAULT NULL,
  `createTime` datetime DEFAULT NULL,
  `updateTime` datetime DEFAULT NULL,
  `channelId` int(11) DEFAULT NULL,
  `status` varchar(20) DEFAULT ‘1‘,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of usr_user
-- ----------------------------
INSERT INTO `usr_user` VALUES (‘1‘, ‘admin‘, ‘8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918‘, ‘110‘, ‘admin@zifangsky.cn‘, ‘2016-10-04 10:33:23‘, ‘2016-10-06 10:38:40‘, ‘1‘, ‘enable‘);
INSERT INTO `usr_user` VALUES (‘2‘, ‘test‘, ‘8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92‘, ‘3456789‘, ‘test@110.com‘, ‘2016-10-18 18:25:12‘, ‘2016-10-19 18:25:17‘, ‘2‘, ‘enable‘);
INSERT INTO `usr_user` VALUES (‘5‘, ‘zifangsky‘, ‘8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92‘, ‘911‘, ‘admin@zifangsky.cn‘, ‘2016-10-20 11:46:45‘, ‘2016-10-20 11:46:57‘, ‘1‘, ‘enable‘);
INSERT INTO `usr_user` VALUES (‘6‘, ‘sub‘, ‘8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92‘, null, null, null, null, null, ‘disable‘);
INSERT INTO `usr_user` VALUES (‘7‘, ‘contributor‘, ‘8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92‘, null, null, null, null, null, ‘disable‘);

-- ----------------------------
-- Table structure for usr_user_role
-- ----------------------------
DROP TABLE IF EXISTS `usr_user_role`;
CREATE TABLE `usr_user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) DEFAULT NULL,
  `roleId` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `userId` (`userId`),
  CONSTRAINT `userId` FOREIGN KEY (`userId`) REFERENCES `usr_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of usr_user_role
-- ----------------------------
INSERT INTO `usr_user_role` VALUES (‘1‘, ‘1‘, ‘1‘);
INSERT INTO `usr_user_role` VALUES (‘2‘, ‘5‘, ‘3‘);
INSERT INTO `usr_user_role` VALUES (‘3‘, ‘5‘, ‘5‘);
INSERT INTO `usr_user_role` VALUES (‘4‘, ‘2‘, ‘4‘);
INSERT INTO `usr_user_role` VALUES (‘5‘, ‘6‘, ‘2‘);

最后的测试代码如下:

package cn.zifangsky.test.shiro.func;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import junit.framework.Assert;

@RunWith(JUnit4.class)
public class RoleFuncTest {
	
	@Test
	public void test(){
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:cn/zifangsky/test/shiro/func/shiro-jdbc-func.ini");
		SecurityManager securityManager = factory.getInstance();
		SecurityUtils.setSecurityManager(securityManager);
		
		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken("admin", "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918");
		try {
			subject.login(token);
			Assert.assertEquals(true, subject.isAuthenticated());
			
			//判断用户是否拥有某个角色
			System.out.println(subject.hasRole("manager"));  //true
			System.out.println(subject.hasRole("editor"));  //false
			
			//判断是否被授权
			System.out.println(subject.isPermitted("YHGL:CX"));  //true
			System.out.println(subject.isPermitted("YHGL:XZ"));  //true
			
			subject.logout();
		} catch (IncorrectCredentialsException e) {
			System.out.println("登录密码错误. Password for account " + token.getPrincipal() + " was incorrect.");  
        } catch (ExcessiveAttemptsException e) {  
            System.out.println("登录失败次数过多");  
        } catch (LockedAccountException e) {  
            System.out.println("帐号已被锁定. The account for username " + token.getPrincipal() + " was locked.");  
        } catch (DisabledAccountException e) {  
            System.out.println("帐号已被禁用. The account for username " + token.getPrincipal() + " was disabled.");  
        } catch (ExpiredCredentialsException e) {  
            System.out.println("帐号已过期. the account for username " + token.getPrincipal() + "  was expired.");  
        } catch (UnknownAccountException e) {  
            System.out.println("帐号不存在. There is no user with username of " + token.getPrincipal());  
        } 
		
	}
}

对应的配置文件shiro-jdbc-func.ini:

[main]  
dataSource=org.springframework.jdbc.datasource.DriverManagerDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://127.0.0.1:3306/rbac_db
dataSource.username=root
dataSource.password=root

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.permissionsLookupEnabled = true  
jdbcRealm.dataSource=$dataSource
#用户认证(登录)查询语句,以用户名为查询条件
jdbcRealm.authenticationQuery = SELECT password FROM usr_user WHERE username = ?
#用户角色查询语句,以用户名为查询条件,判断用户是否拥有某个角色
jdbcRealm.userRolesQuery = SELECT usr_role.roleName from usr_user,usr_user_role,usr_role WHERE usr_user.username = ? AND usr_user.id = usr_user_role.userId AND usr_user_role.roleId = usr_role.id
#资源许可查询语句,以角色名称为查询条件,判断角色是否拥有某个资源的许可
jdbcRealm.permissionsQuery = SELECT usr_func.code from usr_role,usr_role_func,usr_func WHERE usr_role.roleName = ? AND usr_role.id = usr_role_func.roleId AND usr_role_func.funcId = usr_func.id

securityManager.realms=$jdbcRealm

关于这个配置文件中定义的大部分内容都已经在上面的例子中说过了,唯一新添加的内容是:自定义了用户认证、角色查询以及权限查询的SQL语句

该测试用例最后的输出结果如下:

true
false
true
true

关于shiro框架的一些基本用法介绍就到此结束了。我将在下一篇文章中介绍如何非入侵地将shiro权限管理集成到Spring等WEB开发框架中,敬请期待!

本文出自 “zifangsky的个人博客” 博客,请务必保留此出处http://983836259.blog.51cto.com/7311475/1889588

Shiro权限控制框架入门1:Shiro的认证流程以及基本概念介绍