天天看点

SpringBoot整合Shiro框架1.1、 配置项目环境1.2、定义用户认证授权微服务2.3、定义Shiro整合服务1.4、使用Redis进行数据缓存1.5、Thymeleaf整合Shiro标签

文章目录

  • 1.1、 配置项目环境
  • 1.2、定义用户认证授权微服务
  • 2.3、定义Shiro整合服务
  • 1.4、使用Redis进行数据缓存
  • 1.5、Thymeleaf整合Shiro标签

1.1、 配置项目环境

Shiro是现在最为流行的权限认证开发框架,与它齐名的只有最初的SpringSecurity(这个开发框架非常不好用,但是千万不要以为SpringSecurity没有用处,它在SpringCloud阶段将发挥重大作用)。但是现在如果要想整合Shiro开发框架有一点很遗憾,SpringBoot没有直接的配置支持,它不像整合所谓的Kafka、Redis、DataSource,也就是说如果要想整合Shiro开发框架那么就必须自己来进行所有的配置。

在整个的Shiro之中最为重要的部分:认真以及授权处理(Realm),在Realm里面实际上在开发之中所需要调用的业务方法只有两类:根据用户编号取得用户的完整信息,在认真通过之后根据用户编号获得用户对应的所有的角色以及权限信息。既然已经到了微架构的阶段,那么不得不去面对一个问题,对于这种用户的业务操作是放在WEB端还是单独提出来做成一个Rest服务?很明显,应该作为一个服务进行抽象出来,也就是说在整体的调用处理之中,Realm需要进行Rest服务调用(RestTemplate存在可以让整个的调用更加容易)。

那么按照如上的设计方案,现在的整体的项目里面认为应该包含有如下的几个开发模块:

  • microboot-shiro-api:应该提供有服务的VO类、各种加密处理的工具类;
  • microboot-shiro-memebre-provider:进行用户认证与授权REST服务提供,要暴露两个接口:用户信息获得、角色与权限信息获得;
  • microboot-shiro-web:主要进行Shiro的认证与授权检查处理。
SpringBoot整合Shiro框架1.1、 配置项目环境1.2、定义用户认证授权微服务2.3、定义Shiro整合服务1.4、使用Redis进行数据缓存1.5、Thymeleaf整合Shiro标签

下面为开发做一些基础准备。

1、【microboot-shiro-member-provider】保存本次的数据库脚本:

-- 删除数据库
DROP DATABASE IF EXISTS mldn ;
-- 创建数据库
CREATE DATABASE mldn CHARACTER SET UTF8 ;
-- 使用数据库
USE mldn ;
CREATE TABLE member(
	mid			VARCHAR(50) ,
	name		VARCHAR(50) ,
	password	VARCHAR(32) ,
	locked		INT ,
	CONSTRAINT pk_mid PRIMARY KEY(mid)
) ;
CREATE TABLE role (
	rid			VARCHAR(50)  ,
	title		VARCHAR(50) ,
	CONSTRAINT pk_rid PRIMARY KEY(rid)
) ;
CREATE TABLE action (
	actid		VARCHAR(50)	,
	title		VARCHAR(50) ,
	rid			VARCHAR(50) ,
	CONSTRAINT pk_actid PRIMARY KEY(actid) 
) ;
CREATE TABLE member_role (
	mid			VARCHAR(50) ,
	rid			VARCHAR(50) 
) ;
INSERT INTO member(mid,name,password,locked) VALUES ('mldnjava','mldn','2E866BF58289E01583AD418F486A69DF',0) ;
INSERT INTO member(mid,name,password,locked) VALUES ('admin','admin','2E866BF58289E01583AD418F486A69DF',0) ;
INSERT INTO role(rid,title) VALUES ('emp','雇员管理') ;
INSERT INTO role(rid,title) VALUES ('dept','部门管理') ;
INSERT INTO action(actid,title,rid) VALUES ('emp:add','雇员入职','emp') ;
INSERT INTO action(actid,title,rid) VALUES ('emp:remove','雇员离职','emp') ;
INSERT INTO action(actid,title,rid) VALUES ('emp:list','雇员列表','emp') ;
INSERT INTO action(actid,title,rid) VALUES ('emp:edit','雇员编辑','emp') ;
INSERT INTO action(actid,title,rid) VALUES ('dept:list','部门列表','dept') ;
INSERT INTO action(actid,title,rid) VALUES ('dept:edit','部门编辑','dept') ;
INSERT INTO member_role(mid,rid) VALUES ('mldnjava','emp') ;
INSERT INTO member_role(mid,rid) VALUES ('admin','emp') ;
INSERT INTO member_role(mid,rid) VALUES ('admin','dept') ;
           

2、【microboot-shiro-api】建立一个Member程序类,保存认证返回信息:

  • Shiro进行认证处理的时候是根据一个用户的编号获得用户对应的完整信息,而后再进行用户是否存在的判端、密码是否正确的判断、用户是否被锁定的判断
package cn.mldn.microboot.vo;

import java.io.Serializable;

public class Member implements Serializable {

    private static final long serialVersionUID = 5879393512553198949L;

    private String mid;
    private String name;
    private String password;
    private String locked;

......此处省略set、get和toString方法......
           

3、【microboot-shiro-api】既然有密码的加密处理,将之前所编写的Base64+MD5的加密程序工具配置到项目之中:

  • Base64加密:
package cn.mldn.microboot.util.enctype;

import java.util.Base64;

public class PasswordUtil {
	private static final String SEED  = "mldnjava" ;	// 该数据为种子数,如果要加密则需要使用Base64做多次迭代
	private static final int NE_NUM = 3 ;	// 密码迭代处理3次
	private PasswordUtil() {}
	private static String createSeed() {	// 创建一个基于Base64的种子数
		String str = SEED ;
		for (int x = 0 ; x < NE_NUM ; x ++) {
			str = Base64.getEncoder().encodeToString(str.getBytes()) ;
		}
		return str ;
	}
	/**
	 * 进行密码的处理操作
	 * @param password 用户输入的真实密码
	 * @return 与数据库保存匹配的加密的处理密码
	 */
	public static String getPassword(String password) {
		MD5Code md5 = new MD5Code() ;
		String pass = "{" + password + ":" + createSeed() + "}";
		for (int x = 0 ; x < NE_NUM ; x ++) {
			pass = md5.getMD5ofStr(pass) ;
		}
		return pass ; 
	}
}
           
  • MD5加密:
package cn.mldn.microboot.util.enctype;

public class MD5Code {
	/*
	 * 下面这些S11-S44实际上是一个4*4的矩阵,在原始的C实现中是用#define 实现的, 这里把它们实现成为static
	 * final是表示了只读,切能在同一个进程空间内的多个 Instance间共享
	 */
	static final int S11 = 7;

	static final int S12 = 12;

	static final int S13 = 17;

	static final int S14 = 22;

	static final int S21 = 5;

	static final int S22 = 9;

	static final int S23 = 14;

	static final int S24 = 20;

	static final int S31 = 4;

	static final int S32 = 11;

	static final int S33 = 16;

	static final int S34 = 23;

	static final int S41 = 6;

	static final int S42 = 10;

	static final int S43 = 15;

	static final int S44 = 21;

	static final byte[] PADDING = { -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			0, 0, 0, 0, 0, 0, 0 };

	/*
	 * 下面的三个成员是MD5计算过程中用到的3个核心数据,在原始的C实现中 被定义到MD5_CTX结构中
	 */
	private long[] state = new long[4];// state (ABCD)

	private long[] count = new long[2];// number of bits, modulo 2^64 (lsb

	// first)

	private byte[] buffer = new byte[64]; // input buffer

	/*
	 * digestHexStr是MD5的唯一一个公共成员,是最新一次计算结果的 16进制ASCII表示.
	 */

	public String digestHexStr;

	/*
	 * digest,是最新一次计算结果的2进制内部表示,表示128bit的MD5值.
	 */
	private byte[] digest = new byte[16];

	/*
	 * getMD5ofStr是类MD5最主要的公共方法,入口参数是你想要进行MD5变换的字符串
	 * 返回的是变换完的结果,这个结果是从公共成员digestHexStr取得的.
	 */
	public String getMD5ofStr(String inbuf) {
		md5Init();
		md5Update(inbuf.getBytes(), inbuf.length());
		md5Final();
		digestHexStr = "";
		for (int i = 0; i < 16; i++) {
			digestHexStr += byteHEX(digest[i]);
		}
		return digestHexStr;
	}

	// 这是MD5这个类的标准构造函数,JavaBean要求有一个public的并且没有参数的构造函数
	public MD5Code() {
		md5Init();
		return;
	}

	/* md5Init是一个初始化函数,初始化核心变量,装入标准的幻数 */
	private void md5Init() {
		count[0] = 0L;
		count[1] = 0L;
		// /* Load magic initialization constants.
		state[0] = 0x67452301L;
		state[1] = 0xefcdab89L;
		state[2] = 0x98badcfeL;
		state[3] = 0x10325476L;
		return;
	}

	/*
	 * F, G, H ,I 是4个基本的MD5函数,在原始的MD5的C实现中,由于它们是
	 * 简单的位运算,可能出于效率的考虑把它们实现成了宏,在java中,我们把它们 实现成了private方法,名字保持了原来C中的。
	 */
	private long F(long x, long y, long z) {
		return (x & y) | ((~x) & z);
	}

	private long G(long x, long y, long z) {
		return (x & z) | (y & (~z));
	}

	private long H(long x, long y, long z) {
		return x ^ y ^ z;
	}

	private long I(long x, long y, long z) {
		return y ^ (x | (~z));
	}

	/*
	 * FF,GG,HH和II将调用F,G,H,I进行近一步变换 FF, GG, HH, and II transformations for
	 * rounds 1, 2, 3, and 4. Rotation is separate from addition to prevent
	 * recomputation.
	 */
	private long FF(long a, long b, long c, long d, long x, long s, long ac) {
		a += F(b, c, d) + x + ac;
		a = ((int) a << s) | ((int) a >>> (32 - s));
		a += b;
		return a;
	}

	private long GG(long a, long b, long c, long d, long x, long s, long ac) {
		a += G(b, c, d) + x + ac;
		a = ((int) a << s) | ((int) a >>> (32 - s));
		a += b;
		return a;
	}

	private long HH(long a, long b, long c, long d, long x, long s, long ac) {
		a += H(b, c, d) + x + ac;
		a = ((int) a << s) | ((int) a >>> (32 - s));
		a += b;
		return a;
	}

	private long II(long a, long b, long c, long d, long x, long s, long ac) {
		a += I(b, c, d) + x + ac;
		a = ((int) a << s) | ((int) a >>> (32 - s));
		a += b;
		return a;
	}

	/*
	 * md5Update是MD5的主计算过程,inbuf是要变换的字节串,inputlen是长度,这个
	 * 函数由getMD5ofStr调用,调用之前需要调用md5init,因此把它设计成private的
	 */
	private void md5Update(byte[] inbuf, int inputLen) {
		int i, index, partLen;
		byte[] block = new byte[64];
		index = (int) (count[0] >>> 3) & 0x3F;
		// /* Update number of bits */
		if ((count[0] += (inputLen << 3)) < (inputLen << 3))
			count[1]++;
		count[1] += (inputLen >>> 29);
		partLen = 64 - index;
		// Transform as many times as possible.
		if (inputLen >= partLen) {
			md5Memcpy(buffer, inbuf, index, 0, partLen);
			md5Transform(buffer);
			for (i = partLen; i + 63 < inputLen; i += 64) {
				md5Memcpy(block, inbuf, 0, i, 64);
				md5Transform(block);
			}
			index = 0;
		} else
			i = 0;
		// /* Buffer remaining input */
		md5Memcpy(buffer, inbuf, index, i, inputLen - i);
	}

	/*
	 * md5Final整理和填写输出结果
	 */
	private void md5Final() {
		byte[] bits = new byte[8];
		int index, padLen;
		// /* Save number of bits */
		Encode(bits, count, 8);
		// /* Pad out to 56 mod 64.
		index = (int) (count[0] >>> 3) & 0x3f;
		padLen = (index < 56) ? (56 - index) : (120 - index);
		md5Update(PADDING, padLen);
		// /* Append length (before padding) */
		md5Update(bits, 8);
		// /* Store state in digest */
		Encode(digest, state, 16);
	}

	/*
	 * md5Memcpy是一个内部使用的byte数组的块拷贝函数,从input的inpos开始把len长度的
	 * 字节拷贝到output的outpos位置开始
	 */
	private void md5Memcpy(byte[] output, byte[] input, int outpos, int inpos,
			int len) {
		int i;
		for (i = 0; i < len; i++)
			output[outpos + i] = input[inpos + i];
	}

	/*
	 * md5Transform是MD5核心变换程序,有md5Update调用,block是分块的原始字节
	 */
	private void md5Transform(byte block[]) {
		long a = state[0], b = state[1], c = state[2], d = state[3];
		long[] x = new long[16];
		Decode(x, block, 64);
		/* Round 1 */
		a = FF(a, b, c, d, x[0], S11, 0xd76aa478L); /* 1 */
		d = FF(d, a, b, c, x[1], S12, 0xe8c7b756L); /* 2 */
		c = FF(c, d, a, b, x[2], S13, 0x242070dbL); /* 3 */
		b = FF(b, c, d, a, x[3], S14, 0xc1bdceeeL); /* 4 */
		a = FF(a, b, c, d, x[4], S11, 0xf57c0fafL); /* 5 */
		d = FF(d, a, b, c, x[5], S12, 0x4787c62aL); /* 6 */
		c = FF(c, d, a, b, x[6], S13, 0xa8304613L); /* 7 */
		b = FF(b, c, d, a, x[7], S14, 0xfd469501L); /* 8 */
		a = FF(a, b, c, d, x[8], S11, 0x698098d8L); /* 9 */
		d = FF(d, a, b, c, x[9], S12, 0x8b44f7afL); /* 10 */
		c = FF(c, d, a, b, x[10], S13, 0xffff5bb1L); /* 11 */
		b = FF(b, c, d, a, x[11], S14, 0x895cd7beL); /* 12 */
		a = FF(a, b, c, d, x[12], S11, 0x6b901122L); /* 13 */
		d = FF(d, a, b, c, x[13], S12, 0xfd987193L); /* 14 */
		c = FF(c, d, a, b, x[14], S13, 0xa679438eL); /* 15 */
		b = FF(b, c, d, a, x[15], S14, 0x49b40821L); /* 16 */
		/* Round 2 */
		a = GG(a, b, c, d, x[1], S21, 0xf61e2562L); /* 17 */
		d = GG(d, a, b, c, x[6], S22, 0xc040b340L); /* 18 */
		c = GG(c, d, a, b, x[11], S23, 0x265e5a51L); /* 19 */
		b = GG(b, c, d, a, x[0], S24, 0xe9b6c7aaL); /* 20 */
		a = GG(a, b, c, d, x[5], S21, 0xd62f105dL); /* 21 */
		d = GG(d, a, b, c, x[10], S22, 0x2441453L); /* 22 */
		c = GG(c, d, a, b, x[15], S23, 0xd8a1e681L); /* 23 */
		b = GG(b, c, d, a, x[4], S24, 0xe7d3fbc8L); /* 24 */
		a = GG(a, b, c, d, x[9], S21, 0x21e1cde6L); /* 25 */
		d = GG(d, a, b, c, x[14], S22, 0xc33707d6L); /* 26 */
		c = GG(c, d, a, b, x[3], S23, 0xf4d50d87L); /* 27 */
		b = GG(b, c, d, a, x[8], S24, 0x455a14edL); /* 28 */
		a = GG(a, b, c, d, x[13], S21, 0xa9e3e905L); /* 29 */
		d = GG(d, a, b, c, x[2], S22, 0xfcefa3f8L); /* 30 */
		c = GG(c, d, a, b, x[7], S23, 0x676f02d9L); /* 31 */
		b = GG(b, c, d, a, x[12], S24, 0x8d2a4c8aL); /* 32 */
		/* Round 3 */
		a = HH(a, b, c, d, x[5], S31, 0xfffa3942L); /* 33 */
		d = HH(d, a, b, c, x[8], S32, 0x8771f681L); /* 34 */
		c = HH(c, d, a, b, x[11], S33, 0x6d9d6122L); /* 35 */
		b = HH(b, c, d, a, x[14], S34, 0xfde5380cL); /* 36 */
		a = HH(a, b, c, d, x[1], S31, 0xa4beea44L); /* 37 */
		d = HH(d, a, b, c, x[4], S32, 0x4bdecfa9L); /* 38 */
		c = HH(c, d, a, b, x[7], S33, 0xf6bb4b60L); /* 39 */
		b = HH(b, c, d, a, x[10], S34, 0xbebfbc70L); /* 40 */
		a = HH(a, b, c, d, x[13], S31, 0x289b7ec6L); /* 41 */
		d = HH(d, a, b, c, x[0], S32, 0xeaa127faL); /* 42 */
		c = HH(c, d, a, b, x[3], S33, 0xd4ef3085L); /* 43 */
		b = HH(b, c, d, a, x[6], S34, 0x4881d05L); /* 44 */
		a = HH(a, b, c, d, x[9], S31, 0xd9d4d039L); /* 45 */
		d = HH(d, a, b, c, x[12], S32, 0xe6db99e5L); /* 46 */
		c = HH(c, d, a, b, x[15], S33, 0x1fa27cf8L); /* 47 */
		b = HH(b, c, d, a, x[2], S34, 0xc4ac5665L); /* 48 */
		/* Round 4 */
		a = II(a, b, c, d, x[0], S41, 0xf4292244L); /* 49 */
		d = II(d, a, b, c, x[7], S42, 0x432aff97L); /* 50 */
		c = II(c, d, a, b, x[14], S43, 0xab9423a7L); /* 51 */
		b = II(b, c, d, a, x[5], S44, 0xfc93a039L); /* 52 */
		a = II(a, b, c, d, x[12], S41, 0x655b59c3L); /* 53 */
		d = II(d, a, b, c, x[3], S42, 0x8f0ccc92L); /* 54 */
		c = II(c, d, a, b, x[10], S43, 0xffeff47dL); /* 55 */
		b = II(b, c, d, a, x[1], S44, 0x85845dd1L); /* 56 */
		a = II(a, b, c, d, x[8], S41, 0x6fa87e4fL); /* 57 */
		d = II(d, a, b, c, x[15], S42, 0xfe2ce6e0L); /* 58 */
		c = II(c, d, a, b, x[6], S43, 0xa3014314L); /* 59 */
		b = II(b, c, d, a, x[13], S44, 0x4e0811a1L); /* 60 */
		a = II(a, b, c, d, x[4], S41, 0xf7537e82L); /* 61 */
		d = II(d, a, b, c, x[11], S42, 0xbd3af235L); /* 62 */
		c = II(c, d, a, b, x[2], S43, 0x2ad7d2bbL); /* 63 */
		b = II(b, c, d, a, x[9], S44, 0xeb86d391L); /* 64 */
		state[0] += a;
		state[1] += b;
		state[2] += c;
		state[3] += d;
	}

	/*
	 * Encode把long数组按顺序拆成byte数组,因为java的long类型是64bit的, 只拆低32bit,以适应原始C实现的用途
	 */
	private void Encode(byte[] output, long[] input, int len) {
		int i, j;
		for (i = 0, j = 0; j < len; i++, j += 4) {
			output[j] = (byte) (input[i] & 0xffL);
			output[j + 1] = (byte) ((input[i] >>> 8) & 0xffL);
			output[j + 2] = (byte) ((input[i] >>> 16) & 0xffL);
			output[j + 3] = (byte) ((input[i] >>> 24) & 0xffL);
		}
	}

	/*
	 * Decode把byte数组按顺序合成成long数组,因为java的long类型是64bit的,
	 * 只合成低32bit,高32bit清零,以适应原始C实现的用途
	 */
	private void Decode(long[] output, byte[] input, int len) {
		int i, j;
		for (i = 0, j = 0; j < len; i++, j += 4)
			output[i] = b2iu(input[j]) | (b2iu(input[j + 1]) << 8)
					| (b2iu(input[j + 2]) << 16) | (b2iu(input[j + 3]) << 24);
		return;
	}

	/*
	 * b2iu是我写的一个把byte按照不考虑正负号的原则的"升位"程序,因为java没有unsigned运算
	 */
	public static long b2iu(byte b) {
		return b < 0 ? b & 0x7F + 128 : b;
	}

	/*
	 * byteHEX(),用来把一个byte类型的数转换成十六进制的ASCII表示,
	 * 因为java中的byte的toString无法实现这一点,我们又没有C语言中的 sprintf(outbuf,"%02X",ib)
	 */
	public static String byteHEX(byte ib) {
		char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
				'B', 'C', 'D', 'E', 'F' };
		char[] ob = new char[2];
		ob[0] = Digit[(ib >>> 4) & 0X0F];
		ob[1] = Digit[ib & 0X0F];
		String s = new String(ob);
		return s;
	}
}
           

1.2、定义用户认证授权微服务

所谓的用户微服务指的是要求再【microboot- shiro-provider】 里面进行实现,该服务之中需要考虑如下几点:

  • 该服务需要进行数据库的开发,所以一定要进行数据库连接池的配置
  • 既然要进行微服务的编写,那么就一定需要提供有业务接口以及DAO实现子类,现在的实现将依靠MyBatis完成
  • 所以的微服务最终要通过 控制器的Rest进行发布处理

1、【microboot-shiro-member-provider】配置Druid数据库连接池

  • 需要修改pom.xml配置文件,为项目的整合添加相关的支持包:
<dependency>
     <groupId>cn.mldn</groupId>
     <artifactId>microboot-shiro-api</artifactId>
     <version>0.0.1-SNAPSHOT</version>
</dependency>
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      </dependency>
<dependency>
       <groupId>ch.qos.logback</groupId>
       <artifactId>logback-core</artifactId>
</dependency>
       <dependency>
       <groupId>org.mybatis.spring.boot</groupId>
       <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
           

2、【microboot-shiro-member-provider】建立几个DAO接口:

  • 提供用户认证的DAO接口:IMemberDao
package cn.mldn.microboot.microbootshiroprovider.dao;
import cn.mldn.microboot.vo.Member;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface IMemberDAO {
    
    public Member findById(String mid);
    
}
           
  • 提供角色检查的IRoleDao接口:
package cn.mldn.microboot.microbootshiroprovider.dao;

import org.apache.ibatis.annotations.Mapper;
import java.util.Set;

@Mapper
public interface IRoleDAO {

    public Set<String> findAllRoleByMember(String mid);
    
}
           
  • 提供所有权限检测的IActionDAO接口:
package cn.mldn.microboot.microbootshiroprovider.dao;

import java.util.Set;

public interface IActionDAO {

    public Set<String> findAllActionByMember(String mid);

}
           

3、【microboot-shiro-member-provider】将mybatis的配置文件拷贝到项目的【src/main/resources】中:

  • src/main/resources/mybatis/mybatis.cfg.xml文件配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration   
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"   
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>	<!-- 进行Mybatis的相应的环境的属性定义 -->
	<settings>	<!-- 在本项目之中开启二级缓存 -->
		<setting name="cacheEnabled" value="true"/>
	</settings>
</configuration>
           
  • 配置src/main/resources/mybatis/mapper/cn/mldn/Member.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mldn.microboot.dao.IMemberDAO">
    <select id="findById" parameterType="String" resultType="Member">
		SELECT mid,name,password,locked FROM member WHERE mid=#{mid} ;
	</select>
</mapper>
           
  • 配置src/main/resources/mybatis/mapper/cn/mldn/Role.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mldn.microboot.dao.IRoleDAO">
	<select id="findAllRoleByMember" parameterType="String" resultType="String">
		SELECT rid FROM role WHERE rid IN (
			SELECT rid FROM member_role WHERE mid=#{mid}) ;
	</select>
</mapper>
           
  • 配置src/main/resources/mybatis/mapper/cn/mldn/Role.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mldn.microboot.dao.IActionDAO"> 
	<select id="findAllActionByMember" parameterType="String" resultType="String">
		SELECT actid FROM action WHERE rid IN (
			SELECT rid FROM member_role WHERE mid=#{mid}) ;
	</select>
</mapper>  
           

4、【microboot-shiro-member-provider】修改application.yml配置文件:

server:
  port: 8001
mybatis:
  config-location: classpath:mybatis/batis.cfg.xml  # mybaits配置文件所在路径
  type-aliases-package: cn.mldn.microboot.vo        # 定义所有操作类的别名所在包
  mapper-locations:                                 # 所有的mapper映射文件
    - classpath:mybatis/mapper/**/*.xml
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/mldn
    data-username: root
    data-password: 123456
    dbcp2:
      min-idle: 5                                   # 进行数据库连接池的配置
      initial-size: 5                               # 数据库连接池的最新维持连接数
      max-total: 5                                  # 初始化提供的连接数
      max-wait-millis: 200                          # 等待连接获取的最大超时时间
           

5、【microboot-shiro-member-provider】定义IMemberService业务接口和实现类:

package cn.mldn.microboot.service;

import cn.mldn.microboot.vo.Member;

import java.util.Map;
import java.util.Set;

public interface IMemberSerivce {

    public Member get(String mid);

    public Map<String, Set<String>> listAuthByMember(String mid);
}
           
package cn.mldn.microboot.service.impl;

import cn.mldn.microboot.dao.IActionDAO;
import cn.mldn.microboot.dao.IMemberDAO;
import cn.mldn.microboot.dao.IRoleDAO;
import cn.mldn.microboot.service.IMemberSerivce;
import cn.mldn.microboot.vo.Member;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Service
public class MemberServiceImpl implements IMemberSerivce {

    @Resource
    private IMemberDAO memberDAO;
    @Resource
    private IRoleDAO roleDAO;
    @Resource
    private IActionDAO actionDAO;

    @Override
    public Member get(String mid) {
        return this.memberDAO.findById(mid);
    }

    @Override
    public Map<String, Set<String>> listAuthByMember(String mid) {
        Map<String, Set<String>> map = new HashMap<>();
        map.put("allRoles", this.roleDAO.findAllRoleByMember(mid));
        map.put("allAction", this.actionDAO.findAllActionByMember(mid));
        return map;
    }
}
           

6、【microboot-shiro-member-provider】编写业务层功能测试类:

package cn.mldn.microboot;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import cn.mldn.microboot.service.IMemberService;

@SpringBootTest(classes = StartSpringBootMain.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestMemberService {
	@Resource
	private IMemberService memberService ;
	@Test
	public void testGet() {
		System.out.println(this.memberService.get("admin"));
	}
	@Test
	public void testAuth() {
		System.out.println(this.memberService.listAuthByMember("admin"));
	}
}

           

7、【microboot-shiro-member-provider】进行控制层编写,控制层现在给出的一定是Rest服务:

package cn.mldn.microboot.controller;

import javax.annotation.Resource;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import cn.mldn.microboot.service.IMemberService;

@RestController
public class MemberController {
	@Resource
	private IMemberService memberService;
	@RequestMapping(value="/member/get")
	public Object get(String mid) {
		return this.memberService.get(mid) ;
	}
	@RequestMapping(value="/member/auth")
	public Object auth(String mid) {
		return this.memberService.listAuthByMember(mid) ;
	}
}
           
  • 认证服务端口:http://localhost:8001/member/get?mid=admin
  • 授权服务端口:http://localhost:8001/member/auth?mid=admin

8、【microboot-shiro-member-provider】编写控制层测试,如果要访问Rest服务肯定要使用RestTemplate完成,这个类现在为了简单起见,直接进行对象实例化处理:

修改MemberController中的:
......
@RequestMapping(value="/member/get",method=RequestMethod.POST)
......
@RequestMapping(value="/member/auth",method=RequestMethod.POST)
......
           
package cn.mldn.microboot;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;

import cn.mldn.vo.Member;

@SpringBootTest(classes = StartSpringBootMain.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestMemberController {
	private RestTemplate restTemplate = new RestTemplate() ;
	@Test
	public void testGet() {
		String url = "http://localhost:8001/member/get?mid=admin" ;
		Member vo = this.restTemplate.postForObject(url, null, Member.class) ;
		System.out.println(vo);
	}
	@SuppressWarnings("unchecked")
	@Test
	public void testAuth() { 
		String url = "http://localhost:8001/member/auth?mid=admin" ;
		Map<String,Object> map = this.restTemplate.postForObject(url, null, Map.class) ;
		Set<String> allRoles = new HashSet<String>() ;
		Set<String> allActions = new HashSet<String>() ;
		allRoles.addAll((List<String>) map.get("allRoles"));
		allActions.addAll((List<String>) map.get("allActions")) ;
		System.out.println("【角色】" + allRoles);
		System.out.println("【权限】" + allActions);
	}
}
           

那么此时一个专门进行用户认证以及权限检测的微服务开发完成。

2.3、定义Shiro整合服务

在本次项目之中WEB模块为【microboot-shiro-web】,很明显对于WEB模块之中必须要求调用用户认证与授权微服务(Realm),而后需要进行各种依赖包的配置(Shiro)、考虑到各种缓存的问题、认证与授权检测问题。

1、【microboot-shiro-web】修改pom.xml配置文件,追加Shiro的相关依赖程序包:

<dependency>
			<groupId>cn.mldn</groupId>
			<artifactId>microboot-shiro-api</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.4.0</version>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-ehcache</artifactId>
			<version>1.4.0</version>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-quartz</artifactId>
			<version>1.4.0</version>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<version>1.4.0</version>
		</dependency>
           

2、【microboot-shiro-web】建立一个RestTemplate的配置类对象:

package cn.mldn.microboot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RestTemplate {
    
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
           

3、【microboot-shiro-web】Shiro之中所有认证和授权的处理都在Realm之中定义了:

package cn.mldn.microboot.realm;

import cn.mldn.util.enctype.PasswordUtil;
import cn.mldn.vo.Member;
import groovy.util.IFileNameFinder;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MemberRealm extends AuthorizingRealm {

    @Resource
    private RestTemplate restTemplate;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("============= 1、进行认证操作处理 =============");
        String mid = token.getPrincipal().toString(); // 用户名
        // 取得用户名之后就需要通过业务层获取用户对象以确定该用户名是否可用
        String url = "http://localhost:8001/member/get?mid="+ mid ;
        Member member = this.restTemplate.postForObject(url, null, Member.class);
        // 表示该用户信息不存在,不存在则应该抛出一个异常
        if (member == null){
            throw new UnknownAccountException("搞什么搞,用户名不存在!");
        }
        // 用户名如果存在了,那么就需要缺点密码是否正确
        String password = PasswordUtil.getPassword(new String((char[]) token.getCredentials()));
        if (!password.equals(member.getPassword())){
            throw new IncorrectCredentialsException("密码都记不住,去死吧!");
        }
        // 随后还需要考虑用户被锁定的问题
        if (member.getLocked().equals(1)){ //1表示非0,非0就是true
            throw new LockedAccountException("被锁了,求解锁去吧!")
        }
        // 定义需要进行返回的操作数据信息项,返回的认证信息使用应该是密文
        SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(
                token.getPrincipal(), password, "memberRealm");
        // 在认证完成之后可以直接取得用户所需要的信息内容,保存在Session之中
        SecurityUtils.getSubject().getSession().setAttribute("name","我的名字");
        return auth;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("==========2、进行授权操作处理==============");
        //该操作的主要目的是取得授权信息,说的直白一点就是角色和权限数据
        SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
        //执行到此方法的时候一定是已经进行过用户认证处理了(用户名和密码一定是正确的)
        String mid = (String)principals.getPrimaryPrincipal(); //取得用户名
        String url = "http://localhost:8001/member/auth?mid=" + mid;
        Map<String, Object> map = this.restTemplate.postForObject(url, null, Map.class);
        Set<String> allRoles = new HashSet<>();
        Set<String> allActions = new HashSet<>();
        allRoles.addAll((List<String> map.get("allRoles")));
        allActions.addAll((List<String>) map.get("allActions"));
        auth.setRoles(allRoles); //保存所有的角色
        auth.setStringPermissions(allActions); //保存所有权限
        return auth;
    }
}
           

4、【microboot-shiro-web】现在虽然准备好了Realm程序类,但是在整个Shiro进行整合处理的时候实际上需要编写大量的配置程序类,所以这个时候如果直接使用xml配置文件虽然可以,但是不标准,最好的做法是你将所有的xml配置项变为Bean配置:

package cn.mldn.microboot.config;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import cn.mldn.microboot.realm.CustomerCredentialsMatcher;
import cn.mldn.microboot.realm.MemberRealm;

@Configuration
public class ShiroConfig {
	@Bean
	public MemberRealm getRealm() {// 1、获取配置的Realm,之所以没使用注解配置,是因为此处需要考虑到加密处理
		MemberRealm realm = new MemberRealm();
		realm.setCredentialsMatcher(new CustomerCredentialsMatcher());	
		return realm;
	}

	@Bean(name = "lifecycleBeanPostProcessor")
	public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	@Bean
	@DependsOn("lifecycleBeanPostProcessor")
	public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
		daap.setProxyTargetClass(true);
		return daap;
	}

	@Bean
	public EhCacheManager getCacheManager() {// 2、缓存配置
		EhCacheManager cacheManager = new EhCacheManager();
		cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
		return cacheManager;
	}

	@Bean
	public SessionIdGenerator getSessionIdGenerator() { // 3
		return new JavaUuidSessionIdGenerator();
	}

	@Bean
	public SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { // 4
		EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
		sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
		sessionDAO.setSessionIdGenerator(sessionIdGenerator);
		return sessionDAO;
	}

	@Bean
	public RememberMeManager getRememberManager() { // 5
		CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
		SimpleCookie cookie = new SimpleCookie("MLDNJAVA-RememberMe");
		cookie.setHttpOnly(true);
		cookie.setMaxAge(3600);
		rememberMeManager.setCookie(cookie);
		return rememberMeManager;
	}

	@Bean
	public QuartzSessionValidationScheduler getQuartzSessionValidationScheduler() {
		QuartzSessionValidationScheduler sessionValidationScheduler = new QuartzSessionValidationScheduler();
		sessionValidationScheduler.setSessionValidationInterval(100000);
		return sessionValidationScheduler;
	}

	@Bean
	public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(
			DefaultWebSecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
		aasa.setSecurityManager(securityManager);
		return aasa;
	}

	@Bean
	public DefaultWebSessionManager getSessionManager(SessionDAO sessionDAO,
			QuartzSessionValidationScheduler sessionValidationScheduler) { // 6
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		sessionManager.setGlobalSessionTimeout(1000000);
		sessionManager.setDeleteInvalidSessions(true);
		sessionManager.setSessionValidationScheduler(sessionValidationScheduler);
		sessionManager.setSessionValidationSchedulerEnabled(true);
		sessionManager.setSessionDAO(sessionDAO);
		SimpleCookie sessionIdCookie = new SimpleCookie("mldn-session-id");
		sessionIdCookie.setHttpOnly(true);
		sessionIdCookie.setMaxAge(-1);
		sessionManager.setSessionIdCookie(sessionIdCookie);
		sessionManager.setSessionIdCookieEnabled(true);
		return sessionManager;
	}

	@Bean
	public DefaultWebSecurityManager getSecurityManager(Realm memberRealm, EhCacheManager cacheManager,
			SessionManager sessionManager, RememberMeManager rememberMeManager) {// 7
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(memberRealm);
		securityManager.setCacheManager(cacheManager);
		securityManager.setSessionManager(sessionManager);
		securityManager.setRememberMeManager(rememberMeManager);
		return securityManager;
	}

	public FormAuthenticationFilter getLoginFilter() { // 在ShiroFilterFactoryBean中使用
		FormAuthenticationFilter filter = new FormAuthenticationFilter();
		filter.setUsernameParam("mid");
		filter.setPasswordParam("password");
		filter.setRememberMeParam("rememberMe");
		filter.setLoginUrl("/loginPage");	// 登录提交页面
		filter.setFailureKeyAttribute("error");
		return filter;
	}

	public LogoutFilter getLogoutFilter() { // 在ShiroFilterFactoryBean中使用
		LogoutFilter logoutFilter = new LogoutFilter();
		logoutFilter.setRedirectUrl("/");	// 首页路径,登录注销后回到的页面
		return logoutFilter;
	}

	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		// 必须设置 SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		shiroFilterFactoryBean.setLoginUrl("/loginPage");	// 设置登录页路径
		shiroFilterFactoryBean.setSuccessUrl("/pages/hello");	// 设置跳转成功页
		shiroFilterFactoryBean.setUnauthorizedUrl("/pages/unauthUrl");	// 授权错误页
		Map<String, Filter> filters = new HashMap<String, Filter>();
		filters.put("authc", this.getLoginFilter());
		filters.put("logout", this.getLogoutFilter());
		shiroFilterFactoryBean.setFilters(filters);
		Map<String, String> filterChainDefinitionMap = new HashMap<String, String>();
		filterChainDefinitionMap.put("/logout", "logout");
		filterChainDefinitionMap.put("/loginPage", "authc");	// 定义内置登录处理
		filterChainDefinitionMap.put("/pages/back/**", "authc");
		filterChainDefinitionMap.put("/*", "anon");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}
}

           

将ehcache.xml配置文件拷贝到scr/main/resources目录之中

5、【microboot-shiro-web】建立一个控制器

package cn.mldn.microboot.controller;

import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DeptController {

	@RequiresRoles("dept")
	@RequestMapping("/pages/back/dept/get")
	public String get(){
		return "部门信息";
	}
}

           

6、【microboot-shiro-web】登录出错之后应该跑到表单上,所以建立一个MemberController,这个程序类负责此跳转处理:

package cn.mldn.microboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MemberController {
    @RequestMapping("/loginPage")
    public String get(){
        return "member_login";
    }
}
           

7、【microboot-shiro-web】建立一个member_login.html页面:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	<title>SpringBoot模版渲染</title>
	<script type="text/javascript" th:src="@{/js/main.js}"></script> 
	<link rel="icon" type="image/x-icon" href="/images/mldn.ico"/>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
	<h1>用户登录表单、<span th:text="${error}"/></h1>
	<form th:action="@{/loginPage}" method="post">
		登录名:<input type="text" name="mid" value="mldnjava"/><br/>
		密&nbsp;码:<input type="text" name="password" value="hello"/><br/>
		<input type="submit" value="登录"/>
	</form>
</body> 
</html>
           

此时实现了一个最基础的整合处理操作。

1.4、使用Redis进行数据缓存

现在是使用了EHCache缓存组件进行了缓存处理,而实际的项目之中往往会利用Redis实现缓存配置,那么下面将对程序进行一些修改。

1、【microboot-shiro-web】如果要进行缓存的使用,则首先一定要配置缓存处理类

package cn.mldn.microboot.cache;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

public class RedisCache<K, V> implements Cache<K, V> {
	private Log log = LogFactory.getLog(RedisCache.class);
	private RedisTemplate<String, Object> redisTempate; // 要提供有Redis处理工具类
	public RedisCache(RedisTemplate<String, Object> redisTempate) {
		this.redisTempate = redisTempate;
	}
	@Override
	public V get(K key) throws CacheException {
		log.info("### get() : K = " + key);
		return (V) this.redisTempate.opsForValue().get(key.toString());
	}
	@Override
	public V put(K key, V value) throws CacheException {
		log.info("### put() : K = " + key + "、V = " + value);
		this.redisTempate.opsForValue().set(key.toString(), value);
		return value;
	}

	@Override
	public V remove(K key) throws CacheException {
		log.info("### remove() : K = " + key);
		V val = this.get(key);
		this.redisTempate.delete(key.toString());
		return val;
	}

	@Override
	public void clear() throws CacheException {
		log.info("### clear()");
		this.redisTempate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection)
					throws DataAccessException {
				connection.flushDb(); // 清空数据库
				return true;
			}
		});
	}

	@Override
	public int size() {
		log.info("### size()");
		return this.redisTempate.execute(new RedisCallback<Integer>() {
			@Override
			public Integer doInRedis(RedisConnection connection)
					throws DataAccessException {
				return connection.keys("*".getBytes()).size();
			}
		});
	}

	@Override
	public Set<K> keys() {
		log.info("### keys()");
		return this.redisTempate.execute(new RedisCallback<Set<K>>() {
			@Override
			public Set<K> doInRedis(RedisConnection connection)
					throws DataAccessException {
				Set<K> set = new HashSet<K>();
				Set<byte[]> keys = connection.keys("*".getBytes());
				Iterator<byte[]> iter = keys.iterator();
				while (iter.hasNext()) {
					set.add((K) iter.next());
				}
				return set;
			}
		});
	}

	@Override
	public Collection<V> values() {
		log.info("### values()");
		return this.redisTempate.execute(new RedisCallback<Set<V>>() {
			@Override
			public Set<V> doInRedis(RedisConnection connection)
					throws DataAccessException {
				Set<V> set = new HashSet<V>();
				Set<byte[]> keys = connection.keys("*".getBytes());
				Iterator<byte[]> iter = keys.iterator();
				while (iter.hasNext()) {
					set.add((V) connection.get(iter.next()));
				}
				return set;
			}
		});
	}
}
           

2、【microboot-shiro-web】进行Redis缓存管理类的配置:

package cn.mldn.microboot.cache;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.annotation.Resource;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisCacheManager implements CacheManager {
	// CacheManager负责所有数据的缓存,那么对于数据而言,应该保存在缓存里面
	private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
	@Resource
	private RedisTemplate<String, Object> redisTemplate;
	@Override
	public Cache<Object, Object> getCache(String name) throws CacheException {
		Cache<Object, Object> cache = this.caches.get(name); // 通过Map取得cache数据
		if (cache == null) { // 当前的集合里面没有Cache的数据
			cache = new RedisCache(this.redisTemplate); // 实例化一个新的Cache对象
			this.caches.put(name, cache);
		}
		return cache;
	}

}
           

3、【microboot-shiro-web】配置一个Shiro中的Session管理操作

package cn.mldn.microboot.session;

import java.io.Serializable;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
// 此时的类将实现SessionDAO的改写
import org.springframework.data.redis.core.RedisTemplate;
public class RedisSessionDAO extends EnterpriseCacheSessionDAO {
	private Log log = LogFactory.getLog(RedisSessionDAO.class);
	@Resource
	private RedisTemplate<String, Object> redisTempate; // 要提供有Redis处理工具类
	@Override
	protected Serializable doCreate(Session session) { // 创建Session,返回session id
		log.info("*** doCreate : " + session);
		Serializable sessionId = super.doCreate(session); // 创建sessionid
		// 将当前创建好的Session的数据保存在Redis数据库里面
		this.redisTempate.opsForValue().set(sessionId.toString(), session,
				1800);
		return sessionId;
	}
	@Override
	protected Session doReadSession(Serializable sessionId) { // 根据session
																// id读取session数据
		log.info("*** doReadSession : " + sessionId);
		Session session = super.doReadSession(sessionId); // 读取Session数据
		if (session == null) { // 现在没有读取到session数据,通过Redis读取
			return (Session) this.redisTempate.opsForValue()
					.get(sessionId.toString());
		}
		return null;
	}
	@Override
	protected void doUpdate(Session session) { // 实现Session更新,每次操作都要更新
		log.info("*** doUpdate : " + session);
		super.doUpdate(session);
		if (session != null) {
			this.redisTempate.opsForValue().set(session.getId().toString(),
					session, 1800);
		}
	}
	@Override
	protected void doDelete(Session session) { // session的删除处理
		log.info("*** doDelete : " + session);
		super.doDelete(session);
		this.redisTempate.delete(session.getId().toString());
	}
}
           

4、【microboot-shiro-web】在当前的项目开发过程之中,配置shiro的Bean里面所使用的还是EHCache缓存组件,所以需要进行更换处理

  • 更换现在要使用的SessionDAO实现子类:
@Bean
	public SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { // 4
		RedisSessionDAO sessionDAO = new RedisSessionDAO(); // 使用Redis进行Session管理
		sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
		sessionDAO.setSessionIdGenerator(sessionIdGenerator);
		return sessionDAO;
	}
           
  • 更换使用的缓存组件:
@Bean
	public DefaultWebSecurityManager getSecurityManager(Realm memberRealm, RedisCacheManager cacheManager,
			SessionManager sessionManager, RememberMeManager rememberMeManager) {// 7
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(memberRealm);
		securityManager.setCacheManager(cacheManager);
		securityManager.setSessionManager(sessionManager);
		securityManager.setRememberMeManager(rememberMeManager);
		return securityManager;
	}
           

5、【microboot-shiro-web】修改application.yml配置文件进行Redis配置:

spring:
  redis:
    host: 192.168.68.166
    port: 6379
    password: mldnjava
    timeout: 1000
    database: 0
    pool:
      max-active: 10
      max-idle: 8
      min-idle: 2
      max-wait: 100
server:
  port: 8080
           

同时修改pom.xml文件redis依赖支持包:

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
           

6、【microboot-shiro-web】建立一个RedisTemplate的配置程序类:

package cn.mldn.microboot.config;

import javax.annotation.Resource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import cn.mldn.microboot.util.RedisObjectSerializer;

@Configuration
public class RedisConfig {
	@Resource
	private JedisConnectionFactory jedisConnectionFactory ;
	@Bean("shiroRedis")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
		RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
		template.setConnectionFactory(this.jedisConnectionFactory);
		template.setKeySerializer(new StringRedisSerializer());
		template.setValueSerializer(new RedisObjectSerializer());
		return template; 
	}
}
           

7、【microboot-shiro-web】建立Redis序列号程序类:

package cn.mldn.microboot.util;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
public class RedisObjectSerializer implements RedisSerializer<Object> {
	private Converter<Object, byte[]> serializer = new SerializingConverter();
	private Converter<byte[], Object> deserializer = new DeserializingConverter();
	private static final byte[] EMPTY_ARRAY = new byte[0];
	@Override
	public byte[] serialize(Object object) throws SerializationException {
		if (object == null) {
			return EMPTY_ARRAY;
		}
		try {
			return serializer.convert(object);
		} catch (Exception ex) {
			return EMPTY_ARRAY;
		}
	}
	@Override
	public Object deserialize(byte[] bytes) throws SerializationException {
		if (this.isEmpty(bytes)) {
			return null;
		}
		try {
			return deserializer.convert(bytes);
		} catch (Exception ex) {
			throw new SerializationException("序列化对象出错!", ex);
		}
	}
	private boolean isEmpty(byte[] data) {
		return (data == null || data.length == 0);
	}
}
           

1.5、Thymeleaf整合Shiro标签

在使用JSP的时候可以直接在JSP页面之中使用shiro标签来判断用户是否登录或者来进行授权检测,但是在SpringBoot里面所使用的页面技术为thymeleaf,那么如果要想在这样的模板页面之中实现Shiro控制,就必须去引入新的依赖包,同时做出一些新的配置。

1、【microboot-shiro-web】修改pom.xml配置文件,追加thymeleaf与shiro的整合依赖:

<dependency>
			<groupId>com.github.theborakompanioni</groupId>
			<artifactId>thymeleaf-extras-shiro</artifactId>
			<version>2.0.0</version>
		</dependency>
           

2、【microboot-shiro-web】随后需要修改一下Shiro配置类,在这个配置类之中需要启用Shiro页面支持:

@Bean
	public ShiroDialect getShiroDialect(){ //必须配置此操作才可以使用thymeleaf-extras-shiro开发包
		return new ShiroDialect();
	}
           

3、【microboot-shiro-web】建立一个新的页面:member_show.html页面:

  • 修改DeptController程序类进行一个跳转的配置:
@RequestMapping("/pages/back/dept/show")
	public String show(){
		return "dept_show";
	}
           
  • 建立dept_show.html页面,而后在页面之中需要编写以下代码:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
	  xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" >
<head>
	<title>SpringBoot模版渲染</title>
	<script type="text/javascript" th:src="@{/js/main.js}"></script> 
	<link rel="icon" type="image/x-icon" href="/images/mldn.ico"/>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
	<h1>显示部门信息的内容</h1>
    <h2>欢迎:<shiro:principal/></h2>
</body> 
</html>
           

4、【microboot-shiro-web】修改dept_show.html页面进行认证和授权检测

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
	  xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" >
<head>
	<title>SpringBoot模版渲染</title>
	<script type="text/javascript" th:src="@{/js/main.js}"></script> 
	<link rel="icon" type="image/x-icon" href="/images/mldn.ico"/>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
	<h1>显示部门信息的内容</h1>
    <h2>欢迎:<shiro:principal/></h2>
    <p><a shiro:hasRole="emp">雇员管理</a></p>
	<p><a shiro:hasRole="dept">部门管理</a></p>
    <p><a shiro:hasPermission="emp:add">雇员增加</a></p>
	<p><a shiro:hasPermission="dept:edit">部门修改</a></p>
    <p shiro:notAuthenticated="">您还未登录,请先登录!</p>
	<p shiro:authenticated="">欢迎光临!</p>
</body> 
</html>
           

如果在以后进行Shiro与SpringBoot整合的时候一定要考虑使用如上的标签进行整体处理。

继续阅读