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‘, ‘[email protected]‘, ‘2016-10-04 10:33:23‘, ‘2016-10-06 10:38:40‘, ‘1‘, ‘enable‘);
INSERT INTO `usr_user` VALUES (‘2‘, ‘test‘, ‘8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92‘, ‘3456789‘, ‘[email protected]‘, ‘2016-10-18 18:25:12‘, ‘2016-10-19 18:25:17‘, ‘2‘, ‘enable‘);
INSERT INTO `usr_user` VALUES (‘5‘, ‘zifangsky‘, ‘8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92‘, ‘911‘, ‘[email protected]‘, ‘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开发框架中,敬请期待!

时间: 2024-10-25 09:24:39

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

了解权限控制框架shiro 之实际应用.

Apache Shiro 1.权限控制分为 a.粗粒度 URL 级别权限控制     b.细粒度方法级别权限控制 2.使用shiro进行权限控制主要有四种主要方式 : a. 在程序中 通过 Subject 编程方式进行权限控制 b. 配置 Filter 实现 URL 级别粗粒度权限控制 c. 配置代理,基于注解实现细粒度权限控制 d. 在页面中使用 shiro 自定义标签实现 页面显示权限控制 3.shiro实际应用之基本配置: a.用父工程引入shiro b.配置web.xml c.配置app

基于JSP的教学大纲与进度在线管理系统-java教学大纲与进度管理系统shiro权限控制

基于JSP的教学大纲与进度在线管理系统-java教学大纲与进度管理系统shiro权限控制 1.包含源程序,数据库脚本.2.课题设计仅供参考学习使用,可以在此基础上进行扩展完善.开发环境:Eclipse ,MySQL 5.1,JDK1.7,Tomcat 7涉及技术点:MVC模式.JavaWeb.JDBC.HTML.CSS.JQUERY.shiro.文件上传.购物车等. 系统没用任何框架,前台纯JSP实现,后台servlet映射,适合刚学习J2EE的新手,代码思路清晰,注解详细,数据库用的是mysq

类Shiro权限校验框架的设计和实现(2)--对复杂权限表达式的支持

前言: 我看了下shiro好像默认不支持复杂表达式的权限校验, 它需要开发者自己去做些功能扩展的工作. 针对这个问题, 同时也会为了弥补上一篇文章提到的支持复杂表示需求, 特地尝试写一下解决方法. 本文主要借助groovy脚本来实现复杂表达式的计算, 其思想是借鉴了Oval支持复杂表达式(groovy/javascript/ruby)的实现方式. 文章系列: 1. springmvc简单集成shiro  2. 类Shiro权限校验框架的设计和实现  3. 权限系统(RBAC)的数据模型设计 目标

足彩基础知识入门(4)赛事数据库与预测平台基础概念介绍(一)

在足球赛事数据库以及统计分析预测平台中,有很多概念,如果不搞懂,很难进行下一步的工作.所以为了配合团队人员的学习和任务进行,特意编写这篇文章.如果有其他问题和不懂的,请留言,将根据情况进行更新. 本文原文地址:足彩基础知识入门(4)赛事数据库与预测平台基础概念介绍(一) 1.指数1/2/3.... 我在 足彩基础知识入门(3)足彩赔率的本质 一文中介绍了赔率的概念,那么指数的概念和赔率以及结果是相关的.我们举个例子: 如上图的比赛,前面是竞彩非让球的赔率:1.74-3.25-4.15,也就是说

权限控制框架Shiro简单介绍及配置实例

Shiro是什么 Apache Shiro是一个非常易用的Java安全框架它能提供验证.授权.加密和Session控制.Shiro非常轻量级而且API也非常易于理解可以使用Shiro完成从APP到企业级应用的所有权限控制. 宏观视图 从宏观来看Shiro架构中有3个重要概念Subjct.SecurityManager和Realms. Subject Subject实际上是正在执行的用户的抽象"用户"这里可以指自然人第三方服务代理账户或者其他. Subject被绑定在SecurityMa

shiro权限控制(一):shiro介绍以及整合SSM框架

shiro安全框架是目前为止作为登录注册最常用的框架,因为它十分的强大简单,提供了认证.授权.加密和会话管理等功能 . shiro能做什么? 认证:验证用户的身份 授权:对用户执行访问控制:判断用户是否被允许做某事 会话管理:在任何环境下使用 Session API,即使没有 Web 或EJB 容器. 加密:以更简洁易用的方式使用加密功能,保护或隐藏数据防止被偷窥 Realms:聚集一个或多个用户安全数据的数据源 单点登录(SSO)功能. 为没有关联到登录的用户启用 "Remember Me&q

Spring boot后台搭建二为Shiro权限控制添加Redis缓存

在添加权限控制后,添加方法 查看 当用户访问”获取用户信息”.”新增用户”和”删除用户”的时,后台输出打印如下信息 , Druid数据源SQL监控 为了避免频繁访问数据库获取权限信息,在Shiro中加入缓存 缓存有基于Redis和Ehcache的,本文只介绍基于Redis的 1.Shiro集成Redis的引入依赖 <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis<

shiro权限控制配置

shiro配置流程 web.xml中配置shiro的filter spring中配置shiro的过滤器工厂,指定对不同地址权限控制 , 传入安全管理器 配置安全管理器,传入realm,realm中定义具体授权和认证的流程 配置自定义凭证匹配器,指定token和info的匹配方式. 权限访问的配置 定义所有的权限的String集合 在自定义realm中重写doGetAuthoriaztionInfo(),创建所有权限的String集合, 创建SimpleAuthorizationInfo的对象,调

bootstrap+springboot实现shiro权限控制的坑

最近在开发一个项目,需要写一个后管系统,Bootstrap是美国Twitter公司的设计师Mark Otto和Jacob Thornton合作基于HTML.CSS.JavaScript 开发的简洁.直观.强悍的前端开发框架,使得 Web 开发更加快捷.Bootstrap提供了优雅的HTML和CSS规范,它即是由动态CSS语言Less写成.使用方便. 在开发的过程中,遇到这样一个场景:针对超级管理员,我希望他拥有删除等高级别的操作,但是对于低级别的普通管理员我只是希望他拥有查看和编辑的权限.这就需