當前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
SpringBoot整合 Shiro
生活随笔
收集整理的這篇文章主要介紹了
SpringBoot整合 Shiro
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Shiro 是現在最為流行的權限認證開發框架,與它起名的只有最初的 SpringSecurity(這個開發框架非常
不好用,但是千萬不要 以為 SpringSecurity 沒有用處,它在 SpringCloud 階段將發揮重大的作用)。
但是現在如果要想整合 Shiro 開發框架有一點很遺憾, SpringBoot 沒有直接的配置支持,它不像整合
所謂的 Kafka、Redis、DataSource,也就是說如果要想整合 Shiro 開發框架那么就必須 自己來進行配置。
2.1、項目開發準備在整個的 Shiro 之中最為重要的部分:認證以及授權處理(Realm),在 Realm 里面實際上在開發之中所
需要調用的業務方法 只有兩類:根據用戶編號取得用戶的完整信息,在認證通過之后根據用戶編號獲得用戶
對應的所有的角色以及權限信息,而且既然已經到了微架構的階段,那么不得不去面對一個問題,對于這種
用戶的業務操作是放在 WEB 端還是單獨提出來做成一個 Rest 服務? 很明顯,應該作為一個服務進行抽象
出來,也就是說在整體的調用處理之中,Realm 需要進行 Rest 服務調用(RestTemplate 存在可 以讓整個
的調用更加容易)。那么按照如上的設計方案,現在的整體的項目里面認為應該包含有如下的幾個開發模塊:· microboot-shiro-api:應該提供有服務的 VO 類、各種加密處理的工具類;· microboot-shiro-member-provider:進行用戶認證與授權 REST 服務的提供,要暴露兩個接口:
用戶信息獲得、角色與權限信息獲得;
1、 【microboot-shiro-member-provider】保存本次的數據庫腳本-- 刪除數據庫
DROP DATABASE IF EXISTS study ;
-- 創建數據庫
CREATE DATABASE study CHARACTER SET UTF8 ;
-- 使用數據庫
USE study ;
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 ('studyjava','study','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 ('studyjava','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.study.vo;import java.io.Serializable;@SuppressWarnings("serial")
public class Member implements Serializable {private String mid ;private String name ;private String password ;private Integer locked ;public String getMid() {return mid;}public void setMid(String mid) {this.mid = mid;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Integer getLocked() {return locked;}public void setLocked(Integer locked) {this.locked = locked;}@Overridepublic String toString() {return "Member [mid=" + mid + ", name=" + name + ", password="+ password + ", locked=" + locked + "]";}
}
3、 【microboot-shiro-api】密碼的加密處理;package cn.study.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;} elsei = 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;}
}
package cn.study.util.enctype;import java.util.Base64;public class PasswordUtil {private static final String SEED = "studyjava" ;
// 該數據為種子數,如果要加密則需要使用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 ; }
}
2.2、用戶微服務所謂的用戶微服務指的是要求在“microboot-shiro-member-provider”里面進行實現,該服務之中需要考慮
如下的幾點:· 該服務需要進行數據庫的開發,所以一定要進行數據庫連接池的配置;· 既然要進行微服務的編寫,那么就一定需要提供有業務接口以及 DAO 實現子類,現在的實現將依靠MyBatis 完成;· 所有的微服務最終要通過控制器的 Rest 進行發布處理。1、 【microboot-shiro-member-provider】配置 Druid 數據庫連接池;· 需要修改 pom.xml 配置文件,為項目的整合添加相關的支持包:<dependencies><dependency><!-- 引入web模塊 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>microboot-shiro-api</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><version>1.0.31</version></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><version>2.0.0</version></dependency></dependencies>
2、 【microboot-shiro-member-provider】建立幾個 DAO 接口:· 提供用戶認證的 DAO 接口:IMemberDAO;package cn.study.microboot.dao;import org.apache.ibatis.annotations.Mapper;import cn.study.vo.Member;@Mapper
public interface IMemberDAO {public Member findById(String mid) ;
}
· 提供角色檢測的 IRoleDAO 接口:package cn.study.microboot.dao;import java.util.Set;import org.apache.ibatis.annotations.Mapper;@Mapper
public interface IRoleDAO {public Set<String> findAllRoleByMember(String mid) ;
}
· 提供所有權限檢測的 IActionDAO 接口:package cn.study.microboot.dao;import java.util.Set;import org.apache.ibatis.annotations.Mapper;@Mapper
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/study/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.study.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/study/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.study.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/study/Action.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.study.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.properties 配置文件:#server.port = 80spring.datasource.url=jdbc:mysql://localhost:3306/study
?characterEncoding=utf-8&useSSL=false&useUnicode=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.druid.initialSize=5
spring.datasource.druid.minIdle=5
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000## Mybatis?
mybatis.config-location=classpath:mybatis/mybatis.cfg.xml
mybatis.mapper-locations=classpath:mybatis/mapper/**/*.xml
mybatis.typeAliasesPackage=cn.study.vo
5、 【microboot-shiro-member-provider】定義 IMemberService 業務接口:package cn.study.microboot.service;import java.util.Map;
import java.util.Set;import cn.study.vo.Member;public interface IMemberService {public Member get(String mid) ;public Map<String,Set<String>> listAuthByMember(String mid) ;
}
package cn.study.microboot.service.impl;import java.util.HashMap;
import java.util.Map;
import java.util.Set;import javax.annotation.Resource;import org.springframework.stereotype.Service;import cn.study.microboot.dao.IActionDAO;
import cn.study.microboot.dao.IMemberDAO;
import cn.study.microboot.dao.IRoleDAO;
import cn.study.microboot.service.IMemberService;
import cn.study.vo.Member;@Service
public class MemberServiceImpl implements IMemberService {@Resourceprivate IMemberDAO memberDAO;@Resourceprivate IRoleDAO roleDAO;@Resourceprivate IActionDAO actionDAO;@Overridepublic Member get(String mid) {return this.memberDAO.findById(mid);}@Overridepublic Map<String, Set<String>> listAuthByMember(String mid) {Map<String, Set<String>> map = new HashMap<String, Set<String>>();map.put("allRoles", this.roleDAO.findAllRoleByMember(mid));map.put("allActions", this.actionDAO.findAllActionByMember(mid));return map;}}
6、 【microboot-shiro-member-provider】編寫業務層功能測試類;package cn.study.microboot.test;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.study.microboot.StartSpringBootMain;
import cn.study.microboot.service.IMemberService;@SpringBootTest(classes = StartSpringBootMain.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestMemberService {@Resourceprivate IMemberService memberService ;@Testpublic void testGet() {System.out.println(this.memberService.get("admin"));}@Testpublic void testAuth() {System.out.println(this.memberService.listAuthByMember("admin"));}
}
7、 【microboot-shiro-member-provider】進行控制層編寫,控制層現在給出的一定是 Rest 服務:package cn.study.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.study.microboot.service.IMemberService;@RestController
public class MemberController {@Resourceprivate IMemberService memberService;@RequestMapping(value="/member/get",method=RequestMethod.POST)public Object get(String mid) {return this.memberService.get(mid) ;}@RequestMapping(value="/member/auth",method=RequestMethod.POST)public Object auth(String mid) {return this.memberService.listAuthByMember(mid) ;}
}認證服務端口:http://localhost:8001/member/get?mid=admin{"mid":"admin","name":"admin","password":"2E866BF58289E01583AD418F486A69DF","locked":0}授權服務端口:http://localhost:8001/member/auth?mid=admin{"allActions":["emp:list","dept:edit","dept:list","emp:edit","emp:remove","emp:add"],
"allRoles":["emp","dept"]}
8、 【microboot-shiro-member-provider】編寫控制層測試,如果要訪問 Rest 服務肯定要使用 RestTemplate 完成,這個類現在為了 簡單起見,直接進行對象實例化處理:package cn.study.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.study.microboot.service.IMemberService;@RestController
public class MemberController {@Resourceprivate IMemberService memberService;@RequestMapping(value="/member/get",method=RequestMethod.POST)public Object get(String mid) {return this.memberService.get(mid) ;}@RequestMapping(value="/member/auth",method=RequestMethod.POST)public Object auth(String mid) {return this.memberService.listAuthByMember(mid) ;}
}package cn.study.microboot.test;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.study.microboot.StartSpringBootMain;
import cn.study.vo.Member;@SpringBootTest(classes = StartSpringBootMain.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestMemberController {private RestTemplate restTemplate = new RestTemplate() ;@Testpublic 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")@Testpublic 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);}
}記得一定要先啟動服務,否則會報錯:
org.springframework.web.client.ResourceAccessException: I/O error on POST request for package cn.study.microboot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class StartSpringBootMain{ // 必須繼承指定的父類public static void main(String[] args) throws Exception {SpringApplication.run(StartSpringBootMain.class, args);}
}那么此時一個專門進行用戶認證以及授權檢測的微服務開發完成。
2.3、定義 Shiro 整合服務在本次項目之中 WEB 模塊為“microboot-shiro-web”,很明顯對于 WEB 模塊之中必須要求調用用戶認證與
授權微服務(Realm), 而后需要進行各種依賴包的配置(Shiro)、考慮到各種緩存的問題、認證與授權檢測
問題。
1、 【microboot-shiro-web】修改 pom.xml 配置文件,追加 Shiro 的相關依賴程序包:<dependency><groupId>cn.study</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.3.2</version>
</dependency>
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.1</version>
</dependency>
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-quartz</artifactId><version>1.3.1</version>
</dependency>
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.3.1</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、 【microboot-shiro-web】建立一個 RestTemplate 的配置類對象:package cn.study.microboot.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class RestConfig {@Beanpublic RestTemplate getRestTemplate() {return new RestTemplate() ;}
}
3、 【microboot-shiro-web】Shiro 之中所有認證與授權的處理都在 Realm 之中定義了;package cn.study.microboot.realm;import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;import javax.annotation.Resource;import org.apache.shiro.SecurityUtils;
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.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
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 cn.study.util.enctype.PasswordUtil;
import cn.study.vo.Member;public class MemberRealm extends AuthorizingRealm {@Resourceprivate RestTemplate restTemplate ;@Overrideprotected 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就是truethrow new LockedAccountException("被鎖了,求解鎖去吧!");}// 定義需要進行返回的操作數據信息項,返回的認證信息使用應該是密文SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(token.getPrincipal(), password, "memberRealm");// 在認證完成之后可以直接取得用戶所需要的信息內容,保存在Session之中SecurityUtils.getSubject().getSession().setAttribute("name", "我的名字");return auth;}@SuppressWarnings("unchecked")@Overrideprotected 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<String>() ;Set<String> allActions = new HashSet<String>() ;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.study.microboot.realm;import cn.study.util.enctype.MD5Code;public class MyPasswordEncrypt {private static final String SALT = "*****************";/** * 提供有密碼的加密處理操作* @param password* @return*/public static String encryptPassword(String password) {return new MD5Code().getMD5ofStr(password + "*****"+SALT+"*****") ;}
}
package cn.study.microboot.realm;import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;public class CustomerCredentialsMatcher extends SimpleCredentialsMatcher {@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {// 取得原始的輸入數據信息Object tokenCredentials = MyPasswordEncrypt.encryptPassword(super.toString(
token.getCredentials())).getBytes();// 取得認證數據庫中的數據Object accountCredentials = super.getCredentials(info) ;return super.equals(tokenCredentials, accountCredentials); }
}
package cn.study.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.study.microboot.realm.CustomerCredentialsMatcher;
import cn.study.microboot.realm.MemberRealm;@Configuration
public class ShiroConfig {@Beanpublic 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;}@Beanpublic EhCacheManager getCacheManager() {// 2、緩存配置EhCacheManager cacheManager = new EhCacheManager();cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");return cacheManager;}@Beanpublic SessionIdGenerator getSessionIdGenerator() { // 3return new JavaUuidSessionIdGenerator();}@Beanpublic SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { // 4EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");sessionDAO.setSessionIdGenerator(sessionIdGenerator);return sessionDAO;}@Beanpublic RememberMeManager getRememberManager() { // 5CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();SimpleCookie cookie = new SimpleCookie("studyJAVA-RememberMe");cookie.setHttpOnly(true);cookie.setMaxAge(3600);rememberMeManager.setCookie(cookie);return rememberMeManager;}@Beanpublic QuartzSessionValidationScheduler getQuartzSessionValidationScheduler() {QuartzSessionValidationScheduler sessionValidationScheduler
= new QuartzSessionValidationScheduler();sessionValidationScheduler.setSessionValidationInterval(100000);return sessionValidationScheduler;}@Beanpublic AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();aasa.setSecurityManager(securityManager);return aasa;}@Beanpublic DefaultWebSessionManager getSessionManager(SessionDAO sessionDAO,QuartzSessionValidationScheduler sessionValidationScheduler) { // 6DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setGlobalSessionTimeout(1000000);sessionManager.setDeleteInvalidSessions(true);sessionManager.setSessionValidationScheduler(sessionValidationScheduler);sessionManager.setSessionValidationSchedulerEnabled(true);sessionManager.setSessionDAO(sessionDAO);SimpleCookie sessionIdCookie = new SimpleCookie("study-session-id");sessionIdCookie.setHttpOnly(true);sessionIdCookie.setMaxAge(-1);sessionManager.setSessionIdCookie(sessionIdCookie);sessionManager.setSessionIdCookieEnabled(true);return sessionManager;}@Beanpublic DefaultWebSecurityManager getSecurityManager(Realm memberRealm,
EhCacheManager cacheManager,SessionManager sessionManager, RememberMeManager rememberMeManager) {// 7DefaultWebSecurityManager 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;}@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(
DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必須設置 SecurityManagershiroFilterFactoryBean.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;}
}
在src/main/resources 目錄之中編寫ehcache.xml 配置文件;ehcache.xml<?xml version="1.1" encoding="UTF-8"?>
<ehcache name="shirocache"><diskStore path="java.io.tmpdir"/><defaultCache maxElementsInMemory="2000"eternal="true"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="true"/><!-- <cache name="diskCache"maxEntriesLocalHeap="2000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"></cache> --><cache name="passwordRetryCache"maxElementsInMemory="2000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="0"overflowToDisk="false"></cache><cache name="authorizationCache"maxElementsInMemory="2000"eternal="false"timeToIdleSeconds="1800"timeToLiveSeconds="0"overflowToDisk="false"></cache><cache name="authenticationCache"maxElementsInMemory="2000"eternal="false"timeToIdleSeconds="1800"timeToLiveSeconds="0"overflowToDisk="false"></cache><cache name="shiro-activeSessionCache"maxElementsInMemory="2000"eternal="false"timeToIdleSeconds="1800"timeToLiveSeconds="0"overflowToDisk="false"></cache>
</ehcache>
5、 【microboot-shiro-web】建立一個控制器package cn.study.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.study.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】建立一個 templates/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/study.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="studyjava"/><br/>密 碼:<input type="text" name="password" value="hello"/><br/><input type="submit" value="登錄"/></form>
</body>
</html>
application.propertiesserver.port = 8002spring.datasource.url=jdbc:mysql://localhost:3306/study
?characterEncoding=utf-8&useSSL=false&useUnicode=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.druid.initialSize=5
spring.datasource.druid.minIdle=5
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000package cn.study.microboot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class ShiroWEBStartSpringBootMain{ // 必須繼承指定的父類public static void main(String[] args) throws Exception {SpringApplication.run(ShiroWEBStartSpringBootMain.class, args);}
}
2.4、使用 Redis 進行數據緩存現在是使用了 EHCache 緩存組件進行了緩存處理,而實際的項目之中往往會利用 Redis 實現緩存配置,那么下面將對程序進 行一些修改。
1、 【microboot-shiro-web】如果要進行緩存的使用,則首先一定要配置緩存處理類;package cn.study.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.apache.shiro.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;}@Overridepublic V get(K key) throws CacheException {log.info("### get() : K = " + key);return (V) this.redisTempate.opsForValue().get(key.toString());}@Overridepublic V put(K key, V value) throws CacheException {log.info("### put() : K = " + key + "、V = " + value);this.redisTempate.opsForValue().set(key.toString(), value);return value;}@Overridepublic V remove(K key) throws CacheException {log.info("### remove() : K = " + key);V val = this.get(key);this.redisTempate.delete(key.toString());return val;}@Overridepublic void clear() throws CacheException {log.info("### clear()");this.redisTempate.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection)throws DataAccessException {connection.flushDb(); // 清空數據庫return true;}});}@Overridepublic int size() {log.info("### size()");return this.redisTempate.execute(new RedisCallback<Integer>() {@Overridepublic Integer doInRedis(RedisConnection connection)throws DataAccessException {return connection.keys("*".getBytes()).size();}});}@Overridepublic Set<K> keys() {log.info("### keys()");return this.redisTempate.execute(new RedisCallback<Set<K>>() {@Overridepublic 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;}});}@Overridepublic Collection<V> values() {log.info("### values()");return this.redisTempate.execute(new RedisCallback<Set<V>>() {@Overridepublic 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.study.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>();@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic 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.study.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;
import org.springframework.data.redis.core.RedisTemplate;public class RedisSessionDAO extends EnterpriseCacheSessionDAO {private Log log = LogFactory.getLog(RedisSessionDAO.class);@Resourceprivate RedisTemplate<String, Object> redisTempate; // 要提供有Redis處理工具類@Overrideprotected Serializable doCreate(Session session) { // 創建Session,返回session idlog.info("*** doCreate : " + session);Serializable sessionId = super.doCreate(session); // 創建sessionid// 將當前創建好的Session的數據保存在Redis數據庫里面this.redisTempate.opsForValue().set(sessionId.toString(), session,1800);return sessionId;}@Overrideprotected 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;}@Overrideprotected void doUpdate(Session session) { // 實現Session更新,每次操作都要更新log.info("*** doUpdate : " + session);super.doUpdate(session);if (session != null) {this.redisTempate.opsForValue().set(session.getId().toString(),session, 1800);}}@Overrideprotected 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 實現子類:· 更換使用的緩存組件:cn.study.microboot.config.ShiroConfig//更換現在要使用的 SessionDAO 實現子類
@Bean
public SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { // 4RedisSessionDAO sessionDAO = new RedisSessionDAO(); // 使用Redis進行Session管理sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");sessionDAO.setSessionIdGenerator(sessionIdGenerator);return sessionDAO;
}
5、 【microboot-shiro-web】修改 application.yml 配置文件進行 Redis 配置:spring:redis:host: x.x.x.xport: 6379password: studyjavatimeout: 1000database: 0pool:max-active: 10max-idle: 8min-idle: 2max-wait: 100
server:port: 8080
6、 【microboot-shiro-web】建立一個 RedisTemplate 的配置程序類。· 定義 Redis 序列化管理器:package cn.study.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];@Overridepublic byte[] serialize(Object object) throws SerializationException {if (object == null) {return EMPTY_ARRAY;}try {return serializer.convert(object);} catch (Exception ex) {return EMPTY_ARRAY;}}@Overridepublic 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);}
}
· 實現 RedisTemplate 配置程序類:package cn.study.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.study.microboot.util.RedisObjectSerializer;@Configuration
public class RedisConfig {@Resourceprivate JedisConnectionFactory jedisConnectionFactory;@Bean({"shiroRedis"})public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate template = new RedisTemplate();template.setConnectionFactory(this.jedisConnectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new RedisObjectSerializer());return template;}
}此時就使用了 Redis 實現了緩存處理,這樣將適合于分布式集群開發。
?
總結
以上是生活随笔為你收集整理的SpringBoot整合 Shiro的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用RestTemplate模版实现 R
- 下一篇: SpringCloud 搭建项目环境、创