1、POM 依赖
spring boot 版本 2.5.1
shiro 依赖
<spring-shiro.version>1.6.0</spring-shiro.version>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${spring-shiro.version}</version>
</dependency>
jwt 依赖
<jwt.version>3.3.0</jwt.version>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
redis 依赖,使用 redis 存储 token
<jedis.version>2.9.0</jedis.version>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
2、基础配置
公钥私钥可以用 ssh 生成,我将 token 存储到了 redis,所以这里配置了 redis
rsaPrivateKey=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIxgS3b5J9IRYlvEFIEeDeQCGkOI5pT+Nl3wNe0fdWIiw36g4sH1l2sLuTZ6bew9YRLIapude6ORGZF5UfNy9cor3N7n3ew/fXCEKVRC6Kg+cREm1rqyMjDc9NtfksXG4RGf7GNeoTmUDVStsnXvoLnzvrE7FvbB12XKfhTQDnSZAgMBAAECgYBcyET45SP5x/2/87EtymSaAP3FB5aIgiIDIwMxsKpQa/PVHZfjZWVonn4T0QYYsFUaKhe0tXmEGiLRMWQGSkTEGfZ5l7uRmrNZ0Nk9asu4/fyjwZNHYDDGAELU5R4WgLvO09PdVLG/uyIxXh9qg9y9OpYM4KoATnsH7t7TPdI5gQJBAMnB6nz18BKSsHX7qDkWWpxZUOZmcKIsZoaDlz1NkoAKrOuH8TYc75uwLhR17nOOI9kO+10FKlYQ/5+yUiQIE+kCQQCyHcU/0mlvvByrJsYJMDUMtDk5/BtCWD6UZan/X1GH2EHx1W5LuL+wyIr6CUrY5G3osVU5ZvLly4zTFRQHr10xAkEAqFJT4zT7uUMQXR47VoVDyzTovY+xYFtSnd6jCs3w70n4wfeEUfUKIgV2LDPHYDixx6EsLIrmqy87VGxdAxqKIQJAe2bOyvnfXK9KeXWCjMkeZ+/RGhBFXoC+0pdg4PHMDb7RaVgCc2nLPRKj3rljZsNUNnvt3LgrnvOYXIHk/7IKcQJAGakaB+211CbKnIHtjxSsg07EiM3dZVA1jrXTbJ6NuhURZTIOR0YJsVdWoEbwBLv/STTvc7+G+wo1yuzzBAse+w==
rsaPublicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMYEt2+SfSEWJbxBSBHg3kAhpDiOaU/jZd8DXtH3ViIsN+oOLB9ZdrC7k2em3sPWESyGqbnXujkRmReVHzcvXKK9ze593sP31whClUQuioPnERJta6sjIw3PTbX5LFxuERn+xjXqE5lA1UrbJ176C5876xOxb2wddlyn4U0A50mQIDAQAB
# AES密码加密私钥(Base64加密)
encryptAESKey=V2FuZzkyNjQ1NGRTQkFQSUpXVA==
# JWT认证加密私钥(Base64加密)
encryptJWTKey=U0JBUElKV1RkV2FuZzkyNjQ1NA==
# AccessToken过期时间-5分钟-5*60(秒为单位)
accessTokenExpireTime=300
# RefreshToken过期时间-30分钟-30*60(秒为单位)
refreshTokenExpireTime=1800
# Shiro缓存过期时间-5分钟-5*60(秒为单位)(一般设置与AccessToken过期时间一致)
shiroCacheExpireTime=10
3、JWT 工具类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.kmair.ky.contract.common.entity.Constant;
import com.kmair.ky.contract.utils.security.Base64ConvertUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.util.Date;
/**
* JAVA-JWT工具类
* @author Wang926454
* @date 2018/8/30 11:45
*/
@Component
public class JwtUtil {
/**
* logger
*/
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
/**
* 过期时间改为从配置文件获取
*/
private static String accessTokenExpireTime;
/**
* JWT认证加密私钥(Base64加密)
*/
private static String encryptJWTKey;
@Value("${accessTokenExpireTime}")
public void setAccessTokenExpireTime(String accessTokenExpireTime) {
JwtUtil.accessTokenExpireTime = accessTokenExpireTime;
}
@Value("${encryptJWTKey}")
public void setEncryptJWTKey(String encryptJWTKey) {
JwtUtil.encryptJWTKey = encryptJWTKey;
}
/**
* 校验token是否正确
* @param token Token
* @return boolean 是否正确
* @author Wang926454
* @date 2018/8/31 9:05
*/
public static boolean verify(String token) {
try {
// 帐号加JWT私钥解密
String secret = getClaim(token, Constant.ACCOUNT) + Base64ConvertUtil.decode(encryptJWTKey);
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(token);
return true;
} catch (UnsupportedEncodingException e) {
logger.error("JWTToken认证解密出现UnsupportedEncodingException异常:{}", e.getMessage());
}
return false;
}
/**
* 获得Token中的信息无需secret解密也能获得
* @param token
* @param claim
* @return java.lang.String
* @author Wang926454
* @date 2018/9/7 16:54
*/
public static String getClaim(String token, String claim) {
try {
DecodedJWT jwt = JWT.decode(token);
// 只能输出String类型,如果是其他类型返回null
return jwt.getClaim(claim).asString();
} catch (JWTDecodeException e) {
logger.error("解密Token中的公共信息出现JWTDecodeException异常:{}", e.getMessage());
e.printStackTrace();
}
return null;
}
/**
* 生成签名
* @param account 帐号
* @return java.lang.String 返回加密的Token
* @author Wang926454
* @date 2018/8/31 9:07
*/
public static String sign(String account, String currentTimeMillis) {
try {
// 帐号加JWT私钥加密
String secret = account + Base64ConvertUtil.decode(encryptJWTKey);
// 此处过期时间是以毫秒为单位,所以乘以1000
Date date = new Date(System.currentTimeMillis() + Long.parseLong(accessTokenExpireTime) * 1000);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带account帐号信息
return JWT.create()
.withClaim("account", account)
.withClaim("currentTimeMillis", currentTimeMillis)
.withExpiresAt(date)
.sign(algorithm);
} catch (UnsupportedEncodingException e) {
logger.error("JWTToken加密出现UnsupportedEncodingException异常:{}", e.getMessage());
e.printStackTrace();
}
return null;
}
}
4、redis 工具类
序列化工具
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
/**
* Serializable工具(JDK)(也可以使用Protobuf自行百度)
* @author dolyw.com
* @date 2018/9/4 15:13
*/
public class SerializableUtil {
private SerializableUtil() {}
/**
* logger
*/
private static final Logger logger = LoggerFactory.getLogger(SerializableUtil.class);
/**
* 序列化
* @param object
* @return byte[]
* @author dolyw.com
* @date 2018/9/4 15:14
*/
public static byte[] serializable(Object object) {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
} catch (IOException e) {
logger.error("SerializableUtil工具类序列化出现IOException异常:{}", e.getMessage());
e.printStackTrace();
} finally {
try {
if (oos != null) {
oos.close();
}
if (baos != null) {
baos.close();
}
} catch (IOException e) {
logger.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
e.printStackTrace();
}
}
return null;
}
/**
* 反序列化
* @param bytes
* @return java.lang.Object
* @author dolyw.com
* @date 2018/9/4 15:14
*/
public static Object unserializable(byte[] bytes) {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (ClassNotFoundException e) {
logger.error("SerializableUtil工具类反序列化出现ClassNotFoundException异常:{}", e.getMessage());
} catch (IOException e) {
logger.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
} finally {
try {
if (ois != null) {
ois.close();
}
if (bais != null) {
bais.close();
}
} catch (IOException e) {
logger.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
}
}
return null;
}
}
字符串工具类
5、配置信息读取
import lombok.Data;
/**
* 系统属性
* @author Mr.Wen
*/
@Data
public class SysProperties {
private String rsaPublicKey;
private String rsaPrivateKey;
private String encryptAESKey;
private String encryptJWTKey;
private int accessTokenExpireTime;
private int refreshTokenExpireTime;
private int shiroCacheExpireTime;
private String fileCachePath;
}
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* 系统配置
* @author Mr.Wen
*/
@Data
@EnableAutoConfiguration
@Configuration
@PropertySource("classpath:config.properties")
public class SysConfig {
@Value("${rsaPublicKey}")
private String rsaPublicKey;
@Value("${rsaPrivateKey}")
private String rsaPrivateKey;
@Value("${encryptAESKey}")
private String encryptAESKey;
@Value("${encryptJWTKey}")
private String encryptJWTKey;
@Value("${accessTokenExpireTime}")
private int accessTokenExpireTime;
@Value("${refreshTokenExpireTime}")
private int refreshTokenExpireTime;
@Value("${shiroCacheExpireTime}")
private int shiroCacheExpireTime;
@Value("${fileCachePath}")
private String fileCachePath;
@Bean(value = "sysProperties",name = "sysProperties")
public SysProperties init(){
SysProperties sysProperties = new SysProperties();
sysProperties.setRsaPublicKey(rsaPublicKey);
sysProperties.setRsaPrivateKey(rsaPrivateKey);
sysProperties.setEncryptAESKey(this.encryptAESKey);
sysProperties.setEncryptJWTKey(this.encryptJWTKey);
sysProperties.setAccessTokenExpireTime(this.accessTokenExpireTime);
sysProperties.setRefreshTokenExpireTime(this.refreshTokenExpireTime);
sysProperties.setShiroCacheExpireTime(this.shiroCacheExpireTime);
sysProperties.setFileCachePath(fileCachePath);
return sysProperties;
}
}
6、常量类
/**
* 常量
* @author dolyw.com
* @date 2018/9/3 16:03
*/
public class Constant {
private Constant() {}
/**
* redis-OK
*/
public static final String OK = "OK";
/**
* redis过期时间,以秒为单位,一分钟
*/
public static final int EXRP_MINUTE = 60;
/**
* redis过期时间,以秒为单位,一小时
*/
public static final int EXRP_HOUR = 60 * 60;
/**
* redis过期时间,以秒为单位,一天
*/
public static final int EXRP_DAY = 60 * 60 * 24;
/**
* redis-key-前缀-shiro:cache:
*/
public static final String PREFIX_SHIRO_CACHE = "shiro:cache:";
/**
* redis-key-前缀-shiro:access_token:
*/
public static final String PREFIX_SHIRO_ACCESS_TOKEN = "shiro:access_token:";
/**
* redis-key-前缀-shiro:refresh_token_prefix:
*/
public static final String PREFIX_SHIRO_USER = "shiro:user_prefix:";
/**
* redis-key-前缀-shiro:refresh_token:
*/
public static final String PREFIX_SHIRO_REFRESH_TOKEN = "shiro:refresh_token:";
/**
* JWT-account:
*/
public static final String ACCOUNT = "account";
/**
* JWT-currentTimeMillis:
*/
public static final String CURRENT_TIME_MILLIS = "currentTimeMillis";
/**
* PASSWORD_MAX_LEN
*/
public static final Integer PASSWORD_MAX_LEN = 8;
/**
* 资源目录菜单标识
*/
public static final String MENU_CODE = "menu";
}
7、shiro 配置
7.1、权限管理器
shiro 的权限管理器,可以继承 AuthorizingRealm,重写授权和认证的方法
UserDataServiceImpl 是用户信息查询接口,SysRoleServiceImpl 是系统角色查询接口,UserData 就是用户信息存储,SysRole 就是系统角色,因为各个系统对此设计不一,就不贴这几个的代码了,具体实现参考关键思路即可
import com.kmair.ky.contract.common.entity.Constant;
import com.kmair.ky.contract.config.jwt.JwtToken;
import com.kmair.ky.contract.system.user.entity.SysRole;
import com.kmair.ky.contract.system.user.entity.UserData;
import com.kmair.ky.contract.system.user.service.impl.SysRoleServiceImpl;
import com.kmair.ky.contract.system.user.service.impl.UserDataServiceImpl;
import com.kmair.ky.contract.utils.auth.JwtUtil;
import com.kmair.ky.contract.utils.cache.JedisUtil;
import com.kmair.ky.contract.utils.common.StringUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* shiro权限管理器
* @author Mr.Wen
*/
public class SysShiroRealm extends AuthorizingRealm {
@Resource
private UserDataServiceImpl userDataService;
@Resource
private SysRoleServiceImpl sysRoleService;
@Override
public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof JwtToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String workId = (String) principalCollection.getPrimaryPrincipal();
//查询用户名称
UserData user = userDataService.getUserByWorkId(workId);
//根据用户名查询角色
List<SysRole> sysRoleList = sysRoleService.selectByUserId(user.getId());
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (SysRole role : sysRoleList) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleCode());
}
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String token = (String) authenticationToken.getCredentials();
// 解密获得account,用于和数据库进行对比
String account = JwtUtil.getClaim(token, Constant.ACCOUNT);
// 帐号为空
if (StringUtil.isBlank(account)) {
throw new AuthenticationException("Token中帐号为空(The account in Token is empty.)");
}
// 查询用户是否存在
UserData userData = userDataService.getUserByWorkId(account);
if (userData == null) {
throw new AuthenticationException("该帐号不存在(The account does not exist.)");
}
// 验证token和refreshToken
Boolean exists = JedisUtil.exists(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account);
if (JwtUtil.verify(token) && exists != null && exists) {
return new SimpleAuthenticationInfo(token, token, "userRealm");
}
throw new AuthenticationException("Token已过期(Token expired or incorrect.)");
}
}
7.2、认证过滤器
实现 AuthenticationToken 接口,定义一个自己的 token
import lombok.Data;
import org.apache.shiro.authc.AuthenticationToken;
/**
* @author Mr.Wen
* token信息
*/
@Data
public class JwtToken implements AuthenticationToken {
/**
* Token
*/
private String token;
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
定义过滤器
代码中的 HttpResult 是我自定义的 http 请求返回的信息,换成自己的即可,这里的 token 过期是通过 redis 的数据过期来实现的,一共有两个 token,一个是 refreshToken,另一个是 accessToken,accessToken 过期,则判断 refreshToken 是否过期,没过期,则重新颁发一个 accessToken,否则让用户重新登录
/**
* @author Mr.Wen
* 过滤器拦截token请求
*/
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {
/**
* logger
*/
private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (this.isLoginAttempt(request, response)) {
try {
this.executeLogin(request, response);
} catch (Exception e) {
String msg = e.getMessage();
Throwable throwable = e.getCause();
if (throwable instanceof SignatureVerificationException) {
msg = "Token或者密钥不正确(" + throwable.getMessage() + ")";
logger.info(msg);
} else if (throwable instanceof TokenExpiredException) {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
String accessToken = httpRequest.getHeader("Authorization");
String username = JwtUtil.getClaim(accessToken, Constant.ACCOUNT);
Boolean exist = JedisUtil.exists(Constant.PREFIX_SHIRO_REFRESH_TOKEN + username);
if(exist != null && exist){
return refreshToken(request,response);
}else{
this.response(4011,response,"token过期,需要重新登录");
logger.info("token过期,需要重新登录",throwable);
}
return false;
} else {
if (throwable != null) {
this.response(500,response,throwable.getMessage());
logger.error("服务器错误",throwable);
}
}
return false;
}
} else {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
String httpMethod = httpServletRequest.getMethod();
String requestUrl = httpServletRequest.getRequestURI();
logger.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestUrl, httpMethod);
this.response(HttpStatus.UNAUTHORIZED.value(),response, "请先登录");
return false;
}
return true;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
this.sendChallenge(request, response);
return false;
}
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
String token = this.getAuthzHeader(request);
return token != null;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
JwtToken token = new JwtToken(this.getAuthzHeader(request));
this.getSubject(request, response).login(token);
return true;
}
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
return super.preHandle(request, response);
}
private boolean refreshToken(ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
String refreshToken = httpServletRequest.getHeader("refreshToken");
String username = JwtUtil.getClaim(refreshToken, Constant.ACCOUNT);
if(username == null){
return false;
}
username = username.replace(Constant.PREFIX_SHIRO_USER,"");
Boolean exists = JedisUtil.exists(Constant.PREFIX_SHIRO_REFRESH_TOKEN + username);
if (exists != null && exists) {
//重新签发access_token
String currentTimeMillis = String.valueOf(System.currentTimeMillis());
String accessToken = JwtUtil.sign(username, currentTimeMillis);
JwtToken jwtToken = new JwtToken(accessToken);
SecurityUtils.getSubject().login(jwtToken);
SysProperties sysProperties = SpringUtil.getBean("sysProperties");
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setHeader("NEW_ACCESS_TOKEN", accessToken);
httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
JedisUtil.setObject(Constant.PREFIX_SHIRO_ACCESS_TOKEN + currentTimeMillis, sysProperties.getAccessTokenExpireTime());
return true;
}
return false;
}
private void response(int code,ServletResponse response, String msg) {
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setStatus(HttpStatus.OK.value());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
try (PrintWriter out = httpServletResponse.getWriter()) {
out.append(JSON.toJSONString(HttpResult.customCode(code,msg,null)));
} catch (IOException e) {
logger.error("直接返回Response信息出现IOException异常:{}", e.getMessage());
}
}
}
7.3、shiro 缓存处理
我们需要将 shiro 的数据存储到 redis,要关闭 shiro 自带的缓存,自定义一个缓存管理器
import com.kmair.ky.contract.common.entity.Constant;
import com.kmair.ky.contract.config.common.SysProperties;
import com.kmair.ky.contract.utils.auth.JwtUtil;
import com.kmair.ky.contract.utils.cache.JedisUtil;
import com.kmair.ky.contract.utils.common.SerializableUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import javax.annotation.Resource;
import java.util.*;
/**
* @author Mr.Wen
* @param <K> 键
* @param <V> 值
*/
@SuppressWarnings("unchecked")
public class CustomCache<K,V> implements Cache<K,V> {
@Resource
private SysProperties sysProperties;
private String getKey(Object key) {
return Constant.PREFIX_SHIRO_CACHE + JwtUtil.getClaim(key.toString(), Constant.ACCOUNT);
}
/**
* 获取缓存
*/
@Override
public Object get(Object key) throws CacheException {
if(Boolean.FALSE.equals(JedisUtil.exists(this.getKey(key)))){
return null;
}
return JedisUtil.getObject(this.getKey(key));
}
/**
* 保存缓存
*/
@Override
public Object put(Object key, Object value) throws CacheException {
// 读取配置文件,获取Redis的Shiro缓存过期时间
int shiroCacheExpireTime = sysProperties.getShiroCacheExpireTime();
// 设置Redis的Shiro缓存
return JedisUtil.setObject(this.getKey(key), value, shiroCacheExpireTime);
}
/**
* 移除缓存
*/
@Override
public Object remove(Object key) throws CacheException {
if(Boolean.FALSE.equals(JedisUtil.exists(this.getKey(key)))){
return null;
}
JedisUtil.delKey(this.getKey(key));
return null;
}
/**
* 清空所有缓存
*/
@Override
public void clear() throws CacheException {
Objects.requireNonNull(JedisUtil.getJedis()).flushDB();
}
/**
* 缓存的个数
*/
@Override
public int size() {
Long size = Objects.requireNonNull(JedisUtil.getJedis()).dbSize();
return size.intValue();
}
/**
* 获取所有的key
*/
@Override
public Set keys() {
Set<byte[]> keys = Objects.requireNonNull(JedisUtil.getJedis()).keys("*".getBytes());
Set<Object> set = new HashSet<>();
for (byte[] bs : keys) {
set.add(SerializableUtil.unserializable(bs));
}
return set;
}
/**
* 获取所有的value
*/
@Override
public Collection values() {
Set keys = this.keys();
List<Object> values = new ArrayList<>();
for (Object key : keys) {
values.add(JedisUtil.getObject(this.getKey(key)));
}
return values;
}
}
缓存管理器,定义缓存操作
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
/**
* 自定义shiro缓存管理器,使用redis存储token信息
*/
public class CustomCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return new CustomCache<K,V>();
}
}
7.4、shiro 配置
配置自己的授权认证器、token 过滤器,缓存到 shiro 中,并设置放行的请求
import com.kmair.ky.contract.config.jwt.JwtFilter;
import com.kmair.ky.contract.config.shiro.config.CustomCacheManager;
import com.kmair.ky.contract.system.login.service.impl.SysShiroRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* shiro配置类
* @author Mr.Wen
*/
@Configuration
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAap = new DefaultAdvisorAutoProxyCreator();
defaultAap.setProxyTargetClass(true);
return defaultAap;
}
@Bean
public AuthorizingRealm sysShiroRealm() {
return new SysShiroRealm();
}
/**
* 权限管理,配置主要是Realm的管理认证
* 需要使用redis存储认证信息,所以,关闭session,重写缓存管理器
* @return 安全管理器
*/
@Bean
public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(sysShiroRealm());
// 关闭Shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setSubjectDAO(subjectDAO);
// 设置自定义Cache缓存
defaultWebSecurityManager.setCacheManager(new CustomCacheManager());
return securityManager;
}
/**
* Filter工厂,设置对应的过滤条件和跳转条件
* @param securityManager 安全管理器
* @return 过滤组件
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filterMap = new HashMap<>(16);
filterMap.put("jwtFilter", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
Map<String, String> map = new HashMap<>();
//登出
map.put("/logout", "logout");
//swagger放行
map.put("/swagger-ui.html", "anon");
map.put("/swagger-resources/**", "anon");
map.put("/v2/api-docs", "anon");
map.put("/webjars/**", "anon");
//登录
map.put("/user/loginByUsernameAndPassword", "anon");
map.put("/user/refreshToken", "anon");
map.put("/**","jwtFilter");
shiroFilterFactoryBean.setLoginUrl("/login/loginPage");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于