聲明:本文來源於MLDN培訓視頻的課堂筆記,寫在這裏只是爲了方便查閱。javascript
一、概念:SpringBoot 整合 Shirohtml
二、具體內容java
Shiro 是如今最爲流行的權限認證開發框架,與它起名的只有最初的 SpringSecurity(這個開發框架很是很差用,可是千萬不要 覺得 SpringSecurity 沒有用處,它在 SpringCloud 階段將發揮重大的做用)。可是如今若是要想整合 Shiro 開發框架有一點很遺憾, SpringBoot 沒有直接的配置支持,它不像整合所謂的 Kafka、Redis、DataSource,也就是說若是要想整合 Shiro 開發框架那麼就必須 本身來進行配置。mysql
2.一、項目開發準備git
在整個的 Shiro 之中最爲重要的部分:認證以及受權處理(Realm),在 Realm 裏面實際上在開發之中所須要調用的業務方法 只有兩類:根據用戶編號取得用戶的完整信息,在認證經過以後根據用戶編號得到用戶對應的全部的角色以及權限信息,並且既然已經到了微架構的階段,那麼不得不去面對一個問題,對於這種用戶的業務操做是放在 WEB 端仍是單獨提出來作成一個 Rest 服務? 很明顯,應該做爲一個服務進行抽象出來,也就是說在總體的調用處理之中,Realm 須要進行 Rest 服務調用(RestTemplate 存在可 以讓整個的調用更加容易)。github
那麼按照如上的設計方案,如今的總體的項目裏面認爲應該包含有以下的幾個開發模塊:web
· microboot-shiro-api:應該提供有服務的 VO 類、各類加密處理的工具類;redis
· microboot-shiro-member-provider:進行用戶認證與受權 REST 服務的提供,要暴露兩個接口:用戶信息得到、角色與權限信息得到;spring
· microboot-shiro-web:主要進行 Shiro 的認證與受權檢測處理。sql
一、 【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') ;
二、 【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; } @Override public String toString() { return "Member [mid=" + mid + ", name=" + name + ", password=" + password + ", locked=" + locked + "]"; } }
三、 【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; } 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; } }
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.二、用戶微服務
所謂的用戶微服務指的是要求在「microboot-shiro-member-provider」裏面進行實現,該服務之中須要考慮以下的幾點:
· 該服務須要進行數據庫的開發,因此必定要進行數據庫鏈接池的配置;
· 既然要進行微服務的編寫,那麼就必定須要提供有業務接口以及 DAO 實現子類,如今的實現將依靠 MyBatis 完成;
· 全部的微服務最終要經過控制器的 Rest 進行發佈處理。
一、 【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>
二、 【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) ; }
三、 【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.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/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.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/mldn/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>
四、 【microboot-shiro-member-provider】修改 application.yml 配置文件:
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路徑
type-aliases-package: cn.study.vo # 定義全部操做類的別名所在包
mapper-locations: # 全部的mapper映射文件
- classpath:mybatis/mapper/**/*.xml
spring:
messages:
basename: i18n/Messages,i18n/Pages
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 配置當前要使用的數據源的操做類型
driver-class-name: org.gjt.mm.mysql.Driver # 配置MySQL的驅動程序類
url: jdbc:mysql://localhost:3306/study # 數據庫鏈接地址
username: root # 數據庫用戶名
password: mysqladmin # 數據庫鏈接密碼
dbcp2: # 進行數據庫鏈接池的配置
min-idle: 5 # 數據庫鏈接池的最小維持鏈接數
initial-size: 5 # 初始化提供的鏈接數
max-total: 5 # 最大的鏈接數
max-wait-millis: 200 # 等待鏈接獲取的最大超時時間
五、 【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 { @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<String, Set<String>>(); map.put("allRoles", this.roleDAO.findAllRoleByMember(mid)); map.put("allActions", this.actionDAO.findAllActionByMember(mid)); return map; } }
六、 【microboot-shiro-member-provider】編寫業務層功能測試類;
package cn.study.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.study.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")); } }
七、 【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 { @Resource private 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;
受權服務端口:http://localhost:8001/member/auth?mid=admin;
八、 【microboot-shiro-member-provider】編寫控制層測試,若是要訪問 Rest 服務確定要使用 RestTemplate 完成,這個類如今爲了 簡單起見,直接進行對象實例化處理:
package cn.study.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.study.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.三、定義 Shiro 整合服務
在本次項目之中 WEB 模塊爲「microboot-shiro-web」,很明顯對於 WEB 模塊之中必需要求調用用戶認證與受權微服務(Realm), 然後須要進行各類依賴包的配置(Shiro)、考慮到各類緩存的問題、認證與受權檢測問題。
一、 【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>
二、 【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 { @Bean public RestTemplate getRestTemplate() { return new RestTemplate() ; } }
三、 【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 { @Resource private RestTemplate restTemplate ; @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { System.out.println("============== 一、進行認證操做處理 =============="); 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; } @SuppressWarnings("unchecked") @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { System.out.println("++++++++++++++ 二、進行受權操做處理 ++++++++++++++"); // 該操做的主要目的是取得受權信息,說的直白一點就是角色和權限數據 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; } }
四、 【microboot-shiro-web】如今雖然準備好了 Realm 程序類,可是在整個 Shiro 進行整合處理的時候實際上須要編寫大量的配置 程序類,因此這個時候若是直接使用 xml 配置文件雖然能夠,可是不標準,最好的作法是你將全部的 xml 配置項變爲 Bean 配置。
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 { @Bean public MemberRealm getRealm() {// 一、獲取配置的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() {// 二、緩存配置 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("studyJAVA-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("study-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; } }
在src/main/resources 目錄之中編寫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>
五、 【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 "部門信息" ; } }
六、 【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"; } }
七、 【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>
此時實現了一個最基礎的整合處理操做。
2.四、使用 Redis 進行數據緩存
如今是使用了 EHCache 緩存組件進行了緩存處理,而實際的項目之中每每會利用 Redis 實現緩存配置,那麼下面將對程序進 行一些修改。
一、 【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.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; } }); } }
二、 【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>(); @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; } }
三、 【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; // 此時的類將實現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()); } }
四、 【microboot-shiro-web】在當前的項目開發過程之中,配置 Shiro 的 Bean 裏面所使用的仍是 EHCache 緩存組件,因此須要進 行更換處理。
· 更換如今要使用的 SessionDAO 實現子類:
· 更換使用的緩存組件:
1 package cn.mldn.microboot.config; 2 import java.util.HashMap; 3 import java.util.Map; 4 5 import javax.servlet.Filter; 6 7 import org.apache.shiro.mgt.RememberMeManager; 8 import org.apache.shiro.realm.Realm; 9 import org.apache.shiro.session.mgt.SessionManager; 10 import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator; 11 import org.apache.shiro.session.mgt.eis.SessionDAO; 12 import org.apache.shiro.session.mgt.eis.SessionIdGenerator; 13 import org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler; 14 import org.apache.shiro.spring.LifecycleBeanPostProcessor; 15 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 16 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 17 import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; 18 import org.apache.shiro.web.filter.authc.LogoutFilter; 19 import org.apache.shiro.web.mgt.CookieRememberMeManager; 20 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 21 import org.apache.shiro.web.servlet.SimpleCookie; 22 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 23 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 24 import org.springframework.context.annotation.Bean; 25 import org.springframework.context.annotation.Configuration; 26 import org.springframework.context.annotation.DependsOn; 27 28 import cn.mldn.microboot.cache.RedisCacheManager; 29 import cn.mldn.microboot.realm.CustomerCredentialsMatcher; 30 import cn.mldn.microboot.realm.MemberRealm; 31 import cn.mldn.microboot.session.RedisSessionDAO; 32 33 @Configuration 34 public class ShiroConfig { 35 @Bean 36 public MemberRealm getRealm() {// 一、獲取配置的Realm,之因此沒使用註解配置,是由於此處須要考慮到加密處理 37 MemberRealm realm = new MemberRealm(); 38 realm.setCredentialsMatcher(new CustomerCredentialsMatcher()); 39 return realm; 40 } 41 42 @Bean(name = "lifecycleBeanPostProcessor") 43 public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { 44 return new LifecycleBeanPostProcessor(); 45 } 46 47 @Bean 48 @DependsOn("lifecycleBeanPostProcessor") 49 public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { 50 DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); 51 daap.setProxyTargetClass(true); 52 return daap; 53 } 54 55 // @Bean 56 // public EhCacheManager getCacheManager() {// 二、緩存配置 57 // EhCacheManager cacheManager = new EhCacheManager(); 58 // cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml"); 59 // return cacheManager; 60 // } 61 62 @Bean 63 public SessionIdGenerator getSessionIdGenerator() { // 3 64 return new JavaUuidSessionIdGenerator(); 65 } 66 //更換如今要使用的 SessionDAO 實現子類 67 @Bean 68 public SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { // 4 69 RedisSessionDAO sessionDAO = new RedisSessionDAO(); // 使用Redis進行Session管理 70 sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache"); 71 sessionDAO.setSessionIdGenerator(sessionIdGenerator); 72 return sessionDAO; 73 } 74 75 @Bean 76 public RememberMeManager getRememberManager() { // 5 77 CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); 78 SimpleCookie cookie = new SimpleCookie("MLDNJAVA-RememberMe"); 79 cookie.setHttpOnly(true); 80 cookie.setMaxAge(3600); 81 rememberMeManager.setCookie(cookie); 82 return rememberMeManager; 83 } 84 85 @Bean 86 public QuartzSessionValidationScheduler getQuartzSessionValidationScheduler() { 87 QuartzSessionValidationScheduler sessionValidationScheduler = new QuartzSessionValidationScheduler(); 88 sessionValidationScheduler.setSessionValidationInterval(100000); 89 return sessionValidationScheduler; 90 } 91 92 @Bean 93 public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor( 94 DefaultWebSecurityManager securityManager) { 95 AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); 96 aasa.setSecurityManager(securityManager); 97 return aasa; 98 } 99 100 @Bean 101 public DefaultWebSessionManager getSessionManager(SessionDAO sessionDAO, 102 QuartzSessionValidationScheduler sessionValidationScheduler) { // 6 103 DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); 104 sessionManager.setGlobalSessionTimeout(1000000); 105 sessionManager.setDeleteInvalidSessions(true); 106 sessionManager.setSessionValidationScheduler(sessionValidationScheduler); 107 sessionManager.setSessionValidationSchedulerEnabled(true); 108 sessionManager.setSessionDAO(sessionDAO); 109 SimpleCookie sessionIdCookie = new SimpleCookie("mldn-session-id"); 110 sessionIdCookie.setHttpOnly(true); 111 sessionIdCookie.setMaxAge(-1); 112 sessionManager.setSessionIdCookie(sessionIdCookie); 113 sessionManager.setSessionIdCookieEnabled(true); 114 return sessionManager; 115 } 116 //更換使用的緩存組件 117 @Bean 118 public DefaultWebSecurityManager getSecurityManager(Realm memberRealm, RedisCacheManager cacheManager, 119 SessionManager sessionManager, RememberMeManager rememberMeManager) {// 7 120 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 121 securityManager.setRealm(memberRealm); 122 securityManager.setCacheManager(cacheManager); 123 securityManager.setSessionManager(sessionManager); 124 securityManager.setRememberMeManager(rememberMeManager); 125 return securityManager; 126 } 127 128 public FormAuthenticationFilter getLoginFilter() { // 在ShiroFilterFactoryBean中使用 129 FormAuthenticationFilter filter = new FormAuthenticationFilter(); 130 filter.setUsernameParam("mid"); 131 filter.setPasswordParam("password"); 132 filter.setRememberMeParam("rememberMe"); 133 filter.setLoginUrl("/loginPage"); // 登陸提交頁面 134 filter.setFailureKeyAttribute("error"); 135 return filter; 136 } 137 138 public LogoutFilter getLogoutFilter() { // 在ShiroFilterFactoryBean中使用 139 LogoutFilter logoutFilter = new LogoutFilter(); 140 logoutFilter.setRedirectUrl("/"); // 首頁路徑,登陸註銷後回到的頁面 141 return logoutFilter; 142 } 143 144 @Bean 145 public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { 146 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 147 // 必須設置 SecurityManager 148 shiroFilterFactoryBean.setSecurityManager(securityManager); 149 shiroFilterFactoryBean.setLoginUrl("/loginPage"); // 設置登陸頁路徑 150 shiroFilterFactoryBean.setSuccessUrl("/pages/hello"); // 設置跳轉成功頁 151 shiroFilterFactoryBean.setUnauthorizedUrl("/pages/unauthUrl"); // 受權錯誤頁 152 Map<String, Filter> filters = new HashMap<String, Filter>(); 153 filters.put("authc", this.getLoginFilter()); 154 filters.put("logout", this.getLogoutFilter()); 155 shiroFilterFactoryBean.setFilters(filters); 156 Map<String, String> filterChainDefinitionMap = new HashMap<String, String>(); 157 filterChainDefinitionMap.put("/logout", "logout"); 158 filterChainDefinitionMap.put("/loginPage", "authc"); // 定義內置登陸處理 159 filterChainDefinitionMap.put("/pages/back/**", "authc"); 160 filterChainDefinitionMap.put("/*", "anon"); 161 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 162 return shiroFilterFactoryBean; 163 } 164 }
五、 【microboot-shiro-web】修改 application.yml 配置文件進行 Redis 配置:
spring:
redis:
host: x.x.x.x
port: 6379
password: studyjava
timeout: 1000
database: 0
pool:
max-active: 10
max-idle: 8
min-idle: 2
max-wait: 100
server:
port: 8080
六、 【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]; @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); } }
· 實現 RedisTemplate 配置程序類:
package cn.study.microboot.config; import cn.study.microboot.util.RedisObjectSerializer; 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; @Configuration public class RedisConfig { @Resource private 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 實現了緩存處理,這樣將適合於分佈式集羣開發。
2.五、thymeleaf 整合 Shiro 標籤
在使用 JSP 的時候能夠直接在 JSP 頁面之中使用 shiro 標籤來判斷用戶是否登陸或者來進行受權檢測,可是在 SpringBoot 裏面 所使用的頁面技術爲 thymeleaf,那麼若是要想在這樣的模版頁面之中實現 Shiro 控制,就必須去引入新的依賴包,同時作出一些新 的配置
一、 【microboot-shiro-web】修改 pom.xml 配置文件,追加 thymeleaf 與 shiro 的整合依賴:
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>1.2.1</version> </dependency>
二、 【microboot-shiro-web】隨後須要修改一下 Shiro 配置類,在這個配置類之中須要啓用 Shiro 頁面支持:
package cn.study.microboot.config; import java.util.HashMap; import java.util.Map; import javax.servlet.Filter; 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.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 at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import cn.study.microboot.cache.RedisCacheManager; import cn.study.microboot.realm.CustomerCredentialsMatcher; import cn.study.microboot.realm.MemberRealm; import cn.study.microboot.session.RedisSessionDAO; @Configuration public class ShiroConfig { @Bean public ShiroDialect getShiroDialect() { // 必須配置此操做纔可使用thymeleaf-extras-shiro開發包 return new ShiroDialect() ; } @Bean public MemberRealm getRealm() {// 一、獲取配置的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() {// 二、緩存配置 // 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 RedisSessionDAO sessionDAO = new RedisSessionDAO(); // 使用Redis進行Session管理 sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache"); sessionDAO.setSessionIdGenerator(sessionIdGenerator); return sessionDAO; } @Bean public RememberMeManager getRememberManager() { // 5 CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); SimpleCookie cookie = new SimpleCookie("studyJAVA-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("study-session-id"); sessionIdCookie.setHttpOnly(true); sessionIdCookie.setMaxAge(-1); sessionManager.setSessionIdCookie(sessionIdCookie); sessionManager.setSessionIdCookieEnabled(true); return sessionManager; } @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; } 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; } }
三、 【microboot-shiro-web】創建一個新的頁面:dept_show.html 頁面;
· 修改 DeptController 程序類進行一個跳轉的配置:
package cn.study.microboot.controller; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class DeptController { @RequiresAuthentication @RequestMapping("/pages/back/dept/get") @ResponseBody public String get() { return "部門信息" ; } @RequestMapping("/pages/back/dept/show") public String show() { return "dept_show" ; } }
· 創建 dept_show.html 頁面,然後在頁面之中須要編寫如下代碼:
<!DOCTYPE HTML> <html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <title>SpringBoot模版渲染</title> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> </head> <body> <h1>顯示部門信息的內容</h1> <h2>歡迎:<shiro:principal/></h2> </body> </html>
四、 【microboot-shiro-web】修改 dept_show.html 頁面進行認證與受權的處理操做。
<!DOCTYPE HTML> <html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <title>SpringBoot模版渲染</title> <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 整合的時候必定要考慮使用如上的標籤進行總體處理。
三、總結
SpringBoot 總結:
· 優勢:
|- Rest 支持度高,總體的開發難度相對於 SSM、SSH 整合仍是挺簡單的;
|- 與各個服務的單一集成很方便,可是若是要進行多集成就很是麻煩了,須要編寫各類配置類;
|- thymeleaf 做爲一款優秀的頁面模版工具,所帶來的功能的確強悍,頁面開發更簡單;
|- 與它想整合的開發框架整合方便;
|- 方便使用 jar 包進行項目部署與發佈;
· 缺點:
|- thymeleaf 頁面開發要求較高,由於語法嚴格;
|- 太簡單了,讓人不適應。
SpringBoot 中的 Rest 就是邁向 SpringCloud 的第一步。