SpringBoot系列十二:SpringBoot整合 Shiro
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅。
1、概念:SpringBoot 整合 Shiro
2、具体内容
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 服务的提供,要暴露两个接口:用户信息获得、角色与权限信息获得;
· microboot-shiro-web:主要进行 Shiro 的认证与授权检测处理。
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;
- }
- @Override
- public 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;
- } 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.2、用户微服务
所谓的用户微服务指的是要求在“microboot-shiro-member-provider”里面进行实现,该服务之中需要考虑如下的几点:
· 该服务需要进行数据库的开发,所以一定要进行数据库连接池的配置;
· 既然要进行微服务的编写,那么就一定需要提供有业务接口以及 DAO 实现子类,现在的实现将依靠 MyBatis 完成;
· 所有的微服务最终要通过控制器的 Rest 进行发布处理。
1、 【microboot-shiro-member-provider】配置 Druid 数据库连接池;
· 需要修改 pom.xml 配置文件,为项目的整合添加相关的支持包:
- <dependency>
- <groupId>cn.mldn</groupId>
- <artifactId>microboot-shiro-api</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid</artifactId>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-core</artifactId>
- </dependency>
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- </dependency>
2、 【microboot-shiro-member-provider】建立几个 DAO 接口:
· 提供用户认证的 DAO 接口:IMemberDAO;
- package cn.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/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>
4、 【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 # 等待连接获取的最大超时时间
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 {
- @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;
- }
- }
6、 【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"));
- }
- }
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 {
- @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;
8、 【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.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 {
- @Bean
- public 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 {
- @Resource
- private RestTemplate restTemplate ;
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(
- AuthenticationToken token) throws AuthenticationException {
- System.out.println("============== 1、进行认证操作处理 ==============");
- String mid = token.getPrincipal().toString(); // 用户名
- // 取得用户名之后就需要通过业务层获取用户对象以确定改用户名是否可用
- String url = "http://localhost:8001/member/get?mid=" + mid ;
- Member member = this.restTemplate.postForObject(url, null, Member.class) ; // 通过用户名获取用户信息
- if (member == null) { // 表示该用户信息不存在,不存在则应该抛出一个异常
- throw new UnknownAccountException("搞什么搞,用户名不存在!");
- }
- // 用户名如果存在了,那么就需要确定密码是否正确
- String password = PasswordUtil
- .getPassword(new String((char[]) token.getCredentials()));
- if (!password.equals(member.getPassword())) { // 密码验证
- throw new IncorrectCredentialsException("密码都记不住,去死吧!");
- }
- // 随后还需要考虑用户被锁定的问题
- if (member.getLocked().equals(1)) { // 1表示非0,非0就是true
- throw new LockedAccountException("被锁了,求解锁去吧!");
- }
- // 定义需要进行返回的操作数据信息项,返回的认证信息使用应该是密文
- SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(
- token.getPrincipal(), password, "memberRealm");
- // 在认证完成之后可以直接取得用户所需要的信息内容,保存在Session之中
- SecurityUtils.getSubject().getSession().setAttribute("name", "我的名字");
- return auth;
- }
- @SuppressWarnings("unchecked")
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(
- PrincipalCollection principals) {
- System.out.println("++++++++++++++ 2、进行授权操作处理 ++++++++++++++");
- // 该操作的主要目的是取得授权信息,说的直白一点就是角色和权限数据
- SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
- // 执行到此方法的时候一定是已经进行过用户认证处理了(用户名和密码一定是正确的)
- String mid = (String) principals.getPrimaryPrincipal(); // 取得用户名
- String url = "http://localhost:8001/member/auth?mid=" + mid ;
- Map<String,Object> map = this.restTemplate.postForObject(url, null, Map.class) ;
- Set<String> allRoles = new HashSet<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.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() {// 1、获取配置的Realm,之所以没使用注解配置,是因为此处需要考虑到加密处理
- MemberRealm realm = new MemberRealm();
- realm.setCredentialsMatcher(new CustomerCredentialsMatcher());
- return realm;
- }
- @Bean(name = "lifecycleBeanPostProcessor")
- public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
- return new LifecycleBeanPostProcessor();
- }
- @Bean
- @DependsOn("lifecycleBeanPostProcessor")
- public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
- DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
- daap.setProxyTargetClass(true);
- return daap;
- }
- @Bean
- public EhCacheManager getCacheManager() {// 2、缓存配置
- EhCacheManager cacheManager = new EhCacheManager();
- cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
- return cacheManager;
- }
- @Bean
- public SessionIdGenerator getSessionIdGenerator() { //
- return new JavaUuidSessionIdGenerator();
- }
- @Bean
- public SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { //
- EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
- sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
- sessionDAO.setSessionIdGenerator(sessionIdGenerator);
- return sessionDAO;
- }
- @Bean
- public RememberMeManager getRememberManager() { //
- 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) { //
- 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) {//
- 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>
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>
此时实现了一个最基础的整合处理操作。
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.springframework.dao.DataAccessException;
- import org.springframework.data.redis.connection.RedisConnection;
- import org.springframework.data.redis.core.RedisCallback;
- import org.springframework.data.redis.core.RedisTemplate;
- public class RedisCache<K, V> implements Cache<K, V> {
- private Log log = LogFactory.getLog(RedisCache.class);
- private RedisTemplate<String, Object> redisTempate; // 要提供有Redis处理工具类
- public RedisCache(RedisTemplate<String, Object> redisTempate) {
- this.redisTempate = redisTempate;
- }
- @Override
- public V get(K key) throws CacheException {
- log.info("### get() : K = " + key);
- return (V) this.redisTempate.opsForValue().get(key.toString());
- }
- @Override
- public V put(K key, V value) throws CacheException {
- log.info("### put() : K = " + key + "、V = " + value);
- this.redisTempate.opsForValue().set(key.toString(), value);
- return value;
- }
- @Override
- public V remove(K key) throws CacheException {
- log.info("### remove() : K = " + key);
- V val = this.get(key);
- this.redisTempate.delete(key.toString());
- return val;
- }
- @Override
- public void clear() throws CacheException {
- log.info("### clear()");
- this.redisTempate.execute(new RedisCallback<Boolean>() {
- @Override
- public Boolean doInRedis(RedisConnection connection)
- throws DataAccessException {
- connection.flushDb(); // 清空数据库
- return true;
- }
- });
- }
- @Override
- public int size() {
- log.info("### size()");
- return this.redisTempate.execute(new RedisCallback<Integer>() {
- @Override
- public Integer doInRedis(RedisConnection connection)
- throws DataAccessException {
- return connection.keys("*".getBytes()).size();
- }
- });
- }
- @Override
- public Set<K> keys() {
- log.info("### keys()");
- return this.redisTempate.execute(new RedisCallback<Set<K>>() {
- @Override
- public Set<K> doInRedis(RedisConnection connection)
- throws DataAccessException {
- Set<K> set = new HashSet<K>();
- Set<byte[]> keys = connection.keys("*".getBytes());
- Iterator<byte[]> iter = keys.iterator();
- while (iter.hasNext()) {
- set.add((K) iter.next());
- }
- return set;
- }
- });
- }
- @Override
- public Collection<V> values() {
- log.info("### values()");
- return this.redisTempate.execute(new RedisCallback<Set<V>>() {
- @Override
- public Set<V> doInRedis(RedisConnection connection)
- throws DataAccessException {
- Set<V> set = new HashSet<V>();
- Set<byte[]> keys = connection.keys("*".getBytes());
- Iterator<byte[]> iter = keys.iterator();
- while (iter.hasNext()) {
- set.add((V) connection.get(iter.next()));
- }
- return set;
- }
- });
- }
- }
2、 【microboot-shiro-web】进行 Redis 缓存管理类的配置
- package cn.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;
- }
- }
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;
- // 此时的类将实现SessionDAO的改写
- import org.springframework.data.redis.core.RedisTemplate;
- public class RedisSessionDAO extends EnterpriseCacheSessionDAO {
- private Log log = LogFactory.getLog(RedisSessionDAO.class);
- @Resource
- private RedisTemplate<String, Object> redisTempate; // 要提供有Redis处理工具类
- @Override
- protected Serializable doCreate(Session session) { // 创建Session,返回session id
- log.info("*** doCreate : " + session);
- Serializable sessionId = super.doCreate(session); // 创建sessionid
- // 将当前创建好的Session的数据保存在Redis数据库里面
- this.redisTempate.opsForValue().set(sessionId.toString(), session,
- 1800);
- return sessionId;
- }
- @Override
- protected Session doReadSession(Serializable sessionId) { // 根据session
- // id读取session数据
- log.info("*** doReadSession : " + sessionId);
- Session session = super.doReadSession(sessionId); // 读取Session数据
- if (session == null) { // 现在没有读取到session数据,通过Redis读取
- return (Session) this.redisTempate.opsForValue()
- .get(sessionId.toString());
- }
- return null;
- }
- @Override
- protected void doUpdate(Session session) { // 实现Session更新,每次操作都要更新
- log.info("*** doUpdate : " + session);
- super.doUpdate(session);
- if (session != null) {
- this.redisTempate.opsForValue().set(session.getId().toString(),
- session, 1800);
- }
- }
- @Override
- protected void doDelete(Session session) { // session的删除处理
- log.info("*** doDelete : " + session);
- super.doDelete(session);
- this.redisTempate.delete(session.getId().toString());
- }
- }
4、 【microboot-shiro-web】在当前的项目开发过程之中,配置 Shiro 的 Bean 里面所使用的还是 EHCache 缓存组件,所以需要进 行更换处理。
· 更换现在要使用的 SessionDAO 实现子类:
· 更换使用的缓存组件:
- package cn.mldn.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 cn.mldn.microboot.cache.RedisCacheManager;
- import cn.mldn.microboot.realm.CustomerCredentialsMatcher;
- import cn.mldn.microboot.realm.MemberRealm;
- import cn.mldn.microboot.session.RedisSessionDAO;
- @Configuration
- public class ShiroConfig {
- @Bean
- public MemberRealm getRealm() {// 1、获取配置的Realm,之所以没使用注解配置,是因为此处需要考虑到加密处理
- MemberRealm realm = new MemberRealm();
- realm.setCredentialsMatcher(new CustomerCredentialsMatcher());
- return realm;
- }
- @Bean(name = "lifecycleBeanPostProcessor")
- public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
- return new LifecycleBeanPostProcessor();
- }
- @Bean
- @DependsOn("lifecycleBeanPostProcessor")
- public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
- DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
- daap.setProxyTargetClass(true);
- return daap;
- }
- // @Bean
- // public EhCacheManager getCacheManager() {// 2、缓存配置
- // EhCacheManager cacheManager = new EhCacheManager();
- // cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
- // return cacheManager;
- // }
- @Bean
- public SessionIdGenerator getSessionIdGenerator() { //
- return new JavaUuidSessionIdGenerator();
- }
- //更换现在要使用的 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 }
- @Bean
- public RememberMeManager getRememberManager() { //
- CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
- SimpleCookie cookie = new SimpleCookie("MLDNJAVA-RememberMe");
- cookie.setHttpOnly(true);
- cookie.setMaxAge(3600);
- rememberMeManager.setCookie(cookie);
- return rememberMeManager;
- }
- @Bean
- public QuartzSessionValidationScheduler getQuartzSessionValidationScheduler() {
- QuartzSessionValidationScheduler sessionValidationScheduler = new QuartzSessionValidationScheduler();
- sessionValidationScheduler.setSessionValidationInterval(100000);
- return sessionValidationScheduler;
- }
- 92 @Bean
- 93 public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(
- 94 DefaultWebSecurityManager securityManager) {
- 95 AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
- 96 aasa.setSecurityManager(securityManager);
- 97 return aasa;
- 98 }
- 99
- @Bean
- public DefaultWebSessionManager getSessionManager(SessionDAO sessionDAO,
- QuartzSessionValidationScheduler sessionValidationScheduler) { //
- DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
- sessionManager.setGlobalSessionTimeout(1000000);
- sessionManager.setDeleteInvalidSessions(true);
- sessionManager.setSessionValidationScheduler(sessionValidationScheduler);
- sessionManager.setSessionValidationSchedulerEnabled(true);
- sessionManager.setSessionDAO(sessionDAO);
- SimpleCookie sessionIdCookie = new SimpleCookie("mldn-session-id");
- sessionIdCookie.setHttpOnly(true);
- sessionIdCookie.setMaxAge(-1);
- sessionManager.setSessionIdCookie(sessionIdCookie);
- sessionManager.setSessionIdCookieEnabled(true);
- return sessionManager;
- }
- //更换使用的缓存组件
- 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 }
- 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;
- }
- }
5、 【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
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];
- @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.5、thymeleaf 整合 Shiro 标签
在使用 JSP 的时候可以直接在 JSP 页面之中使用 shiro 标签来判断用户是否登录或者来进行授权检测,但是在 SpringBoot 里面 所使用的页面技术为 thymeleaf,那么如果要想在这样的模版页面之中实现 Shiro 控制,就必须去引入新的依赖包,同时做出一些新 的配置
1、 【microboot-shiro-web】修改 pom.xml 配置文件,追加 thymeleaf 与 shiro 的整合依赖:
- <dependency>
- <groupId>com.github.theborakompanioni</groupId>
- <artifactId>thymeleaf-extras-shiro</artifactId>
- <version>1.2.1</version>
- </dependency>
2、 【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() {// 1、获取配置的Realm,之所以没使用注解配置,是因为此处需要考虑到加密处理
- MemberRealm realm = new MemberRealm();
- realm.setCredentialsMatcher(new CustomerCredentialsMatcher());
- return realm;
- }
- @Bean(name = "lifecycleBeanPostProcessor")
- public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
- return new LifecycleBeanPostProcessor();
- }
- @Bean
- @DependsOn("lifecycleBeanPostProcessor")
- public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
- DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
- daap.setProxyTargetClass(true);
- return daap;
- }
- // @Bean
- // public EhCacheManager getCacheManager() {// 2、缓存配置
- // EhCacheManager cacheManager = new EhCacheManager();
- // cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
- // return cacheManager;
- // }
- @Bean
- public SessionIdGenerator getSessionIdGenerator() { //
- return new JavaUuidSessionIdGenerator();
- }
- @Bean
- public SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { //
- RedisSessionDAO sessionDAO = new RedisSessionDAO(); // 使用Redis进行Session管理
- sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
- sessionDAO.setSessionIdGenerator(sessionIdGenerator);
- return sessionDAO;
- }
- @Bean
- public RememberMeManager getRememberManager() { //
- 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) { //
- 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) {//
- 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;
- }
- }
3、 【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>
4、 【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 整合的时候一定要考虑使用如上的标签进行整体处理。
3、总结
SpringBoot 总结:
· 优点:
|- Rest 支持度高,整体的开发难度相对于 SSM、SSH 整合还是挺简单的;
|- 与各个服务的单一集成很方便,但是如果要进行多集成就非常麻烦了,需要编写各种配置类;
|- thymeleaf 作为一款优秀的页面模版工具,所带来的功能的确强悍,页面开发更简单;
|- 与它想整合的开发框架整合方便;
|- 方便使用 jar 包进行项目部署与发布;
· 缺点:
|- thymeleaf 页面开发要求较高,因为语法严格;
|- 太简单了,让人不适应。
SpringBoot 中的 Rest 就是迈向 SpringCloud 的第一步。
SpringBoot系列十二:SpringBoot整合 Shiro的更多相关文章
- SpringBoot系列(十二)过滤器配置详解
SpringBoot(十二)过滤器详解 往期精彩推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件 ...
- (十二)整合 Shiro 框架,实现用户权限管理
整合 Shiro 框架,实现用户权限管理 1.Shiro简介 1.1 基础概念 1.2 核心角色 1.3 核心理念 2.SpringBoot整合Shiro 2.1 核心依赖 2.2 Shiro核心配置 ...
- springboot系列十、springboot整合redis、多redis数据源配置
一.简介 Redis 的数据库的整合在 java 里面提供的官方工具包:jedis,所以即便你现在使用的是 SpringBoot,那么也继续使用此开发包. 二.redidTemplate操作 在 Sp ...
- SpringBoot系列十:SpringBoot整合Redis
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot 整合 Redis 2.背景 Redis 的数据库的整合在 java 里面提供的官方工具包:jed ...
- springboot系列十二、springboot集成RestTemplate及常见用法
一.背景介绍 在微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端.我们可以使用JDK原生的URLConnection.Apache的Http Client.N ...
- SpringBoot(十二)-- 整合Redis
1.pom依赖 <!-- 添加redis支持 --> <dependency> <groupId>org.springframework.boot</grou ...
- springboot系列总结(二)---springboot的常用注解
上一篇文章我们简单讲了一下@SpringBootApplication这个注解,申明让spring boot自动给程序进行必要的配置,他是一个组合注解,包含了@ComponentScan.@Confi ...
- SpringBoot系列(二)入门知识
SpringBoot系列(二)入门知识 往期推荐 SpringBoot系列(一)idea新建springboot项目 引言 本来新建springboot项目应该放在入门知识这一章的,但是由于新建spr ...
- SpringBoot第十二集:度量指标监控与异步调用(2020最新最易懂)
SpringBoot第十二集:度量指标监控与异步调用(2020最新最易懂) Spring Boot Actuator是spring boot项目一个监控模块,提供了很多原生的端点,包含了对应用系统的自 ...
随机推荐
- Presentational and Container Components
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 There’s a simple pattern I fi ...
- angular-1.3 之ng-model-options指令
ng-model-options是angular-1.3新出的一个指令,这篇文章就来介绍这个指令的用法. ng-model-options允许我们控制ng-model何时进行同步. 比如:1.当某个确 ...
- mysql_secure_installation
安装完mysql-server 会提示可以运行mysql_secure_installation.运行mysql_secure_installation会执行几个设置: a)为root用户设置密码 ...
- vue2.0的contextmenu右键菜单
1.事情对象 <!DOCTYPE html> <html> <head> <title></title> <meta charset= ...
- Python爬取猫眼top100排行榜数据【含多线程】
# -*- coding: utf-8 -*- import requests from multiprocessing import Pool from requests.exceptions im ...
- TCP、UDP数据包大小的限制
版权声明:本文为灿哥哥http://blog.csdn.net/caoshangpa 原创文章,转载请标明出处. https://blog.csdn.net/caoshangpa/article/de ...
- delphi调用webservice (.NET C#版)
uses XMLIntf, XMLDoc; XML to XTR文件转换 .File-->open打开你要分析的XML文件 .在左边选择你要分析的接点,双击加到中间的转换列表中 .Create- ...
- WCF 服务应用程序与 服务库之间的区别
简单理解, WCF服务库,可以认为是一个包含WCF服务以及契约定义的类库.这儿库还不能直接运行,你可以在其他项目里引用,在宿主里启用托管这个库. 而WCF应用程序,是一个可以执行的程序,它有独立的进程 ...
- 机器学习----人脸对齐的算法-ASM.AAM..CLM.SDM
引自:http://blog.csdn.net/linolzhang/article/details/55271815 人脸检测 早已比较成熟,传统的基于HOG+线性分类器 的方案检测效果已经相当不错 ...
- ansible普通用户su切换
[root@361way.com ~]# cat /etc/ansible/hosts [test01] 10.212.52.14 ansible_ssh_user=test ansible_ssh_ ...