--
本文记录了 SpringBoot + Shiro整合,包括 Shiro 登录认证(多个 Realm认证),Session、Cache + Redis共享,RememberMe(记住我)等多项功能。
--
一、Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.made</groupId>
<artifactId>ecifms</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>SpringBootTest</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- oracle -->
<dependency>
<groupId>myOracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0</version>
<type>jar</type>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
<!-- shiro spring. -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<!-- 热启动 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- tomcat 的支持 可以访问跳转页面 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- 支持访问jsp页面 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!--mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!--mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>1.1.4</version>
</dependency>
<!--pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 热部署 热启动 -->
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
<!--私有仓库,即私服 -->
<repositories>
<repository>
<id>public</id><!-- 这个id需要与你的组的group ID一致 -->
<name>Public Repository</name>
<url>http://10.1.164.8:15000/nexus/content/groups/public</url>
</repository>
</repositories>
</project>
二、Redis配置
1. application.properties
#配置哨兵
#spring.redis.database=0
#spring.redis.password=made
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
#哨兵监听redis server名称
spring.redis.sentinel.master=mymaster
#哨兵的配置列表
spring.redis.sentinel.nodes=10.1.164.11:26379,10.1.164.12:26379
2. RedisConfig.java
package com.made.common.config;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisSentinelPool;
@Configuration
public class RedisConfig {
//注入集群节点的信息
/*@Value("${spring.redis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.pool.max-active}")
private String maxActive;
@Value("${spring.redis.pool.max-wait}")
private long maxWait;*/
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.sentinel.master}")
private String master;
@Value("${spring.redis.sentinel.nodes}")
private String nodes;
@Bean //<bean>
public JedisSentinelPool getJedisSentinelPool(){
//分割集群节点
String[] cNodes = nodes.split(",");
//创建set集合
Set<String> nodes = new HashSet<String>();
//循环集群节点对象
for (String node : cNodes) {
//String[] hp = node.split(":");
nodes.add(node);
System.out.println(node);
}
/*JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxWaitMillis(maxWait);*/
//JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, nodes, poolConfig, password);
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(master, nodes, password);
return jedisSentinelPool;
}
}
3. RedisService.java
package com.made.common.redis;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import com.made.common.CommonDefinitionUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
@Component
public class RedisService {
private static Logger logger = LoggerFactory.getLogger(RedisService.class);
@Autowired
private JedisSentinelPool jedisPool;
/**
* get value from redis
* @param key
* @return
*/
public byte[] get(byte[] key){
byte[] value = null;
Jedis jedis = jedisPool.getResource();
try{
value = jedis.get(key);
} catch(Exception e) {
logger.error("redis key:{} get value occur exception", new String(key));
} finally{
jedis.close();
}
return value;
}
/**
* set
* @param key
* @param value
* @return
*/
public byte[] set(byte[] key,byte[] value){
Jedis jedis = jedisPool.getResource();
try{
jedis.set(key,value);
jedis.expire(key, (int) CommonDefinitionUtil.SHIRO_SESSION_TIME);
} catch(Exception e) {
logger.error("redis key:{} set value:{} occur exception", new String(key), new String(value));
} finally{
jedis.close();
}
return value;
}
/**
* set
* @param key
* @param value
* @param expire
* @return
*/
public byte[] set(byte[] key,byte[] value,int expire){
Jedis jedis = jedisPool.getResource();
try{
jedis.set(key,value);
if(expire != 0){
jedis.expire(key, expire);
}
} catch(Exception e) {
logger.error("redis key:{} set value:{} in expire:{} occur exception",
new String(key), new String(value), expire);
} finally{
jedis.close();
}
return value;
}
/**
* del
* @param key
*/
public void del(byte[] key){
Jedis jedis = jedisPool.getResource();
try{
jedis.del(key);
} catch(Exception e) {
logger.error("redis key:{} del value occur exception", new String(key));
} finally{
jedis.close();
}
}
/**
* flush
*/
public void flushDB(){
Jedis jedis = jedisPool.getResource();
try{
jedis.flushDB();
} catch(Exception e) {
logger.error("redis flushDB occur exception");
} finally{
jedis.close();
}
}
/**
* size
*/
public Long dbSize(){
Long dbSize = 0L;
Jedis jedis = jedisPool.getResource();
try{
dbSize = jedis.dbSize();
} catch(Exception e) {
logger.error("redis get dbSize occur exception");
} finally{
jedis.close();
}
return dbSize;
}
/**
* keys
* @param regex
* @return
*/
public Set<byte[]> keys(String pattern){
Set<byte[]> keys = null;
Jedis jedis = jedisPool.getResource();
try{
keys = jedis.keys(pattern.getBytes());
} catch(Exception e) {
logger.error("redis get keys in pattern:{} occur exception", pattern);
} finally{
jedis.close();
}
return keys;
}
/**
* 判断key是否存在
*/
public Boolean exists(String key) {
Jedis jedis = jedisPool.getResource();
Boolean result = jedis.exists(key);
jedis.close();
return result;
}
/**
* 设置失效时间
*/
public Long expire(String key, int seconds) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.expire(key, seconds);
jedis.close();
return result;
}
/**
* key-value 存数据到redis
*/
public String set(String key, String value) {
Jedis jedis = jedisPool.getResource();
String result = jedis.set(key, value);
jedis.close();
return result;
}
/**
* 根据key,获取值
*/
public String get(String key) {
Jedis jedis = jedisPool.getResource();
String result = jedis.get(key);
jedis.close();
return result;
}
/**
* 根据key,删除数据
*/
/*public Long del(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.del(key);
jedis.close();
return result;
}*/
}
三、Realm认证(多个Realm认证)
1. UsernamePasswordTokenCustom.java
首先需要继承 UsernamePasswordToken类,新增一个属性,用来标识登录类型。
package com.made.common.shiro.token;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* 自定义 UsernamePasswordToken
* @author majunde
*
*/
public class UsernamePasswordTokenCustom extends UsernamePasswordToken{
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 登录类型: 普通登录,SSO登录
*/
private String loginType;
/**
* 构造函数
* @param username
* @param password
* @param loginType 登录类型
*/
public UsernamePasswordTokenCustom(String username, String password, String loginType) {
super(username, password);
this.loginType = loginType;
}
public UsernamePasswordTokenCustom(String username, String password, String loginType, boolean rememberMe) {
super(username, password, rememberMe);
// TODO Auto-generated constructor stub
this.loginType = loginType;
}
public UsernamePasswordTokenCustom(String username, String password, boolean rememberMe) {
super(username, password, rememberMe);
// TODO Auto-generated constructor stub
}
public UsernamePasswordTokenCustom(String username, String password) {
super(username, password);
// TODO Auto-generated constructor stub
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
2. ModularRealmAuthenticatorCustom.java
其次,还需要继承与ModularRealmAuthenticator,重写其中的
doAuthenticate(AuthenticationToken authenticationToken)方法。
package com.made.common.shiro.realm;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import com.made.common.shiro.token.UsernamePasswordTokenCustom;
/**
* 自定义 ModularRealmAuthenticator
* 由该类 决定 分配调用 那个 realm, 默认全部调用
* @author majunde
*
*/
public class ModularRealmAuthenticatorCustom extends ModularRealmAuthenticator{
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
throws AuthenticationException {
// TODO Auto-generated method stub
// 判断 getRealms(); 是否为空
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
// 获取 token 得到 loginType
UsernamePasswordTokenCustom token = (UsernamePasswordTokenCustom) authenticationToken;
String loginType = token.getLoginType();
Collection<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
// 如果 loginTyppe为空,代表默认
if (loginType == null) {
if (realm.getName().contains("Default")) {
typeRealms.add(realm);
break;
}
continue;
}
if (realm.getName().contains(loginType)) {
typeRealms.add(realm);
break;
}
}
if (typeRealms.size() == 1) {
return doSingleRealmAuthentication(typeRealms.iterator().next(), authenticationToken);
}
// 改变 realms --> typeRealms
return doMultiRealmAuthentication(typeRealms, authenticationToken);
}
}
3.LoginTypeEnum.java
另外,为了标识 登录类型,使用了枚举来记录用户的登录类型。
package com.made.common.shiro.token;
/**
* 登录类型
* @author majunde
*
*/
public enum LoginTypeEnum {
/**
* 默认登录
*/
Default("Default"),
/**
* SSO 登录
*/
Sso("Sso");
private final String value;
private LoginTypeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}