代码地址https://download.csdn.net/download/guan1843036360/86746065
在上面我们自定了自己的realm,但是我们发现
在认证和授权的时候,程序需要频繁的访问数据库,这样对于数据库的压力可想而知,那我们怎么处理呢?
此时我们对UserBridgeServiceImpl的实现类里面的逻辑加入了自定义的SimpleCacheService缓存服务接口,简单来说实现了在认证和鉴权时不需要每次都去查询数据库,而是把认证和鉴权信息放入到redis缓存中,以减低数据库的访问压力
1、集成redis服务器,作为集中存储认证和鉴权信息
2、改写UserBridgeServiceImpl使其优先从缓存中读取
此类主要负责yaml文件的配置类
package com.itheima.shiro.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
/**
* @Description redis配置文件
*/
@Data
@ConfigurationProperties(prefix = "itheima.framework.shiro.redis")
public class ShiroRedisProperties implements Serializable {
/**
* redis连接地址
*/
private String nodes ;
/**
* 获取连接超时时间
*/
private int connectTimeout ;
/**
* 连接池大小
*/
private int connectPoolSize;
/**
* 初始化连接数
*/
private int connectionMinimumidleSize ;
/**
* 等待数据返回超时时间
*/
private int timeout ;
/**
* 全局超时时间
*/
private long globalSessionTimeout;
}
集成redisson的相关配置,同时启用ShiroRedisProperties的配置
package com.itheima.shiro.config;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.impl.ShiroDbRealmImpl;
import com.itheima.shiro.filter.RolesOrAuthorizationFilter;
import com.itheima.shiro.properties.PropertiesUtil;
import lombok.extern.log4j.Log4j2;
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.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description 权限配置类
*/
@Configuration
@ComponentScan(basePackages = "com.itheima.shiro.core")
@EnableConfigurationProperties({ShiroRedisProperties.class})
@Log4j2
public class ShiroConfig {
@Autowired
private ShiroRedisProperties shiroRedisProperties;
/**
* @Description redission客户端
*/
@Bean("redissonClientForShiro")
public RedissonClient redissonClient() {
log.info("=====初始化redissonClientForShiro开始======");
String[] nodeList = shiroRedisProperties.getNodes().split(",");
Config config = new Config();
if (nodeList.length == 1) {
config.useSingleServer().setAddress(nodeList[0])
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
} else {
config.useClusterServers().addNodeAddress(nodeList)
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setMasterConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setMasterConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
}
RedissonClient redissonClient = Redisson.create(config);
log.info("=====初始化redissonClientForShiro完成======");
return redissonClient;
}
/**
* @Description 创建cookie对象
*/
@Bean(name="sessionIdCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
/**
* @Description 权限管理器
* @param
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroDbRealm());
securityManager.setSessionManager(shiroSessionManager());
return securityManager;
}
/**
* @Description 自定义RealmImpl
*/
@Bean(name="shiroDbRealm")
public ShiroDbRealm shiroDbRealm(){
return new ShiroDbRealmImpl();
}
/**
* @Description 会话管理器
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager shiroSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(simpleCookie());
sessionManager.setGlobalSessionTimeout(3600000);
return sessionManager;
}
/**
* @Description 保证实现了Shiro内部lifecycle函数的bean执行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* @Description AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(defaultWebSecurityManager());
return new AuthorizationAttributeSourceAdvisor();
}
/**
* @Description 过滤器链
*/
private Map<String, String> filterChainDefinition(){
List<Object> list = PropertiesUtil.propertiesShiro.getKeyList();
Map<String, String> map = new LinkedHashMap<>();
for (Object object : list) {
String key = object.toString();
String value = PropertiesUtil.getShiroValue(key);
log.info("读取防止盗链控制:---key{},---value:{}",key,value);
map.put(key, value);
}
return map;
}
/**
* @Description 自定义过滤器定义
*/
private Map<String, Filter> filters() {
Map<String, Filter> map = new HashMap<String, Filter>();
map.put("roleOr", new RolesOrAuthorizationFilter());
return map;
}
/**
* @Description Shiro过滤器
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(defaultWebSecurityManager());
//使自定义过滤器生效
shiroFilter.setFilters(filters());
shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
shiroFilter.setLoginUrl("/login");
shiroFilter.setUnauthorizedUrl("/login");
return shiroFilter;
}
}
package com.itheima.shiro.core.base;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* @Description 缓存实现类, 实现序列 接口方便对象存储于第三方容器(Map存放键值对)
*/
public class SimpleMapCache implements Cache<Object, Object>, Serializable {
private final Map<Object, Object> attributes;
private final String name;
public SimpleMapCache(String name, Map<Object, Object> backingMap) {
if (name == null)
throw new IllegalArgumentException("Cache name cannot be null.");
if (backingMap == null) {
throw new IllegalArgumentException("Backing map cannot be null.");
} else {
this.name = name;
attributes = backingMap;
}
}
public Object get(Object key) throws CacheException {
return attributes.get(key);
}
public Object put(Object key, Object value) throws CacheException {
return attributes.put(key, value);
}
public Object remove(Object key) throws CacheException {
return attributes.remove(key);
}
public void clear() throws CacheException {
attributes.clear();
}
public int size() {
return attributes.size();
}
public Set<Object> keys() {
Set<Object> keys = attributes.keySet();
if (!keys.isEmpty())
return Collections.unmodifiableSet(keys);
else
return Collections.emptySet();
}
public Collection<Object> values() {
Collection<Object> values = attributes.values();
if (!EmptyUtil.isNullOrEmpty(values))
return Collections.unmodifiableCollection(values);
else
return Collections.emptySet();
}
@Override
public String toString() {
return "SimpleMapCache [attributes=" + attributes + ", name=" + name
+ ", keys()=" + keys() + ", size()=" + size() + ", values()="
+ values() + "]";
}
}
package com.itheima.shiro.utils;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.codec.Base64;
import java.io.*;
/**
* @Description:实现shiro会话的序列化存储
*/
@Log4j2
public class ShiroRedissionSerialize {
public static Object deserialize(String str) {
if (EmptyUtil.isNullOrEmpty(str)) {
return null;
}
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
Object object=null;
try {
bis = new ByteArrayInputStream(EncodesUtil.decodeBase64(str));
ois = new ObjectInputStream(bis);
object = ois.readObject();
} catch (IOException |ClassNotFoundException e) {
log.error("流读取异常:{}",e);
} finally {
try {
bis.close();
ois.close();
} catch (IOException e) {
log.error("流读取异常:{}",e);
}
}
return object;
}
public static String serialize(Object obj) {
if (EmptyUtil.isNullOrEmpty(obj)) {
return null;
}
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
String base64String = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
base64String = EncodesUtil.encodeBase64(bos.toByteArray());
} catch (IOException e) {
log.error("流写入异常:{}",e);
} finally {
try {
bos.close();
oos.close();
} catch (IOException e) {
log.error("流写入异常:{}",e);
}
}
return base64String;
}
}
SimpleCacheService
package com.itheima.shiro.core;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
/**
* @Description 简单的缓存管理接口
*/
public interface SimpleCacheService {
/**
* 功能说明::新增缓存堆到管理器
*/
void createCache(String cacheName, Cache<Object, Object> cache) throws CacheException;
/**
* 方法名::getCache
* 功能说明::获取缓存堆
*/
Cache<Object, Object> getCache(String cacheName) throws CacheException;
/**
* 方法名::removeCache
* 功能说明::移除缓存堆
*/
void removeCache(String cacheName) throws CacheException;
/**
* 方法名::updateCahce
* 功能说明::更新缓存堆
*/
void updateCahce(String cacheName, Cache<Object, Object> cache) throws CacheException;
}
SimpleCacheServiceImpl
调用RedissonClient去实现缓存,同时使用ShiroRedissionSerialize实现序列化
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.core.SimpleCacheService;
import com.itheima.shiro.utils.ShiroRedissionSerialize;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
*
* @Description 简单的缓存管理接口的实现
*/
@Log4j2
@Component
public class SimpleCacheServiceImpl implements SimpleCacheService {
@Resource(name = "redissonClientForShiro")
RedissonClient redissonClient;
@Override
public void createCache(String name, Cache<Object, Object> cache){
RBucket<String> bucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+name);
bucket.trySet(ShiroRedissionSerialize.serialize(cache), SecurityUtils.getSubject().getSession().getTimeout()/1000, TimeUnit.SECONDS);
}
@SuppressWarnings("unchecked")
@Override
public Cache<Object, Object> getCache(String name) throws CacheException {
RBucket<String> bucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+name);
return (Cache<Object, Object>) ShiroRedissionSerialize.deserialize(bucket.get());
}
@Override
public void removeCache(String name) throws CacheException {
RBucket<String> bucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+name);
bucket.delete();
}
@Override
public void updateCahce(String name, Cache<Object, Object> cache){
RBucket<String> bucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+name);
bucket.set(ShiroRedissionSerialize.serialize(cache), SecurityUtils.getSubject().getSession().getTimeout()/1000, TimeUnit.MILLISECONDS);
}
}
package com.itheima.shiro.core.bridge;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.pojo.User;
import org.apache.shiro.authz.AuthorizationInfo;
import java.util.List;
/**
* @Description:用户信息桥接(后期会做缓存)
*/
public interface UserBridgeService {
/**
* @Description 查找用户信息
* @param loginName 用户名称
* @return user对象
*/
User findUserByLoginName(String loginName);
/**
* @Description 鉴权方法
* @param shiroUser 令牌对象
* @return 鉴权信息
*/
AuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser);
/**
* @Description 查询用户对应角色标识list
* @param userId 用户id
* @return 角色标识集合
*/
List<String> findRoleList(String key,String userId);
/**
* @Description 查询用户对应资源标识list
* @param userId 用户id
* @return 资源标识集合
*/
List<String> findResourcesList(String key,String userId);
/**
* @Description 查询资源ids
* @param userId 用户id
* @return 资源id集合
*/
List<String> findResourcesIds(String userId);
/**
* @Description 加载缓存
* @param shiroUser 令牌对象
* @return
*/
void loadUserAuthorityToCache(ShiroUser shiroUser);
}
此时我们就可以修改UserBridgeServiceImpl实现缓存了
package com.itheima.shiro.core.bridge.impl;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.core.SimpleCacheService;
import com.itheima.shiro.core.adapter.UserAdapter;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleMapCache;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.pojo.Resource;
import com.itheima.shiro.pojo.Role;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.utils.ShiroUtil;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description:用户信息桥接(后期会做缓存)
*/
@Component("userBridgeService")
public class UserBridgeServiceImpl implements UserBridgeService {
@Autowired
UserAdapter userAdapter;
@Autowired
SimpleCacheService simpleCacheService;
@Override
public User findUserByLoginName(String loginName) {
String key = CacheConstant.FIND_USER_BY_LOGINNAME + loginName;
//获取缓存
Cache<Object, Object> cache = simpleCacheService.getCache(key);
//缓存存在
if (!EmptyUtil.isNullOrEmpty(cache)){
return (User) cache.get(key);
}
//缓存不存在
User user = userAdapter.findUserByLoginName(loginName);
if (!EmptyUtil.isNullOrEmpty(user)){
Map<Object,Object> map = new HashMap<>();
map.put(key, user);
SimpleMapCache simpleMapCache = new SimpleMapCache(key, map);
simpleCacheService.creatCache(key, simpleMapCache);
}
return user;
}
@Override
public List<String> findResourcesIds(String userId) {
String sessionId = ShiroUtil.getShiroSessionId();
String key = CacheConstant.RESOURCES_KEY_IDS+sessionId;
List<Resource> resources = new ArrayList<>();
//获取缓存
Cache<Object, Object> cache = simpleCacheService.getCache(key);
//缓存存在
if (!EmptyUtil.isNullOrEmpty(cache)){
resources = (List<Resource>) cache.get(key);
}else {
//缓存不存在
resources = userAdapter.findResourceByUserId(userId);
if (!EmptyUtil.isNullOrEmpty(resources)){
Map<Object,Object> map = new HashMap<>();
map.put(key, resources);
SimpleMapCache simpleMapCache = new SimpleMapCache(key, map);
simpleCacheService.creatCache(key,simpleMapCache );
}
}
List<String> ids = new ArrayList<>();
for (Resource resource : resources) {
ids.add(resource.getId());
}
return ids;
}
@Override
public AuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser) {
String sessionId = ShiroUtil.getShiroSessionId();
String roleKey = CacheConstant.ROLE_KEY+sessionId;
String resourcesKey = CacheConstant.RESOURCES_KEY+sessionId;
//查询用户对应的角色标识
List<String> roleList = this.findRoleList(roleKey,shiroUser.getId());
//查询用户对于的资源标识
List<String> resourcesList = this.findResourcesList(resourcesKey,shiroUser.getId());
//构建鉴权信息对象
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(roleList);
simpleAuthorizationInfo.addStringPermissions(resourcesList);
return simpleAuthorizationInfo;
}
@Override
public List<String> findRoleList(String key,String userId){
List<Role> roles = new ArrayList<>();
//获得缓存
Cache<Object, Object> cache = simpleCacheService.getCache(key);
//缓存存在
if (!EmptyUtil.isNullOrEmpty(cache)){
roles = (List<Role>) cache.get(key);
}else {
//缓存不存在
roles = userAdapter.findRoleByUserId(userId);
if (!EmptyUtil.isNullOrEmpty(roles)){
Map<Object,Object> map = new HashMap<>();
map.put(key, roles);
SimpleMapCache simpleMapCache = new SimpleMapCache(key, map);
simpleCacheService.creatCache(key,simpleMapCache );
}
}
List<String> roleLabel = new ArrayList<>();
for (Role role : roles) {
roleLabel.add(role.getLabel());
}
return roleLabel;
}
@Override
public List<String> findResourcesList(String key,String userId){
List<Resource> resources = new ArrayList<>();
//获得缓存
Cache<Object, Object> cache = simpleCacheService.getCache(key);
//缓存存在
if (!EmptyUtil.isNullOrEmpty(cache)){
resources = (List<Resource>) cache.get(key);
}else {
//缓存不存在
resources = userAdapter.findResourceByUserId(userId);
if (!EmptyUtil.isNullOrEmpty(resources)){
Map<Object,Object> map = new HashMap<>();
map.put(key, resources);
SimpleMapCache simpleMapCache = new SimpleMapCache(key, map);
simpleCacheService.creatCache(key,simpleMapCache );
}
}
List<String> resourceLabel = new ArrayList<>();
for (Resource resource : resources) {
resourceLabel.add(resource.getLabel());
}
return resourceLabel;
}
@Override
public void loadUserAuthorityToCache(ShiroUser shiroUser) {
String sessionId = ShiroUtil.getShiroSessionId();
String roleKey = CacheConstant.ROLE_KEY+sessionId;
String resourcesKey = CacheConstant.RESOURCES_KEY+sessionId;
//查询用户对应的角色标识
List<String> roleList = this.findRoleList(roleKey,shiroUser.getId());
//查询用户对于的资源标识
List<String> resourcesList = this.findResourcesList(resourcesKey,shiroUser.getId());
}
}
使用debug模式启动项目,使用admin/pass登录系统,访问资源,进入debug模式
第二次走缓存
我们实现的realm的集中式缓存,那么还有什么问题没有解决呢?
用户在点击退出时候,我们还没有清理缓存!如果不清理,在用户量大的时候,可能会有大量的垃圾信息在redis中存在。
重写ShiroConfig
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.constant.SuperConstant;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.SimpleCacheService;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.utils.ShiroUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @Description:自定义realm的抽象类实现
*/
public class ShiroDbRealmImpl extends ShiroDbRealm {
@Autowired
UserBridgeService userBridgeService;
@Autowired
SimpleCacheService simpleCacheService;
@Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token令牌信息
SimpleToken simpleToken = (SimpleToken) token;
//查询user对象
User user = userBridgeService.findUserByLoginName(simpleToken.getUsername());
if (EmptyUtil.isNullOrEmpty(user)){
throw new UnknownAccountException("账号不存在!");
}
//构建认证令牌对象
ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);
shiroUser.setResourceIds(userBridgeService.findResourcesIds(shiroUser.getId()));
String slat = shiroUser.getSalt();
String password = shiroUser.getPassWord();
//构建认证信息对象:1、令牌对象 2、密文密码 3、加密因子 4、当前realm的名称
return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(slat), getName());
}
@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
return userBridgeService.getAuthorizationInfo(shiroUser);
}
@Override
protected void doClearCache(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
String sessionId = ShiroUtil.getShiroSessionId();
String roleKey = CacheConstant.ROLE_KEY+sessionId;
String resourcesKey = CacheConstant.RESOURCES_KEY+sessionId;
String loginNamekey = CacheConstant.FIND_USER_BY_LOGINNAME + shiroUser.getLoginName();
String resourcesIdKey = CacheConstant.RESOURCES_KEY_IDS+sessionId;
simpleCacheService.removeCache(roleKey);
simpleCacheService.removeCache(resourcesKey);
simpleCacheService.removeCache(loginNamekey);
simpleCacheService.removeCache(resourcesIdKey);
super.doClearCache(principals);
}
@Override
public void initCredentialsMatcher() {
//指定密码算法
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(SuperConstant.HASH_ALGORITHM);
//指定迭代次数
hashedCredentialsMatcher.setHashIterations(SuperConstant.HASH_INTERATIONS);
//生效密码比较器
setCredentialsMatcher(hashedCredentialsMatcher);
}
}
所有服务器的session信息都存储到了同一个Redis集群中,即所有的服务都将 Session 的信息存储到 Redis 集群中,无论是对 Session 的注销、更新都会同步到集群中,达到了 Session 共享的目的。
Cookie 保存在客户端浏览器中,而 Session 保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就可以了。
在实际工作中我们建议使用外部的缓存设备(包括Redis)来共享 Session,避免单个服务器节点挂掉而影响服务,共享数据都会放到外部缓存容器中
RedisSessionDao继承AbstractSessionDAO,重写了会话的创建、读取、修改等操作,全部缓存与redis中
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.utils.ShiroRedissionSerialize;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* @Description 实现shiro session的memcached集中式管理~
*/
@Log4j2
public class RedisSessionDao extends AbstractSessionDAO {
@Resource(name = "redissonClientForShiro")
RedissonClient redissonClient;
private Long globalSessionTimeout;
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
// log.info("=============创建sessionId:{}",sessionId);
RBucket<String> sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+sessionId.toString());
sessionIdRBucket.trySet(ShiroRedissionSerialize.serialize(session), globalSessionTimeout, TimeUnit.SECONDS);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
RBucket<String> sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+sessionId.toString());
Session session = (Session) ShiroRedissionSerialize.deserialize(sessionIdRBucket.get());
// log.info("=============读取sessionId:{}",session.getId().toString());
return session;
}
@Override
public void delete(Session session) {
// log.info("=============删除sessionId:{}",session.getId().toString());
RBucket<String> sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+session.getId().toString());
sessionIdRBucket.delete();
}
@Override
public Collection<Session> getActiveSessions() {
return Collections.emptySet();
}
@Override
public void update(Session session) {
RBucket<String> sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+session.getId().toString());
sessionIdRBucket.set(ShiroRedissionSerialize.serialize(session), globalSessionTimeout, TimeUnit.SECONDS);
// log.info("=============修改sessionId:{}",session.getId().toString());
}
public void setGlobalSessionTimeout(Long globalSessionTimeout) {
this.globalSessionTimeout = globalSessionTimeout;
}
}
package com.itheima.shiro.config;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.impl.RedisSessionDao;
import com.itheima.shiro.core.impl.ShiroCacheManager;
import com.itheima.shiro.core.impl.ShiroDbRealmImpl;
import com.itheima.shiro.filter.RolesOrAuthorizationFilter;
import com.itheima.shiro.properties.PropertiesUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.session.mgt.eis.SessionDAO;
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.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description 权限配置类
*/
@Configuration
@ComponentScan(basePackages = "com.itheima.shiro.core")
@EnableConfigurationProperties({ShiroRedisProperties.class})
@Log4j2
public class ShiroConfig {
@Autowired
private ShiroRedisProperties shiroRedisProperties;
/**
* @Description redission客户端
*/
@Bean("redissonClientForShiro")
public RedissonClient redissonClient() {
log.info("=====初始化redissonClientForShiro开始======");
String[] nodeList = shiroRedisProperties.getNodes().split(",");
Config config = new Config();
if (nodeList.length == 1) {
config.useSingleServer().setAddress(nodeList[0])
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
} else {
config.useClusterServers().addNodeAddress(nodeList)
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setMasterConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setMasterConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
}
RedissonClient redissonClient = Redisson.create(config);
log.info("=====初始化redissonClientForShiro完成======");
return redissonClient;
}
/**
* @Description 创建cookie对象
*/
@Bean(name="sessionIdCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
/**
* @Description 权限管理器
* @param
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroDbRealm());
securityManager.setSessionManager(shiroSessionManager());
return securityManager;
}
/**
* @Description 自定义RealmImpl
*/
@Bean(name="shiroDbRealm")
public ShiroDbRealm shiroDbRealm(){
return new ShiroDbRealmImpl();
}
/**
* @Description 自定义session会话存储的实现类 ,使用Redis来存储共享session,达到分布式部署目的
*/
@Bean("redisSessionDao")
public SessionDAO redisSessionDao(){
RedisSessionDao sessionDAO = new RedisSessionDao();
sessionDAO.setGlobalSessionTimeout(shiroRedisProperties.getGlobalSessionTimeout());
return sessionDAO;
}
/**
* @Description 会话管理器
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager shiroSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDao());
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(simpleCookie());
sessionManager.setGlobalSessionTimeout(shiroRedisProperties.getGlobalSessionTimeout());
return sessionManager;
}
/**
* @Description 保证实现了Shiro内部lifecycle函数的bean执行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* @Description AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(defaultWebSecurityManager());
return new AuthorizationAttributeSourceAdvisor();
}
/**
* @Description 过滤器链
*/
private Map<String, String> filterChainDefinition(){
List<Object> list = PropertiesUtil.propertiesShiro.getKeyList();
Map<String, String> map = new LinkedHashMap<>();
for (Object object : list) {
String key = object.toString();
String value = PropertiesUtil.getShiroValue(key);
log.info("读取防止盗链控制:---key{},---value:{}",key,value);
map.put(key, value);
}
return map;
}
/**
* @Description 自定义过滤器定义
*/
private Map<String, Filter> filters() {
Map<String, Filter> map = new HashMap<String, Filter>();
map.put("roleOr", new RolesOrAuthorizationFilter());
return map;
}
/**
* @Description Shiro过滤器
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(defaultWebSecurityManager());
//使自定义过滤器生效
shiroFilter.setFilters(filters());
shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
shiroFilter.setLoginUrl("/login");
shiroFilter.setUnauthorizedUrl("/login");
return shiroFilter;
}
}
此时我们需要2个相同的项目shiro-day01-12shiro-redis-SessionManager-A和shiro-day01-13shiro-redis-SessionManager-B
分别启动A,B2个项目
访问http://127.0.0.1:8081/shiro/login,使用admin/pass登录
修改地址栏http://127.0.0.1:8081/shiro/menus/system为http://127.0.0.1:8082/shiro/menus/system,也可以正常访问
此时我们实现了在A服务登录,直接访问B服务则无需登录
保证原子性:
单系统:AtomicLong计数
集群系统:RedissionClient提供的RAtomicLong计数
1、获取系统中是否已有登录次数缓存,缓存对象结构预期为:"用户名--登录次数"。
2、如果之前没有登录缓存,则创建一个登录次数缓存。
3、如果缓存次数已经超过限制,则驳回本次登录请求。
4、将缓存记录的登录次数加1,设置指定时间内有效
5、验证用户本次输入的帐号密码,如果登录登录成功,则清除掉登录次数的缓存
思路有了,那我们在哪里实现呢?我们知道AuthenticatingRealm里有比较密码的入口doCredentialsMatch方法
查看其实现
新建项目shiro-day01-14shiro-RetryLimit
package com.itheima.shiro.core.impl;
import com.itheima.shiro.core.base.ShiroUser;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
/**
* @Description:密码重试比较器
*/
public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher {
private RedissonClient redissonClient;
private static Long RETRY_LIMIT_NUM = 4L;
/**
* @Description 构造函数
* @param hashAlgorithmName 匹配次数
* @return
*/
public RetryLimitCredentialsMatcher(String hashAlgorithmName,RedissonClient redissonClient) {
super(hashAlgorithmName);
this.redissonClient = redissonClient;
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//获得登录吗
String loginName = (String) token.getPrincipal();
//获得缓存
RAtomicLong atomicLong = redissonClient.getAtomicLong(loginName);
long retryFlag = atomicLong.get();
//判断次数
if (retryFlag>RETRY_LIMIT_NUM){
//超过次数设计10分钟后重试
atomicLong.expire(10, TimeUnit.MICROSECONDS);
throw new ExcessiveAttemptsException("密码错误5次,请10分钟以后再试");
}
//累加次数
atomicLong.incrementAndGet();
atomicLong.expire(10, TimeUnit.MICROSECONDS);
//密码校验
boolean flag = super.doCredentialsMatch(token, info);
if (flag){
//校验成功删除限制
atomicLong.delete();
}
return flag;
}
}
修改initCredentialsMatcher方法,使用RetryLimitCredentialsMatcher
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.constant.SuperConstant;
import com.itheima.shiro.core.SimpleCacheManager;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.utils.*;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
/**
* @Description:自定义shiro的实现
*/
public class ShiroDbRealmImpl extends ShiroDbRealm {
@Autowired
private UserBridgeService userBridgeService;
@Autowired
private SimpleCacheManager simpleCacheManager;
@Resource(name = "redissonClientForShiro")
private RedissonClient redissonClient;
/**
* @Description 认证方法
* @param authcToken 校验传入令牌
* @return AuthenticationInfo
*/
@Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
SimpleToken token = (SimpleToken)authcToken;
User user = userBridgeService.findUserByLoginName(token.getUsername());
if(EmptyUtil.isNullOrEmpty(user)){
throw new UnknownAccountException("账号不存在");
}
ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);
String sessionId = ShiroUserUtil.getShiroSessionId();
String cacheKeyResourcesIds = CacheConstant.RESOURCES_KEY_IDS+sessionId;
shiroUser.setResourceIds(userBridgeService.findResourcesIdsList(cacheKeyResourcesIds,user.getId()));
String salt = user.getSalt();
String password = user.getPassWord();
return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(salt), getName());
}
/**
* @Description 授权方法
* @param principals SimpleAuthenticationInfo对象第一个参数
* @return
*/
@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
return userBridgeService.getAuthorizationInfo(shiroUser);
}
/**
* @Description 清理缓存
*/
@Override
public void doClearCache(PrincipalCollection principalcollection) {
String sessionId = ShiroUtil.getShiroSessionId();
simpleCacheManager.removeCache(CacheConstant.ROLE_KEY+sessionId);
simpleCacheManager.removeCache(CacheConstant.RESOURCES_KEY+sessionId);
simpleCacheManager.removeCache(CacheConstant.TOKEN+sessionId);
}
/**
* @Description 加密方式
*/
@Override
public void initCredentialsMatcher() {
RetryLimitCredentialsMatcher matcher = new RetryLimitCredentialsMatcher(SuperConstant.HASH_ALGORITHM,redissonClient);
matcher.setHashIterations(SuperConstant.HASH_INTERATIONS);
setCredentialsMatcher(matcher);
}
}
访问http://127.0.0.1/shiro/login,使用admin账号输入错误密码5次
在实际开发中,我们可能会遇到这样的需求,一个账号只允许同时一个在线,当账号在其他地方登陆的时候,会踢出前面登陆的账号,那我们怎么实现
自定义过滤器:继承AccessControlFilter
使用redis队列控制账号在线数目
实现步骤:
1、只针对登录用户处理,首先判断是否登录
2、使用RedissionClien创建队列
3、判断当前sessionId是否存在于此用户的队列=key:登录名 value:多个sessionId
4、不存在则放入队列尾端==>存入sessionId
5、判断当前队列大小是否超过限定此账号的可在线人数
6、超过:
*从队列头部拿到用户sessionId
*从sessionManger根据sessionId拿到session
*从sessionDao中移除session会话
7、未超过:放过操作
package com.itheima.shiro.filter;
import com.itheima.shiro.core.impl.RedisSessionDao;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.utils.ShiroUserUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.session.ExpiredSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.redisson.api.RDeque;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @Description:
*/
@Log4j2
public class KickedOutAuthorizationFilter extends AccessControlFilter {
private RedissonClient redissonClient;
private SessionDAO redisSessionDao;
private DefaultWebSessionManager sessionManager;
public KickedOutAuthorizationFilter(RedissonClient redissonClient, SessionDAO redisSessionDao, DefaultWebSessionManager sessionManager) {
this.redissonClient = redissonClient;
this.redisSessionDao = redisSessionDao;
this.sessionManager = sessionManager;
}
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
Subject subject = getSubject(servletRequest, servletResponse);
if (!subject.isAuthenticated()) {
//如果没有登录,直接进行之后的流程
return true;
}
//存放session对象进入队列
String sessionId = ShiroUserUtil.getShiroSessionId();
String LoginName = ShiroUserUtil.getShiroUser().getLoginName();
RDeque<String> queue = redissonClient.getDeque("KickedOutAuthorizationFilter:"+LoginName);
//判断sessionId是否存在于此用户的队列中
boolean flag = queue.contains(sessionId);
if (!flag) {
queue.addLast(sessionId);
}
//如果此时队列大于1,则开始踢人
if (queue.size() > 1) {
sessionId = queue.getFirst();
queue.removeFirst();
Session session = null;
try {
session = sessionManager.getSession(new DefaultSessionKey(sessionId));
}catch (UnknownSessionException ex){
log.info("session已经失效");
}catch (ExpiredSessionException expiredSessionException){
log.info("session已经过期");
}
if (!EmptyUtil.isNullOrEmpty(session)){
redisSessionDao.delete(session);
}
}
return true;
}
}
/**
* @Description 自定义过滤器定义
*/
private Map<String, Filter> filters() {
Map<String, Filter> map = new HashMap<String, Filter>();
map.put("roleOr", new RolesOrAuthorizationFilter());
map.put("kickedOut", new KickedOutAuthorizationFilter(redissonClient(), redisSessionDao(), shiroSessionManager()));
return map;
}
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#访问/resource/**需要有admin的角色
/resource/**=role-or[MangerRole,SuperAdmin]
#其他链接是需要登录的
/**=kickedOut,auth
使用谷歌访问http://127.0.0.1/shiro/login,使用admin/pass登陆
使用IE再访问http://127.0.0.1/shiro/login,使用admin/pass登陆
再刷新谷歌浏览器,发现账号被踢出
前面我们实现分布式的会话缓存,但是我们发现此功能的实现是基于浏览的cookie机制,也就是说用户禁用cookie后,我们的系统会就会产生会话不同的问题
我们的前端可能是web、Android、ios等应用,同时我们每一个接口都提供了无状态的应答方式,这里我们提供了基于JWT的token生成方案
1、用户登陆之后,获得此时会话的sessionId,使用JWT根据sessionId颁发签名并设置过期时间(与session过期时间相同)返回token
2、将token保存到客户端本地,并且每次发送请求时都在header上携带JwtToken
3、ShiroSessionManager继承DefaultWebSessionManager,重写getSessionId方法,从header上检测是否携带JwtToken,如果携带,则进行解码JwtToken,使用JwtToken中的jti作为SessionId。
4、重写shiro的默认过滤器,使其支持jwtToken有效期校验、及对JSON的返回支持
JwtAuthcFilter:实现是否需要登录的过滤,拒绝时如果header上携带JwtToken,则返回对应json
JwtPermsFilter:实现是否有对应资源的过滤,拒绝时如果header上携带JwtToken,则返回对应json
JwtRolesFilter:实现是否有对应角色的过滤,拒绝时如果header上携带JwtToken,则返回对应json
JWT(JSON WEB TOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。
广义上:JWT是一个标准的名称;
狭义上:JWT指的就是用来传递的那个token字符串
JWT由三部分构成:header(头部)、payload(载荷)和signature(签名)。
Header
存储两个变量
payload
存储很多东西,基础信息有如下几个
userId
Signature
这个是上面两个经过Header中的算法加密生成的,用于比对信息,防止篡改Header和payload
然后将这三个部分的信息经过加密生成一个JwtToken
的字符串,发送给客户端,客户端保存在本地。当客户端发起请求的时候携带这个到服务端(可以是在cookie
,可以是在header
),在服务端进行验证,我们需要解密对于的payload的内容
用于支持yaml文件配置的配置类
package com.itheima.shiro.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
/**
* @Description:jw配置文件
*/
@Data
@ConfigurationProperties(prefix = "itheima.framework.jwt")
public class JwtProperties implements Serializable {
/**
* @Description 签名密码
*/
private String hexEncodedSecretKey;
}
负责令牌的颁发、解析、校验
package com.itheima.shiro.core.impl;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.itheima.shiro.config.JwtProperties;
import com.itheima.shiro.utils.EncodesUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service("jwtTokenManager")
@EnableConfigurationProperties({JwtProperties.class})
public class JwtTokenManager {
@Autowired
JwtProperties jwtProperties;
/**
* @Description 签发令牌
* jwt字符串包括三个部分
* 1. header
* -当前字符串的类型,一般都是“JWT”
* -哪种算法加密,“HS256”或者其他的加密算法
* 所以一般都是固定的,没有什么变化
* 2. payload
* 一般有四个最常见的标准字段(下面有)
* iat:签发时间,也就是这个jwt什么时候生成的
* jti:JWT的唯一标识
* iss:签发人,一般都是username或者userId
* exp:过期时间
* @param iss 签发人
* @param ttlMillis 有效时间
* @param claims jwt中存储的一些非隐私信息
* @return
*/
public String IssuedToken(String iss, long ttlMillis,String sessionId, Map<String, Object> claims) {
if (claims == null) {
claims = new HashMap<>();
}
long nowMillis = System.currentTimeMillis();
String base64EncodedSecretKey = EncodesUtil.encodeHex(jwtProperties.getBase64EncodedSecretKey().getBytes());
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setId(sessionId)//2. 这个是JWT的唯一标识,一般设置成唯一的,这个方法可以生成唯一标识,此时存储的为sessionId,登录成功后回写
.setIssuedAt(new Date(nowMillis))//1. 这个地方就是以毫秒为单位,换算当前系统时间生成的iat
.setSubject(iss)//3. 签发人,也就是JWT是给谁的(逻辑上一般都是username或者userId)
.signWith(SignatureAlgorithm.HS256, base64EncodedSecretKey);//这个地方是生成jwt使用的算法和秘钥
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);//4. 过期时间,这个也是使用毫秒生成的,使用当前时间+前面传入的持续时间生成
builder.setExpiration(exp);
}
return builder.compact();
}
/**
* @Description 解析令牌
* @param jwtToken 令牌
* @return
*/
public Claims decodeToken(String jwtToken) {
String base64EncodedSecretKey = EncodesUtil.encodeHex(jwtProperties.getBase64EncodedSecretKey().getBytes());
// 得到 DefaultJwtParser
return Jwts.parser()
// 设置签名的秘钥
.setSigningKey(base64EncodedSecretKey)
// 设置需要解析的 jwt
.parseClaimsJws(jwtToken)
.getBody();
}
/**
* @Description 判断令牌是否合法
* @param jwtToken 令牌
* @return
*/
public boolean isVerifyToken(String jwtToken) {
String base64EncodedSecretKey = EncodesUtil.encodeHex(jwtProperties.getBase64EncodedSecretKey().getBytes());
//这个是官方的校验规则,这里只写了一个”校验算法“,可以自己加
Algorithm algorithm = Algorithm.HMAC256(EncodesUtil.decodeBase64(base64EncodedSecretKey));
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(jwtToken); // 校验不通过会抛出异常
//判断合法的标准:1. 头部和荷载部分没有篡改过。2. 没有过期
return true;
}
}
ShiroSessionManager主要是添加jwtToken的jti作为会话的唯一标识
package com.itheima.shiro.core.impl;
import com.itheima.shiro.utils.EmptyUtil;
import io.jsonwebtoken.Claims;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @Description 重写Jwt会话管理
*/
public class ShiroSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "jwtToken";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public ShiroSessionManager(){
super();
}
@Autowired
JwtTokenManager jwtTokenManager;
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response){
String jwtToken = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if(EmptyUtil.isNullOrEmpty(jwtToken)){
//如果没有携带id参数则按照父类的方式在cookie进行获取
return super.getSessionId(request, response);
}else{
//如果请求头中有 authToken 则其值为jwtToken,然后解析出会话session
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);
Claims decode = jwtTokenManager.decodeToken(jwtToken);
String id = (String) decode.get("jti");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
return id;
}
}
}
BaseResponse返回统一json的对象
package com.itheima.shiro.core.base;
import com.itheima.shiro.utils.ToString;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @Description 基础返回封装
*/
@Data
public class BaseResponse extends ToString {
private Integer code ;
private String msg ;
private String date;
private static final long serialVersionUID = -1;
public BaseResponse(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public BaseResponse(Integer code, String msg, String date) {
this.code = code;
this.msg = msg;
this.date = date;
}
}
使用wtTokenManager.isVerifyToken(jwtToken)校验颁发jwtToken是否合法,同时在拒绝的时候返回对应的json数据格式
package com.itheima.shiro.core.filter;
import com.alibaba.fastjson.JSONObject;
import com.itheima.shiro.constant.ShiroConstant;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.core.impl.JwtTokenManager;
import com.itheima.shiro.core.impl.ShiroSessionManager;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @Description:自定义登录验证过滤器
*/
public class JwtAuthcFilter extends FormAuthenticationFilter {
private JwtTokenManager jwtTokenManager;
public JwtAuthcFilter(JwtTokenManager jwtTokenManager) {
this.jwtTokenManager = jwtTokenManager;
}
/**
* @Description 是否允许访问
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//判断当前请求头中是否带有jwtToken的字符串
String jwtToken = WebUtils.toHttp(request).getHeader("jwtToken");
//如果有:走jwt校验
if (!EmptyUtil.isNullOrEmpty(jwtToken)){
boolean verifyToken = jwtTokenManager.isVerifyToken(jwtToken);
if (verifyToken){
return super.isAccessAllowed(request, response, mappedValue);
}else {
return false;
}
}
//没有没有:走原始校验
return super.isAccessAllowed(request, response, mappedValue);
}
/**
* @Description 访问拒绝时调用
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//判断当前请求头中是否带有jwtToken的字符串
String jwtToken = WebUtils.toHttp(request).getHeader("jwtToken");
//如果有:返回json的应答
if (!EmptyUtil.isNullOrEmpty(jwtToken)){
BaseResponse baseResponse = new BaseResponse(ShiroConstant.NO_LOGIN_CODE,ShiroConstant.NO_LOGIN_MESSAGE);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(baseResponse));
return false;
}
//如果没有:走原始方式
return super.onAccessDenied(request, response);
}
}
package com.itheima.shiro.core.filter;
import com.alibaba.fastjson.JSONObject;
import com.itheima.shiro.constant.ShiroConstant;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* @Description:自定义jwt的资源校验
*/
public class JwtPermsFilter extends PermissionsAuthorizationFilter {
/**
* @Description 访问拒绝时调用
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
//判断当前请求头中是否带有jwtToken的字符串
String jwtToken = WebUtils.toHttp(request).getHeader("jwtToken");
//如果有:返回json的应答
if (!EmptyUtil.isNullOrEmpty(jwtToken)){
BaseResponse baseResponse = new BaseResponse(ShiroConstant.NO_AUTH_CODE,ShiroConstant.NO_AUTH_MESSAGE);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(baseResponse));
return false;
}
//如果没有:走原始方式
return super.onAccessDenied(request, response);
}
}
package com.itheima.shiro.core.filter;
import com.alibaba.fastjson.JSONObject;
import com.itheima.shiro.constant.ShiroConstant;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* @Description:自定义jwt角色校验
*/
public class JwtRolesFilter extends RolesAuthorizationFilter {
/**
* @Description 访问拒绝时调用
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
//判断当前请求头中是否带有jwtToken的字符串
String jwtToken = WebUtils.toHttp(request).getHeader("jwtToken");
//如果有:返回json的应答
if (!EmptyUtil.isNullOrEmpty(jwtToken)){
BaseResponse baseResponse = new BaseResponse(ShiroConstant.NO_ROLE_CODE,ShiroConstant.NO_ROLE_MESSAGE);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(baseResponse));
return false;
}
//如果没有:走原始方式
return super.onAccessDenied(request, response);
}
}
1、ShiroSessionManager替换DefaultWebSessionManager
2、生效过滤器
package com.itheima.shiro.config;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.impl.*;
import com.itheima.shiro.filter.*;
import com.itheima.shiro.properties.PropertiesUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.session.mgt.eis.SessionDAO;
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.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description 权限配置类
*/
@Configuration
@ComponentScan(basePackages = {"com.itheima.shiro.core"})
@EnableConfigurationProperties({ShiroRedisProperties.class})
@Log4j2
public class ShiroConfig {
@Autowired
private ShiroRedisProperties shiroRedisProperties;
@Autowired
JwtTokenManager jwtTokenManager;
/**
* @Description redission客户端
*/
@Bean("redissonClientForShiro")
public RedissonClient redissonClient() {
log.info("=====初始化redissonClientForShiro开始======");
String[] nodeList = shiroRedisProperties.getNodes().split(",");
Config config = new Config();
if (nodeList.length == 1) {
config.useSingleServer().setAddress(nodeList[0])
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
} else {
config.useClusterServers().addNodeAddress(nodeList)
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setMasterConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setMasterConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
}
RedissonClient redissonClient = Redisson.create(config);
log.info("=====初始化redissonClientForShiro完成======");
return redissonClient;
}
/**
* @Description 创建cookie对象
*/
@Bean(name="sessionIdCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
/**
* @Description 缓存管理器
* @param
* @return
*/
@Bean(name="shiroCacheManager")
public ShiroCacheManager shiroCacheManager(){
return new ShiroCacheManager();
}
/**
* @Description 权限管理器
* @param
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroDbRealm());
securityManager.setSessionManager(shiroSessionManager());
securityManager.setCacheManager(shiroCacheManager());
return securityManager;
}
/**
* @Description 自定义RealmImpl
*/
@Bean(name="shiroDbRealm")
public ShiroDbRealm shiroDbRealm(){
return new ShiroDbRealmImpl();
}
/**
* @Description 自定义session会话存储的实现类 ,使用Redis来存储共享session,达到分布式部署目的
*/
@Bean("redisSessionDao")
public SessionDAO redisSessionDao(){
RedisSessionDao sessionDAO = new RedisSessionDao();
sessionDAO.setGlobalSessionTimeout(shiroRedisProperties.getGlobalSessionTimeout());
return sessionDAO;
}
/**
* @Description 会话管理器
*/
@Bean(name="sessionManager")
public ShiroSessionManager shiroSessionManager(){
ShiroSessionManager sessionManager = new ShiroSessionManager();
sessionManager.setSessionDAO(redisSessionDao());
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(simpleCookie());
sessionManager.setGlobalSessionTimeout(shiroRedisProperties.getGlobalSessionTimeout());
return sessionManager;
}
/**
* @Description 保证实现了Shiro内部lifecycle函数的bean执行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* @Description AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(defaultWebSecurityManager());
return new AuthorizationAttributeSourceAdvisor();
}
/**
* @Description 过滤器链
*/
private Map<String, String> filterChainDefinition(){
List<Object> list = PropertiesUtil.propertiesShiro.getKeyList();
Map<String, String> map = new LinkedHashMap<>();
for (Object object : list) {
String key = object.toString();
String value = PropertiesUtil.getShiroValue(key);
log.info("读取防止盗链控制:---key{},---value:{}",key,value);
map.put(key, value);
}
return map;
}
/**
* @Description 自定义过滤器定义
*/
private Map<String, Filter> filters() {
Map<String, Filter> map = new HashMap<String, Filter>();
map.put("roleOr", new RolesOrAuthorizationFilter());
map.put("kicked-out", new KickedOutAuthorizationFilter(redissonClient(), redisSessionDao(), shiroSessionManager()));
map.put("jwt-authc", new JwtAuthcFilter(jwtTokenManager));
map.put("jwt-perms", new JwtPermsFilter());
map.put("jwt-roles", new JwtRolesFilter());
return map;
}
/**
* @Description Shiro过滤器
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(defaultWebSecurityManager());
//使自定义过滤器生效
shiroFilter.setFilters(filters());
shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
shiroFilter.setLoginUrl("/login");
shiroFilter.setUnauthorizedUrl("/login");
return shiroFilter;
}
}
添加LoginForJwt方法
/**
* @Description jwt的json登录方式
* @param loginVo
* @return
*/
@RequestMapping("login-jwt")
@ResponseBody
public BaseResponse LoginForJwt(@RequestBody LoginVo loginVo){
return loginService.routeForJwt(loginVo);
}
添加routeForJwt方法
package com.itheima.shiro.service;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.vo.LoginVo;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import java.util.Map;
/**
* @Description 登陆业务接口
*/
public interface LoginService {
/**
* @Description 登陆路由
* @param loginVo 登录参数
* @return
*/
public Map<String, String> route(LoginVo loginVo) throws UnknownAccountException,IncorrectCredentialsException;
/**
* @Description jwt方式登录
@param loginVo 登录参数
* @return
*/
public BaseResponse routeForJwt(LoginVo loginVo) throws UnknownAccountException,IncorrectCredentialsException;
}
package com.itheima.shiro.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.constant.ShiroConstant;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.core.impl.JwtTokenManager;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.service.LoginService;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.ShiroUserUtil;
import com.itheima.shiro.utils.ShiroUtil;
import com.itheima.shiro.vo.LoginVo;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Description 登陆业务实现
*/
@Service("loginService")
@Log4j2
public class LoginServiceImpl implements LoginService {
@Resource(name = "redissonClientForShiro")
RedissonClient redissonClient;
@Autowired
UserBridgeService userBridgeService;
@Autowired
JwtTokenManager jwtTokenManager;
/* (non-Javadoc)
* @see LoginService#route(com.yz.commons.vo.LoginVo)
*/
@Override
public Map<String, String> route(LoginVo loginVo) throws UnknownAccountException, IncorrectCredentialsException {
Map<String, String> map = new HashMap<>();
try {
SimpleToken token = new SimpleToken(null, loginVo.getLoginName(), loginVo.getPassWord());
Subject subject = SecurityUtils.getSubject();
subject.login(token);
//创建缓存
this.loadAuthorityToCache();
} catch (UnknownAccountException ex) {
log.error("登陆异常:{}", ex);
throw new UnknownAccountException(ex);
} catch (IncorrectCredentialsException ex) {
log.error("登陆异常:{}", ex);
throw new IncorrectCredentialsException(ex);
}
return map;
}
@Override
public BaseResponse routeForJwt(LoginVo loginVo) throws UnknownAccountException, IncorrectCredentialsException {
Map<String, String> map = new HashMap<>();
String jwtToken = null;
try {
SimpleToken token = new SimpleToken(null, loginVo.getLoginName(), loginVo.getPassWord());
Subject subject = SecurityUtils.getSubject();
subject.login(token);
String shiroSessionId = ShiroUserUtil.getShiroSessionId();
//登录后颁发的令牌
ShiroUser shiroUser = ShiroUserUtil.getShiroUser();
Map<String, Object> claims = new HashMap<>();
claims.put("shiroUser", JSONObject.toJSONString(shiroUser));
jwtToken = jwtTokenManager.IssuedToken("system", subject.getSession().getTimeout(),shiroSessionId,claims);
map.put("jwtToken",jwtToken );
log.info("jwtToken:{}",map.toString());
//创建缓存
this.loadAuthorityToCache();
} catch (Exception ex) {
BaseResponse baseResponse = new BaseResponse(ShiroConstant.LOGIN_FAILURE_CODE, ShiroConstant.LOGIN_FAILURE_MESSAGE);
return baseResponse;
}
BaseResponse baseResponse = new BaseResponse(ShiroConstant.LOGIN_SUCCESS_CODE,ShiroConstant.LOGIN_SUCCESS_MESSAGE,jwtToken);
return baseResponse;
}
/**
*
* 方法名::loadAuthorityToCache
* 功能说明::加载缓存
*/
private void loadAuthorityToCache(){
//登陆成功后缓存用户的权限信息进入缓存
ShiroUser shiroUser = ShiroUserUtil.getShiroUser();
User user = BeanConv.toBean(shiroUser, User.class);
userBridgeService.loadUserAuthorityToCache(user);
}
}
【5】authentication.properties
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#访问/resource/**需要有admin的角色
#/resource/**=roleOr[MangerRole,SuperAdmin]
/role/** =jwt-roles[SuperAdmin]
/resource/** =jwt-perms[role:listInitialize]
#其他链接是需要登录的
/**=kicked-out,jwt-authc
1、测试登录后,jwtToken的生成,且校验会话是否使用新的jwtToken里的会话jti
2、测试自定义过滤器是否生效
使用jay/pass登录
使用admin/pass登录
在第十章中我们已经实现,使用jwt的令牌实现,重写DefaultWebSessionManager,从ServletRequest获得jwtToken作为会话sessionId
package com.itheima.shiro.core.impl;
import com.itheima.shiro.utils.EmptyUtil;
import io.jsonwebtoken.Claims;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @Description 重写Jwt会话管理
*/
public class ShiroSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "jwtToken";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public ShiroSessionManager(){
super();
}
@Autowired
JwtTokenManager jwtTokenManager;
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response){
String jwtToken = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if(EmptyUtil.isNullOrEmpty(jwtToken)){
//如果没有携带id参数则按照父类的方式在cookie进行获取
return super.getSessionId(request, response);
}else{
//如果请求头中有 authToken 则其值为jwtToken,然后解析出会话session
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);
Claims decode = jwtTokenManager.decodeToken(jwtToken);
String id = (String) decode.get("jti");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
return id;
}
}
}
在第七章中RedisSessionDao继承AbstractSessionDAO,重写了会话的创建、读取、修改等操作,全部缓存于redis中
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.utils.ShiroRedissionSerialize;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* @Description 实现shiro session的memcached集中式管理~
*/
@Log4j2
public class RedisSessionDao extends AbstractSessionDAO {
@Resource(name = "redissonClientForShiro")
RedissonClient redissonClient;
private Long globalSessionTimeout;
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
// log.info("=============创建sessionId:{}",sessionId);
RBucket<String> sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+sessionId.toString());
sessionIdRBucket.trySet(ShiroRedissionSerialize.serialize(session), globalSessionTimeout, TimeUnit.SECONDS);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
RBucket<String> sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+sessionId.toString());
Session session = (Session) ShiroRedissionSerialize.deserialize(sessionIdRBucket.get());
// log.info("=============读取sessionId:{}",session.getId().toString());
return session;
}
@Override
public void delete(Session session) {
// log.info("=============删除sessionId:{}",session.getId().toString());
RBucket<String> sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+session.getId().toString());
sessionIdRBucket.delete();
}
@Override
public Collection<Session> getActiveSessions() {
return Collections.emptySet();
}
@Override
public void update(Session session) {
RBucket<String> sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+session.getId().toString());
sessionIdRBucket.set(ShiroRedissionSerialize.serialize(session), globalSessionTimeout, TimeUnit.SECONDS);
// log.info("=============修改sessionId:{}",session.getId().toString());
}
public void setGlobalSessionTimeout(Long globalSessionTimeout) {
this.globalSessionTimeout = globalSessionTimeout;
}
}
第六章中,我们实现了realm的缓存机制,这里我们会把UserBridgeService使用dubbo服务化
其目的使得实际项目中的认证与鉴权走dubbo,减少服务器压力
在第十章中,我们加载过滤器链的方式
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#访问/resource/**需要有admin的角色
#/resource/**=role-or[MangerRole,SuperAdmin]
#/role/** =jwt-roles[SuperAdmin]
/resource/** =jwt-perms[role:listInitialize]
#其他链接是需要登录的
/**=kicked-out,jwt-authc
在统一鉴权系统中,我们不可能每次发布新的过滤器链,就去重启服务器,我们更希望可以动态管理过滤器链
shiro-client作为jar的依赖,满足以下需求:
1、非侵入式:使用者只需要对jar依赖和做少量的配置,就可以达到统一鉴权的目标
2、可扩展性:用户除使用提供的过滤器外,可以轻松安自己的业务去定义过滤器
3、集中式管理:依赖jar之后,shiro-mgt后台可以同时管控多个平台的权限的认证、鉴权、及动态配置过滤器链
springboot-shiro-gateway:
1、依赖shiro-client项目作为权限的被控制层
2、实现dubbo传输协议到HTTP传输协议的转化,当然这里提供的为通用的转换方式。
3、可复制、复制后只需要在shiro-mgt后台中做简单的配置,就可以实现一个新网关的接入
1、网关服务集群性,同时实现会话的统一管理
2、鉴权服务集群化,提供统一鉴权服务
3、管理后台集群化
springboot-shiro-parent:项目统一jar和plugIn的POM定义
1、dubbo业务服务转换http通讯
2、认证与鉴权服务化消费者
3、生成业务服务化消费者
认证与鉴权服务化的生成者
生产业务服务化生产者
上面的图解中我们可以看到,这里服务化的为UserAdapterFace
模块springboot-shiro-face中的接口定义UserAdapterFace
package com.itheima.shiro.face;
import com.itheima.shiro.vo.ResourceVo;
import com.itheima.shiro.vo.RoleVo;
import com.itheima.shiro.vo.UserVo;
import java.util.List;
/**
* @Description:用户服务接口定义
*/
public interface UserAdapterFace {
/**
* @Description 按用户名查找用户
* @param loginName 登录名
* @return
*/
UserVo findUserByLoginName(String loginName);
/**
* @Description 查找用户所有角色
* @param userId 用户Id
* @return
*/
List<RoleVo> findRoleByUserId(String userId);
/**
* @Description 查询用户有那些资源
* @param userId 用户Id
* @return
*/
List<ResourceVo> findResourceByUserId(String userId);
}
springboot-shiro-producer模块中的生产者UserAdapterFaceImpl
package com.itheima.shiro.faceImpl;
import com.itheima.shiro.adapter.UserAdapter;
import com.itheima.shiro.face.UserAdapterFace;
import com.itheima.shiro.pojo.Resource;
import com.itheima.shiro.pojo.Role;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.vo.ResourceVo;
import com.itheima.shiro.vo.RoleVo;
import com.itheima.shiro.vo.UserVo;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* @Description:
*/
@Service(version = "1.0.0", retries = 3,timeout = 5000)
public class UserAdapterFaceImpl implements UserAdapterFace {
@Autowired
UserAdapter userAdapter;
@Override
public UserVo findUserByLoginName(String loginName) {
User user = userAdapter.findUserByLoginName(loginName);
if (!EmptyUtil.isNullOrEmpty(user)){
return BeanConv.toBean(user,UserVo.class);
}
return null;
}
@Override
public List<RoleVo> findRoleByUserId(String userId) {
List<Role> list = userAdapter.findRoleByUserId(userId);
if (!EmptyUtil.isNullOrEmpty(list)){
return BeanConv.toBeanList(list, RoleVo.class);
}
return null;
}
@Override
public List<ResourceVo> findResourceByUserId(String userId) {
List<Resource> list = userAdapter.findResourceByUserId(userId);
if (!EmptyUtil.isNullOrEmpty(list)){
return BeanConv.toBeanList(list, ResourceVo.class);
}
return null;
}
}
springboot-shiro-handler模块下的消费者UserBridgeServiceImpl
package com.itheima.shiro.client;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.core.SimpleCacheManager;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleMapCache;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.face.UserAdapterFace;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.utils.ShiroUserUtil;
import com.itheima.shiro.vo.ResourceVo;
import com.itheima.shiro.vo.RoleVo;
import com.itheima.shiro.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.util.ByteSource;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Description 权限桥接器
*/
@Slf4j
@Component("userBridgeService")
public class UserBridgeServiceImpl implements UserBridgeService {
@Reference(version = "1.0.0")
private UserAdapterFace userAdapterFace;
@Autowired
private SimpleCacheManager simpleCacheManager;
@javax.annotation.Resource(name = "redissonClientForShiro")
private RedissonClient redissonClient;
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken,String realmName) {
SimpleToken token = (SimpleToken)authcToken;
UserVo user = this.findUserByLoginName(token.getUsername());
if(EmptyUtil.isNullOrEmpty(user)){
throw new UnknownAccountException("账号不存在");
}
ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);
String sessionId = ShiroUserUtil.getShiroSessionId();
String cacheKeyResourcesIds = CacheConstant.RESOURCES_KEY_IDS+sessionId;
shiroUser.setResourceIds(this.findResourcesIdsList(cacheKeyResourcesIds,user.getId()));
String salt = user.getSalt();
String password = user.getPassWord();
return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(salt), realmName);
}
@Override
public SimpleAuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser) {
UserVo user = BeanConv.toBean(shiroUser, UserVo.class);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String sessionId = ShiroUserUtil.getShiroSessionId();
//查询用户拥有的角色
String cacheKeyRole = CacheConstant.ROLE_KEY + sessionId;
info.addRoles(this.findRoleList(cacheKeyRole, user.getId()));
//查询用户拥有的资源
String cacheKeyResources = CacheConstant.RESOURCES_KEY + sessionId;
info.addStringPermissions(this.findResourcesList(cacheKeyResources, user.getId()));
return info;
}
@Override
public List<String> findRoleList(String cacheKeyRole, String userId) {
List<RoleVo> roles = new ArrayList<RoleVo>();
if (simpleCacheManager.getCache(cacheKeyRole) != null) {
roles = (List<RoleVo>) simpleCacheManager.getCache(cacheKeyRole).get(cacheKeyRole);
} else {
roles = userAdapterFace.findRoleByUserId(userId);
if (roles.size() > 0) {
//用户角色存放到map
Map<Object, Object> mapRole = new HashMap<Object, Object>();
mapRole.put(cacheKeyRole, roles);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheRole = new SimpleMapCache(cacheKeyRole, mapRole);
simpleCacheManager.createCache(cacheKeyRole, cacheRole);
}
}
List<String> rolesLabel = new ArrayList<String>();
for (RoleVo role : roles) {
rolesLabel.add(role.getLabel());
}
return rolesLabel;
}
@Override
public List<String> findResourcesList(String cacheKeyResources,String userId) {
List<ResourceVo> resourcesList = new ArrayList<ResourceVo>();
if (simpleCacheManager.getCache(cacheKeyResources) != null) {
resourcesList = (List<ResourceVo>) simpleCacheManager.getCache(cacheKeyResources).get(cacheKeyResources);
} else {
resourcesList = userAdapterFace.findResourceByUserId(userId);
if (resourcesList.size() > 0) {
//用户资源存放到map
Map<Object, Object> mapResource = new HashMap<Object, Object>();
mapResource.put(cacheKeyResources, resourcesList);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);
simpleCacheManager.createCache(cacheKeyResources, cacheResource);
}
}
List<String> resourcesLabel = new ArrayList<String>();
for (ResourceVo resources : resourcesList) {
resourcesLabel.add(resources.getLabel());
}
return resourcesLabel;
}
@Override
public UserVo findUserByLoginName(String loginName) {
String key = CacheConstant.FIND_USER_BY_LOGINNAME+loginName;
RBucket<UserVo> rBucket = redissonClient.getBucket(key);
UserVo user = rBucket.get();
if (!EmptyUtil.isNullOrEmpty(user)) {
return user;
}else {
user = userAdapterFace.findUserByLoginName(loginName);
if (!EmptyUtil.isNullOrEmpty(user)) {
rBucket.set(user, 300, TimeUnit.SECONDS);
return user;
}
}
rBucket.set(new UserVo(), 3, TimeUnit.SECONDS);
return null;
}
@Override
public List<String> findResourcesIdsList(String cacheKeyResources,String userId) {
List<ResourceVo> resourcesList = new ArrayList<ResourceVo>();
if (simpleCacheManager.getCache(cacheKeyResources) != null) {
resourcesList = (List<ResourceVo>) simpleCacheManager.getCache(cacheKeyResources).get(cacheKeyResources);
} else {
resourcesList = userAdapterFace.findResourceByUserId(userId);
if (resourcesList.size() > 0) {
//用户资源存放到map
Map<Object, Object> mapResource = new HashMap<Object, Object>();
mapResource.put(cacheKeyResources, resourcesList);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);
simpleCacheManager.createCache(cacheKeyResources, cacheResource);
}
}
List<String> resourcesLabel = new ArrayList<String>();
for (ResourceVo resources : resourcesList) {
resourcesLabel.add(resources.getId());
}
return resourcesLabel;
}
@Override
public void loadUserAuthorityToCache(ShiroUser user) {
String sessionId = user.getSessionId();
List<RoleVo> roles = userAdapterFace.findRoleByUserId(user.getId());
//创建角色cachaeKey
String cacheKeyRole = CacheConstant.ROLE_KEY + sessionId;
//用户角色存放到map
Map<Object, Object> mapRole = new HashMap<Object, Object>();
mapRole.put(cacheKeyRole, roles);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheRole = new SimpleMapCache(cacheKeyRole, mapRole);
simpleCacheManager.createCache(cacheKeyRole, cacheRole);
List<ResourceVo> resourcesList = userAdapterFace.findResourceByUserId(user.getId());
if (resourcesList.size() > 0) {
//创建资源cachaeKey
String cacheKeyResources = CacheConstant.RESOURCES_KEY + sessionId;
//用户资源存放到map
Map<Object, Object> mapResource = new HashMap<Object, Object>();
mapResource.put(cacheKeyResources, resourcesList);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);
simpleCacheManager.createCache(cacheKeyResources, cacheResource);
}
}
}
通过上面的改造,我们可以发现:用户在认证与鉴权时走的都是dubbo的服务,而在实际业务项目中不会再去操作鉴权相关的内容
在第十章中,我们加载过滤器链的方式
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#访问/resource/**需要有admin的角色
#/resource/**=roleOr[MangerRole,SuperAdmin]
#/role/** =jwt-roles[SuperAdmin]
/resource/** =jwt-perms[role:listInitialize]
#其他链接是需要登录的
/**=kicked-out,jwt-authc
在统计鉴权系统中,我们不可能每次发布新的过滤器链,就去重启服务器,我们更希望可以动态管理过滤器链
实现动态过滤器链,我们需要保证以下几个特性:
1、持久化:原有的properties内容放入数据库,
2、有序性:因过滤器链有序加载的特性,读取过滤器链的时保证其有序性
3、服务化:过滤器链的服务做成dubbo服务,做到集中式管理
4、同步性:不同业务系统对于过滤器链的加载需要同步
5、热加载:过滤器链修改之后,各个业务系统不需要重启服务,以达到热加载的目的
主要是对FilterChain类的CRUD这里就不做赘述,需要注意的是排序:升序排列,以保障过滤器链的有序加载
服务化过滤器链加载
FilterChainFace:过滤器链桥接器dubbo接口层
FilterChainFaceImpl:过滤器链桥接器dubbo接口层实现
FilterChainFace接口
package com.itheima.shiro.face;
import com.itheima.shiro.vo.FilterChainVo;
import java.util.List;
/**
* @Description:过滤器查询接口
*/
public interface FilterChainFace {
public List<FilterChainVo> findFilterChainList();
}
FilterChainFaceImpl
package com.itheima.shiro.faceImpl;
import com.itheima.shiro.face.FilterChainFace;
import com.itheima.shiro.pojo.FilterChain;
import com.itheima.shiro.service.FilterChainService;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.vo.FilterChainVo;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* @Description:
*/
@Service(version = "1.0.0", retries = 3,timeout = 5000)
public class FilterChainFaceImpl implements FilterChainFace {
@Autowired
FilterChainService filterChainService;
@Override
public List<FilterChainVo> findFilterChainList() {
List<FilterChain> filterChainList = filterChainService.findFilterChainList();
if (!EmptyUtil.isNullOrEmpty(filterChainList)){
return BeanConv.toBeanList(filterChainList, FilterChainVo.class);
}
return null;
}
}
这里只是简单的dubbo服务,也不做赘述
定义启动加载过滤器链服务同步:
FilterChainBridgeService:过滤器链桥接器service接口层
FilterChainBridgeServiceImpl:过滤器链桥接器service接口层实现
ShiroFilerChainService:shiro过滤器链服务加载接口
ShiroFilerChainService:shiro过滤器链服务加载接口实现
FilterChainBridgeService
package com.itheima.shiro.core.bridge;
import com.itheima.shiro.vo.FilterChainVo;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
/**
* @Description 过滤器链service接口层
*/
public interface FilterChainBridgeService {
/**
* @Description 查询所有有效的过滤器链
* @return
*/
List<FilterChainVo> findFilterChainList();
}
FilterChainBridgeServiceImpl
package com.itheima.shiro.client;
import com.itheima.shiro.core.bridge.FilterChainBridgeService;
import com.itheima.shiro.face.FilterChainFace;
import com.itheima.shiro.vo.FilterChainVo;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Description:
*/
@Component("filterChainBridgeService")
public class FilterChainBridgeServiceImpl implements FilterChainBridgeService {
@Reference(version = "1.0.0")
private FilterChainFace filterChainFace;
@Override
public List<FilterChainVo> findFilterChainList() {
return filterChainFace.findFilterChainList();
}
}
ShiroFilerChainService过滤器链同步接口
package com.itheima.shiro.service;
import com.itheima.shiro.vo.FilterChainVo;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* @Description:过滤器链同步接口
*/
public interface ShiroFilerChainService {
/**
* @Description 启动时加载数据库中的过滤器链
*/
void init();
/**
* @Description 初始化过滤器链
* @param
* @return
*/
void initFilterChains(List<FilterChainVo> FilterChainVos);
}
ShiroFilerChainServiceImpl过滤器链同步接口实现
package com.itheima.shiro.service.impl;
import com.itheima.shiro.core.impl.CustomDefaultFilterChainManager;
import com.itheima.shiro.service.ShiroFilerChainService;
import com.itheima.shiro.core.bridge.FilterChainBridgeService;
import com.itheima.shiro.vo.FilterChainVo;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.NamedFilterList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Description:过滤器链同步接口实现
*/
@Service("shiroFilerChainManager")
@Log4j2
public class ShiroFilerChainServiceImpl implements ShiroFilerChainService {
//此时注入的为CustomDefaultFilterChainManager
@Autowired
private CustomDefaultFilterChainManager filterChainManager;
@Autowired
FilterChainBridgeService filterChainBridgeService;
private Map<String, NamedFilterList> defaultFilterChains;
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
/**
* @Description 启动定时器,间隔2分钟同步数据库的过滤器链
*/
@Override
@PostConstruct
public void init() {
defaultFilterChains = new LinkedHashMap<>();
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
initFilterChains(filterChainBridgeService.findFilterChainList());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}, 0, 120, TimeUnit.SECONDS);
}
@Override
public void initFilterChains(List<FilterChainVo> FilterChainVos) {
//1、首先删除以前老的filter chain并注册默认的
filterChainManager.getFilterChains().clear();
//2、循环URL Filter 注册filter chain
for (FilterChainVo urlFilterVo : FilterChainVos) {
String url = urlFilterVo.getUrl();
String filterName = urlFilterVo.getFilterName();
String[] filterNames = filterName.split(",");
for (String name : filterNames) {
//注册所有filter,包含自定义的过滤器
switch(name){
case "anon":
filterChainManager.addToChain(url, name);
break;
case "authc":
filterChainManager.addToChain(url, name);
break;
case "roles":
filterChainManager.addToChain(url, name, urlFilterVo.getRoles());
break;
case "perms":
filterChainManager.addToChain(url, name,urlFilterVo.getPermissions());
break;
case "role-or":
filterChainManager.addToChain(url, name,urlFilterVo.getRoles());
break;
case "kicked-out":
filterChainManager.addToChain(url, name);
break;
case "jwt-authc":
filterChainManager.addToChain(url, name);
break;
case "jwt-roles":
filterChainManager.addToChain(url, name, urlFilterVo.getRoles());
break;
case "jwt-perms":
filterChainManager.addToChain(url, name,urlFilterVo.getPermissions());
break;
default:
break;
}
}
}
}
}
为了实现热加载我们需要定义以下3个类
CustomDefaultFilterChainManager:自定义的默认过滤器链管理者
CustomPathMatchingFilterChainResolver:自定义的路径匹配过滤器链解析器
CustomShiroFilterFactoryBean:自定义shiro过滤器工厂bean
咱们来看下顶级接口FilterChainManager
package com.itheima.shiro.core.impl;
import org.apache.shiro.config.Ini;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.util.Nameable;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.NamedFilterList;
import org.apache.shiro.web.filter.mgt.SimpleNamedFilterList;
import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description:自定义默认过滤器管理者
*/
public class CustomDefaultFilterChainManager extends DefaultFilterChainManager {
//登录地址
private String loginUrl;
//登录成功后默认跳转地址
private String successUrl;
//未授权跳转地址
private String unauthorizedUrl;
public CustomDefaultFilterChainManager() {
//构建过滤器
setFilters(new LinkedHashMap<String, Filter>());
//构建过滤器链
setFilterChains(new LinkedHashMap<String, NamedFilterList>());
//构建默认过滤器
addDefaultFilters(true);
}
/**
* @Description 注册我们自定义的过滤器,相当于ShiroFilterFactoryBean的filters属性
* @param customFilters 过滤器
* @return
*/
public void setCustomFilters(Map<String, Filter> customFilters) {
for(Map.Entry<String, Filter> entry : customFilters.entrySet()) {
addFilter(entry.getKey(), entry.getValue(), false);
}
}
/**
* @Description Spring容器启动时调用
*/
@PostConstruct
public void init() {
//配置默认过滤器
Map<String, Filter> filters = getFilters();
//为过滤器链配置全局URL处理属性
for (Filter filter : filters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}
}
/**
* @Description 此时交于spring容器出事化,这里忽略
*/
@Override
protected void initFilter(Filter filter) {
}
private void applyGlobalPropertiesIfNecessary(Filter filter) {
applyLoginUrlIfNecessary(filter);
applySuccessUrlIfNecessary(filter);
applyUnauthorizedUrlIfNecessary(filter);
}
private void applyLoginUrlIfNecessary(Filter filter) {
String loginUrl = getLoginUrl();
if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
AccessControlFilter acFilter = (AccessControlFilter) filter;
//only apply the login url if they haven't explicitly configured one already:
String existingLoginUrl = acFilter.getLoginUrl();
if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
acFilter.setLoginUrl(loginUrl);
}
}
}
private void applySuccessUrlIfNecessary(Filter filter) {
String successUrl = getSuccessUrl();
if (StringUtils.hasText(successUrl) && (filter instanceof AuthenticationFilter)) {
AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
//only apply the successUrl if they haven't explicitly configured one already:
String existingSuccessUrl = authcFilter.getSuccessUrl();
if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
authcFilter.setSuccessUrl(successUrl);
}
}
}
private void applyUnauthorizedUrlIfNecessary(Filter filter) {
String unauthorizedUrl = getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
//only apply the unauthorizedUrl if they haven't explicitly configured one already:
String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
if (existingUnauthorizedUrl == null) {
authzFilter.setUnauthorizedUrl(unauthorizedUrl);
}
}
}
public String getLoginUrl() {
return loginUrl;
}
public void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
public String getSuccessUrl() {
return successUrl;
}
public void setSuccessUrl(String successUrl) {
this.successUrl = successUrl;
}
public String getUnauthorizedUrl() {
return unauthorizedUrl;
}
public void setUnauthorizedUrl(String unauthorizedUrl) {
this.unauthorizedUrl = unauthorizedUrl;
}
}
CustomDefaultFilterChainManager:主要是把原来对象的创建交于spring容器,同时指定过滤器,然后构建过滤器链
package com.itheima.shiro.core.impl;
import org.apache.shiro.config.Ini;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.util.Nameable;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.NamedFilterList;
import org.apache.shiro.web.filter.mgt.SimpleNamedFilterList;
import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description:自定义默认过滤器管理者
*/
public class CustomDefaultFilterChainManager extends DefaultFilterChainManager {
//登录地址
private String loginUrl;
//登录成功后默认跳转地址
private String successUrl;
//未授权跳转地址
private String unauthorizedUrl;
public CustomDefaultFilterChainManager() {
//构建过滤器
setFilters(new LinkedHashMap<String, Filter>());
//构建过滤器链
setFilterChains(new LinkedHashMap<String, NamedFilterList>());
//构建默认过滤器
addDefaultFilters(true);
}
/**
* @Description 注册我们自定义的过滤器,相当于ShiroFilterFactoryBean的filters属性
* @param customFilters 过滤器
* @return
*/
public void setCustomFilters(Map<String, Filter> customFilters) {
for(Map.Entry<String, Filter> entry : customFilters.entrySet()) {
addFilter(entry.getKey(), entry.getValue(), false);
}
}
/**
* @Description Spring容器启动时调用
*/
@PostConstruct
public void init() {
//配置默认过滤器
Map<String, Filter> filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
//注册过滤器
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
//过滤器名称
String name = entry.getKey();
//过滤器
Filter filter = entry.getValue();
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
//配置3个URL
applyGlobalPropertiesIfNecessary(filter);
}
}
}
/**
* @Description 此时交于spring容器出事化,这里忽略
*/
@Override
protected void initFilter(Filter filter) {
}
private void applyGlobalPropertiesIfNecessary(Filter filter) {
applyLoginUrlIfNecessary(filter);
applySuccessUrlIfNecessary(filter);
applyUnauthorizedUrlIfNecessary(filter);
}
private void applyLoginUrlIfNecessary(Filter filter) {
String loginUrl = getLoginUrl();
if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
AccessControlFilter acFilter = (AccessControlFilter) filter;
//only apply the login url if they haven't explicitly configured one already:
String existingLoginUrl = acFilter.getLoginUrl();
if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
acFilter.setLoginUrl(loginUrl);
}
}
}
private void applySuccessUrlIfNecessary(Filter filter) {
String successUrl = getSuccessUrl();
if (StringUtils.hasText(successUrl) && (filter instanceof AuthenticationFilter)) {
AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
//only apply the successUrl if they haven't explicitly configured one already:
String existingSuccessUrl = authcFilter.getSuccessUrl();
if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
authcFilter.setSuccessUrl(successUrl);
}
}
}
private void applyUnauthorizedUrlIfNecessary(Filter filter) {
String unauthorizedUrl = getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
//only apply the unauthorizedUrl if they haven't explicitly configured one already:
String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
if (existingUnauthorizedUrl == null) {
authzFilter.setUnauthorizedUrl(unauthorizedUrl);
}
}
}
public String getLoginUrl() {
return loginUrl;
}
public void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
public String getSuccessUrl() {
return successUrl;
}
public void setSuccessUrl(String successUrl) {
this.successUrl = successUrl;
}
public String getUnauthorizedUrl() {
return unauthorizedUrl;
}
public void setUnauthorizedUrl(String unauthorizedUrl) {
this.unauthorizedUrl = unauthorizedUrl;
}
}
package org.apache.shiro.web.filter.mgt;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public interface FilterChainResolver {
//根据请求获得对应的过滤器链
FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);
}
CustomPathMatchingFilterChainResolver
这里主要核心内容是:指定使用过滤器链管理器为自己定的过滤器管理器
package com.itheima.shiro.core.impl;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.ArrayList;
import java.util.List;
public class CustomPathMatchingFilterChainResolver extends PathMatchingFilterChainResolver {
private CustomDefaultFilterChainManager customDefaultFilterChainManager;
public void setCustomDefaultFilterChainManager(CustomDefaultFilterChainManager customDefaultFilterChainManager) {
this.customDefaultFilterChainManager = customDefaultFilterChainManager;
}
public CustomDefaultFilterChainManager getCustomDefaultFilterChainManager() {
return customDefaultFilterChainManager;
}
@Override
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
//指定使用过滤器链管理器为自己定的过滤器管理器
FilterChainManager filterChainManager = getCustomDefaultFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
String requestURI = getPathWithinApplication(request);
List<String> chainNames = new ArrayList<String>();
//the 'chain names' in this implementation are actually path patterns defined by the user. We just use them
//as the chain name for the FilterChainManager's requirements
for (String pathPattern : filterChainManager.getChainNames()) {
// If the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
}
protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
//Expose the constructed FilterChainManager by first wrapping it in a
// FilterChainResolver implementation. The AbstractShiroFilter implementations
// do not know about FilterChainManagers - only resolvers:
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
//injection of the SecurityManager and FilterChainResolver:
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
ShiroFilterFactoryBean源码我们发现PathMatchingFilterChainResolver未暴露set方法,我们改写一下
package com.itheima.shiro.core.impl;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.BeanInitializationException;
/**
* @Description:
*/
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean {
PathMatchingFilterChainResolver chainResolver ;
public void setChainResolver(PathMatchingFilterChainResolver chainResolver) {
this.chainResolver = chainResolver;
}
@Override
protected AbstractShiroFilter createInstance() throws Exception {
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
chainResolver.setFilterChainManager(manager);
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
//injection of the SecurityManager and FilterChainResolver:
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
private static final class SpringShiroFilter extends AbstractShiroFilter {
protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
super();
if (webSecurityManager == null) {
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
}
setSecurityManager(webSecurityManager);
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
}
}
package com.itheima.shiro.config;
import com.itheima.shiro.constant.SuperConstant;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.filter.*;
import com.itheima.shiro.core.impl.*;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;
/**
* @Description 权限配置类
*/
@Configuration
@ComponentScan(basePackages = {"com.itheima.shiro.core"})
@EnableConfigurationProperties({ShiroRedisProperties.class})
@Log4j2
public class ShiroConfig {
@Autowired
private ShiroRedisProperties shiroRedisProperties;
@Autowired
JwtTokenManager jwtTokenManager;
/**
* @Description redission客户端
*/
@Bean("redissonClientForShiro")
public RedissonClient redissonClient() {
log.info("=====初始化redissonClientForShiro开始======");
String[] nodeList = shiroRedisProperties.getNodes().split(",");
Config config = new Config();
if (nodeList.length == 1) {
config.useSingleServer().setAddress(nodeList[0])
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
} else {
config.useClusterServers().addNodeAddress(nodeList)
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setMasterConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setMasterConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
}
RedissonClient redissonClient = Redisson.create(config);
log.info("=====初始化redissonClientForShiro完成======");
return redissonClient;
}
/**
* @Description 创建cookie对象
*/
@Bean(name="sessionIdCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
/**
* @Description 缓存管理器
* @param
* @return
*/
@Bean(name="shiroCacheManager")
public ShiroCacheManager shiroCacheManager(){
return new ShiroCacheManager(shiroRedisProperties.getGlobalSessionTimeout());
}
/**
* @Description 权限管理器
* @param
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroDbRealm());
securityManager.setSessionManager(shiroSessionManager());
securityManager.setCacheManager(shiroCacheManager());
return securityManager;
}
/**
* @Description 密码比较器
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher (){
RetryLimitCredentialsMatcher matcher = new RetryLimitCredentialsMatcher(SuperConstant.HASH_ALGORITHM);
matcher.setHashIterations(SuperConstant.HASH_INTERATIONS);
return matcher;
}
/**
* @Description 自定义RealmImpl
*/
@Bean(name="shiroDbRealm")
public ShiroDbRealm shiroDbRealm(){
ShiroDbRealm shiroDbRealm =new ShiroDbRealmImpl();
shiroDbRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroDbRealm;
}
/**
* @Description 自定义session会话存储的实现类 ,使用Redis来存储共享session,达到分布式部署目的
*/
@Bean("redisSessionDao")
public SessionDAO redisSessionDao(){
RedisSessionDao sessionDAO = new RedisSessionDao();
sessionDAO.setGlobalSessionTimeout(shiroRedisProperties.getGlobalSessionTimeout());
return sessionDAO;
}
/**
* @Description 会话管理器
*/
@Bean(name="sessionManager")
public ShiroSessionManager shiroSessionManager(){
ShiroSessionManager sessionManager = new ShiroSessionManager();
sessionManager.setSessionDAO(redisSessionDao());
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(simpleCookie());
sessionManager.setGlobalSessionTimeout(shiroRedisProperties.getGlobalSessionTimeout());
return sessionManager;
}
/**
* @Description 保证实现了Shiro内部lifecycle函数的bean执行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* @Description AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(defaultWebSecurityManager());
return new AuthorizationAttributeSourceAdvisor();
}
/**
* @Description 自定义拦截器定义
*/
private Map<String, Filter> filters() {
Map<String, Filter> map = new HashMap<String, Filter>();
map.put("role-or", new RolesOrAuthorizationFilter());
map.put("kicked-out", new KickedOutAuthorizationFilter(redissonClient(), redisSessionDao(), shiroSessionManager()));
map.put("jwt-authc", new JwtAuthcFilter(jwtTokenManager));
map.put("jwt-perms", new JwtPermsFilter());
map.put("jwt-roles", new JwtRolesFilter());
return map;
}
/**
* @Description Shiro过滤器
*/
@Bean("shiroFilter")
public CustomShiroFilterFactoryBean shiroFilterFactoryBean(){
CustomShiroFilterFactoryBean shiroFilter = new CustomShiroFilterFactoryBean();
shiroFilter.setSecurityManager(defaultWebSecurityManager());
shiroFilter.setChainResolver(filterChainResolver());
return shiroFilter;
}
@Bean
public CustomDefaultFilterChainManager defaultFilterChainManager(){
CustomDefaultFilterChainManager filterChainManager = new CustomDefaultFilterChainManager();
filterChainManager.setLoginUrl("/login");
filterChainManager.setUnauthorizedUrl("/login");
filterChainManager.setCustomFilters(filters());
return filterChainManager;
}
@Bean
CustomPathMatchingFilterChainResolver filterChainResolver(){
CustomPathMatchingFilterChainResolver pathMatchingFilterChainResolver = new CustomPathMatchingFilterChainResolver();
pathMatchingFilterChainResolver.setCustomDefaultFilterChainManager(defaultFilterChainManager());
return pathMatchingFilterChainResolver;
}
}
shiro-client作为jar的依赖,满足以下需求:
1、非侵入式:使用者只需要对jar依赖和做少量的配置,就可以达到统一鉴权的目标
2、可扩展性:用户除使用提供的过滤器外,可以轻松安自己的业务区定义过滤器
3、集中式管理:依赖jar之后,shiro-mgt后台可以同时管控多个平台的权限的认证、鉴权、及动态配置过滤器链
springboot-shiro-framework-client项目向上继承了springboot-shiro-framework-core项目,springboot-shiro-framework-core是主要实现认证、鉴权、过滤器定义、会话统一、realm缓存的核心项目。
springboot-shiro-framework-client项目以jar的方式被需要做权限控制的gateway项目所依赖,再由gateway通过对springboot-shiro-producer的dubbo消费,以达到统一认证、鉴权
springboot-shiro-framework-client模块实现了springboot-shiro-framework-core接口的3个类:
UserBridgeServiceImpl:提供用户基本资源操作的业务实现
FilterChainBridgeServiceImpl:提供过滤器链接口的查询
ResourceBridgeServiceImpl:提供资源查询
UserBridgeServiceImpl
package com.itheima.shiro.client;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.core.SimpleCacheManager;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleMapCache;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.face.UserAdapterFace;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.utils.ShiroUserUtil;
import com.itheima.shiro.vo.ResourceVo;
import com.itheima.shiro.vo.RoleVo;
import com.itheima.shiro.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.util.ByteSource;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Description 权限桥接器
*/
@Slf4j
@Component("userBridgeService")
public class UserBridgeServiceImpl implements UserBridgeService {
@Reference(version = "1.0.0")
private UserAdapterFace userAdapterFace;
@Autowired
private SimpleCacheManager simpleCacheManager;
@javax.annotation.Resource(name = "redissonClientForShiro")
private RedissonClient redissonClient;
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken,String realmName) {
SimpleToken token = (SimpleToken)authcToken;
UserVo user = this.findUserByLoginName(token.getUsername());
if(EmptyUtil.isNullOrEmpty(user)){
throw new UnknownAccountException("账号不存在");
}
ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);
String sessionId = ShiroUserUtil.getShiroSessionId();
String cacheKeyResourcesIds = CacheConstant.RESOURCES_KEY_IDS+sessionId;
shiroUser.setResourceIds(this.findResourcesIdsList(cacheKeyResourcesIds,user.getId()));
String salt = user.getSalt();
String password = user.getPassWord();
return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(salt), realmName);
}
@Override
public SimpleAuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser) {
UserVo user = BeanConv.toBean(shiroUser, UserVo.class);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String sessionId = ShiroUserUtil.getShiroSessionId();
//查询用户拥有的角色
String cacheKeyRole = CacheConstant.ROLE_KEY + sessionId;
info.addRoles(this.findRoleList(cacheKeyRole, user.getId()));
//查询用户拥有的资源
String cacheKeyResources = CacheConstant.RESOURCES_KEY + sessionId;
info.addStringPermissions(this.findResourcesList(cacheKeyResources, user.getId()));
return info;
}
@Override
public List<String> findRoleList(String cacheKeyRole, String userId) {
List<RoleVo> roles = new ArrayList<RoleVo>();
if (simpleCacheManager.getCache(cacheKeyRole) != null) {
roles = (List<RoleVo>) simpleCacheManager.getCache(cacheKeyRole).get(cacheKeyRole);
} else {
roles = userAdapterFace.findRoleByUserId(userId);
if (roles.size() > 0) {
//用户角色存放到map
Map<Object, Object> mapRole = new HashMap<Object, Object>();
mapRole.put(cacheKeyRole, roles);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheRole = new SimpleMapCache(cacheKeyRole, mapRole);
simpleCacheManager.createCache(cacheKeyRole, cacheRole);
}
}
List<String> rolesLabel = new ArrayList<String>();
for (RoleVo role : roles) {
rolesLabel.add(role.getLabel());
}
return rolesLabel;
}
@Override
public List<String> findResourcesList(String cacheKeyResources,String userId) {
List<ResourceVo> resourcesList = new ArrayList<ResourceVo>();
if (simpleCacheManager.getCache(cacheKeyResources) != null) {
resourcesList = (List<ResourceVo>) simpleCacheManager.getCache(cacheKeyResources).get(cacheKeyResources);
} else {
resourcesList = userAdapterFace.findResourceByUserId(userId);
if (resourcesList.size() > 0) {
//用户资源存放到map
Map<Object, Object> mapResource = new HashMap<Object, Object>();
mapResource.put(cacheKeyResources, resourcesList);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);
simpleCacheManager.createCache(cacheKeyResources, cacheResource);
}
}
List<String> resourcesLabel = new ArrayList<String>();
for (ResourceVo resources : resourcesList) {
resourcesLabel.add(resources.getLabel());
}
return resourcesLabel;
}
@Override
public UserVo findUserByLoginName(String loginName) {
String key = CacheConstant.FIND_USER_BY_LOGINNAME+loginName;
RBucket<UserVo> rBucket = redissonClient.getBucket(key);
UserVo user = rBucket.get();
if (!EmptyUtil.isNullOrEmpty(user)) {
return user;
}else {
user = userAdapterFace.findUserByLoginName(loginName);
if (!EmptyUtil.isNullOrEmpty(user)) {
rBucket.set(user, 300, TimeUnit.SECONDS);
return user;
}
}
rBucket.set(new UserVo(), 3, TimeUnit.SECONDS);
return null;
}
@Override
public List<String> findResourcesIdsList(String cacheKeyResources,String userId) {
List<ResourceVo> resourcesList = new ArrayList<ResourceVo>();
if (simpleCacheManager.getCache(cacheKeyResources) != null) {
resourcesList = (List<ResourceVo>) simpleCacheManager.getCache(cacheKeyResources).get(cacheKeyResources);
} else {
resourcesList = userAdapterFace.findResourceByUserId(userId);
if (resourcesList.size() > 0) {
//用户资源存放到map
Map<Object, Object> mapResource = new HashMap<Object, Object>();
mapResource.put(cacheKeyResources, resourcesList);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);
simpleCacheManager.createCache(cacheKeyResources, cacheResource);
}
}
List<String> resourcesLabel = new ArrayList<String>();
for (ResourceVo resources : resourcesList) {
resourcesLabel.add(resources.getId());
}
return resourcesLabel;
}
@Override
public void loadUserAuthorityToCache(ShiroUser user) {
String sessionId = user.getSessionId();
List<RoleVo> roles = userAdapterFace.findRoleByUserId(user.getId());
//创建角色cachaeKey
String cacheKeyRole = CacheConstant.ROLE_KEY + sessionId;
//用户角色存放到map
Map<Object, Object> mapRole = new HashMap<Object, Object>();
mapRole.put(cacheKeyRole, roles);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheRole = new SimpleMapCache(cacheKeyRole, mapRole);
simpleCacheManager.createCache(cacheKeyRole, cacheRole);
List<ResourceVo> resourcesList = userAdapterFace.findResourceByUserId(user.getId());
if (resourcesList.size() > 0) {
//创建资源cachaeKey
String cacheKeyResources = CacheConstant.RESOURCES_KEY + sessionId;
//用户资源存放到map
Map<Object, Object> mapResource = new HashMap<Object, Object>();
mapResource.put(cacheKeyResources, resourcesList);
//新建SimpleMapCache实例并放入缓存管理器
SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);
simpleCacheManager.createCache(cacheKeyResources, cacheResource);
}
}
}
FilterChainBridgeServiceImpl
package com.itheima.shiro.client;
import com.itheima.shiro.core.bridge.FilterChainBridgeService;
import com.itheima.shiro.face.FilterChainFace;
import com.itheima.shiro.vo.FilterChainVo;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Description:过滤器链查询
*/
@Component("filterChainBridgeService")
public class FilterChainBridgeServiceImpl implements FilterChainBridgeService {
@Reference(version = "1.0.0")
private FilterChainFace filterChainFace;
@Override
public List<FilterChainVo> findFilterChainList() {
return filterChainFace.findFilterChainList();
}
}
ResourceBridgeServiceImpl
package com.itheima.shiro.client;
import com.itheima.shiro.core.bridge.ResourceBridgeService;
import com.itheima.shiro.face.ResourceAdapterFace;
import com.itheima.shiro.vo.ResourceVo;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Description:查询资源
*/
@Component("resourceBridgeService")
public class ResourceBridgeServiceImpl implements ResourceBridgeService {
@Value("${itheima.resource.systemcode}")
private String systemCode;
@Reference(version = "1.0.0")
ResourceAdapterFace resourceAdapterFace;
@Override
public List<ResourceVo> findValidResourceVoAll(String systemCode) {
return resourceAdapterFace.findValidResourceVoAll(systemCode);
}
}
从中我们可以看到3个类调用了springboot-shiro-handler中提供的dubbo鉴权服务化内容
1、依赖springboot-shiro-framework-client实现认证、鉴权、过滤器定义、会话统一、realm缓存等功能
2、springboot-shiro-mgt管理后台持久化网关资源
3、springboot-shiro-handler实现网关资源查询服务化
4、gateway-service依据持久化的网关资源,动态创建消费端服务
这里在原有资源的基础上,增加的网关资源的管理:
1、定义网关systemcode,用以区分不同网关系统
2、定义访问的路径
3、定义资源的唯一标识,作为权限控制的标识
4、定义业务端dubbo服务端接口、目标方法、传入阐述、轮训算法、超时时间、重试次数等参数,这些内容会在gateway-service项目中解析
ResourceAdapterFace:网关资源服务接口
ResourceAdapterFaceImpl:网关资源服务接口实现
ResourceBridgeService:网关资源桥接器接口
ResourceBridgeServiceImpl:网关资源桥接器接口实现
ResourceAdapterFace
package com.itheima.shiro.face;
import com.itheima.shiro.vo.ResourceVo;
import java.util.List;
/**
* @Description:网关资源服务接口
*/
public interface ResourceAdapterFace {
/**
* @Description 获得当前系统是由有效的dubbo的资源
*/
List<ResourceVo> findValidResourceVoAll(String systemCode);
}
ResourceAdapterFaceImpl
package com.itheima.shiro.faceImpl;
import com.itheima.shiro.face.ResourceAdapterFace;
import com.itheima.shiro.pojo.Resource;
import com.itheima.shiro.service.ResourceService;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.vo.ResourceVo;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* @Description:网关资源服务接口实现
*/
@Service(version = "1.0.0", retries = 3,timeout = 5000)
public class ResourceAdapterFaceImpl implements ResourceAdapterFace {
@Autowired
ResourceService resourceService;
@Override
public List<ResourceVo> findValidResourceVoAll(String systemCode) {
List<Resource> resourceList = resourceService.findValidResourceVoAll(systemCode);
if (!EmptyUtil.isNullOrEmpty(resourceList)){
return BeanConv.toBeanList(resourceList, ResourceVo.class);
}
return null;
}
}
ResourceBridgeService
package com.itheima.shiro.core.bridge;
import com.itheima.shiro.vo.ResourceVo;
import java.util.List;
/**
* @Description:网关资源桥接器接口
*/
public interface ResourceBridgeService {
/**
* @Description 查询当前系统所有有效的DUBBO类型的服务
* @param systemCode 系统编号:与mgt添加系统编号相同
* @return
*/
public List<ResourceVo> findValidResourceVoAll(String systemCode);
}
ResourceBridgeServiceImpl
package com.itheima.shiro.client;
import com.itheima.shiro.core.bridge.ResourceBridgeService;
import com.itheima.shiro.face.ResourceAdapterFace;
import com.itheima.shiro.vo.ResourceVo;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Description:网关资源桥接器接口实现
*/
@Component("resourceBridgeService")
public class ResourceBridgeServiceImpl implements ResourceBridgeService {
@Value("${itheima.resource.systemcode}")
private String systemCode;
@Reference(version = "1.0.0")
ResourceAdapterFace resourceAdapterFace;
@Override
public List<ResourceVo> findValidResourceVoAll(String systemCode) {
return resourceAdapterFace.findValidResourceVoAll(systemCode);
}
}
CacheWare:缓存仓库
CacheWareService:缓存仓库服务接口
CacheWareServiceImpl:缓存仓库服务接口实现
CacheWareSyncService:缓存仓库同步服务接口
CacheWareSyncServiceImpl:缓存仓库同步服务接口实现
LoginAction:登录相应接口
GateWayController:相应层的统一入口
其主要负责:
1、缓存的清除
2、向map容器中创建缓存
3、获得缓存仓库执行对象
package com.itheima.shiro.cache;
import com.google.common.collect.Multimap;
import com.itheima.shiro.pojo.CacheWare;
/**
* @Description:缓存仓库服务
*/
public interface CacheWareService {
/**
* @Description 清除缓存
*/
void clearCacheWare();
/**
* @Description 向map容器中创建缓存
* @param CacheWareMap
*/
void createCacheWare(Multimap<String, CacheWare> CacheWareMap);
/**
* @Description 获得缓存仓库执行对象
* @param serviceName 服务名
* @param methodName 方法名
* @return {@link CacheWare}
*
*/
CacheWare queryCacheWare(String serviceName, String methodName);
}
CacheWareServiceImpl
package com.itheima.shiro.cache.impl;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.itheima.shiro.cache.CacheWareService;
import com.itheima.shiro.pojo.CacheWare;
import com.itheima.shiro.utils.EmptyUtil;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description:
*/
@Service("cacheWareService")
public class CacheWareServiceImpl implements CacheWareService {
private Multimap<String, CacheWare> cacheWareMaps = ArrayListMultimap.create();
/**
* 数据锁
*/
private static ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void clearCacheWare() {
try {
reentrantLock.lock();
cacheWareMaps.clear();
} finally {
reentrantLock.unlock();
}
}
@Override
public void createCacheWare(Multimap<String, CacheWare> CacheWareMap) {
try {
reentrantLock.lock();
this.cacheWareMaps = CacheWareMap;
} finally {
reentrantLock.unlock();
}
}
@Override
public CacheWare queryCacheWare(String serviceName, String methodName) {
if (EmptyUtil.isNullOrEmpty(serviceName) || EmptyUtil.isNullOrEmpty(serviceName)) {
return null;
}
StringBuffer serviceNameStringBuffer = new StringBuffer(serviceName);
StringBuffer methodNameStringBuffer = new StringBuffer(methodName);
String key = serviceNameStringBuffer.append(":").append(methodName).toString();
Collection<CacheWare> cacheWares = cacheWareMaps.get(key);
return EmptyUtil.isNullOrEmpty(cacheWares) ? null : cacheWares.iterator().next();
}
}
其主要职责:
1、启动时、调用CacheWareService的创建缓存方法初始化缓存仓库
2、同步缓存仓库
3、网关资源转化缓存仓库可执行对象
4、从dubbo中,初始化代理对象
注意:为了在多个网关系统下,接口转换的无干扰,读取的只是本网关所对应的资源
package com.itheima.shiro.cache;
import com.itheima.shiro.pojo.CacheWare;
import com.itheima.shiro.vo.ResourceVo;
/**
* @Description:缓存仓库同步刷新
*/
public interface CacheWareSyncService {
/**
* @Description 初始化缓存仓库
*/
void initCacheWare();
/**
* @Description 同步缓存仓库
*/
void refreshCacheWare();
/**
* @Description 资源转换缓存仓库对象
*/
CacheWare resourceConvCacheWare(ResourceVo resource);
/**
* @Description 初始化代理对象
* @param interfaceClass 接口
* @param loadbalance 算法
* @param version 版本
* @param timeout 超时时间
* @param retries 重试次数
*/
Object initProxy(Class<?> interfaceClass,
String loadbalance,
String version,
Integer timeout,
Integer retries);
/**
* @Description 回收资源
*/
void destoryCacheWare();
}
CacheWareSyncServiceImpl
package com.itheima.shiro.cache.impl;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.itheima.shiro.cache.CacheWareService;
import com.itheima.shiro.cache.CacheWareSyncService;
import com.itheima.shiro.core.bridge.ResourceBridgeService;
import com.itheima.shiro.face.ResourceAdapterFace;
import com.itheima.shiro.pojo.CacheWare;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.vo.ResourceVo;
import lombok.extern.log4j.Log4j2;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.utils.ReferenceConfigCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Description:
*/
@Service("cacheWareSyncService")
@Log4j2
public class CacheWareSyncServiceImpl implements CacheWareSyncService {
@Value("${itheima.resource.systemcode}")
private String systemCode;
@Autowired
ResourceBridgeService resourceBridgeService;
@Autowired
CacheWareService cacheWareService;
@Autowired
private ApplicationConfig applicationConfig;
@Autowired
private RegistryConfig registryConfig;
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
@Override
@PostConstruct
public void initCacheWare() {
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
refreshCacheWare();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}, 0, 2, TimeUnit.MINUTES);
}
@Override
public void refreshCacheWare() {
List<ResourceVo> resources = resourceBridgeService.findValidResourceVoAll(systemCode);
//如果当前系统没有资源,则清空
if (CollectionUtils.isEmpty(resources)) {
log.warn("No apis can be used.");
cacheWareService.clearCacheWare();
return;
}
//构建执行集合
Multimap<String, CacheWare> cacheWareMaps = ArrayListMultimap.create();
for (ResourceVo resource : resources) {
if (EmptyUtil.isNullOrEmpty(resource.getServiceName())
||EmptyUtil.isNullOrEmpty(resource.getMethodName())){
log.warn("{} not found serviceName or methodName",resources.toString());
continue;
}
CacheWare cacheWare = resourceConvCacheWare(resource);
if (!EmptyUtil.isNullOrEmpty(cacheWare)){
cacheWareMaps.put(cacheWare.getServiceName()+":"+cacheWare.getMethodName(), cacheWare);
}
}
cacheWareService.createCacheWare(cacheWareMaps);
}
@Override
public CacheWare resourceConvCacheWare(ResourceVo resource) {
//获得类型
Class<?> serviceClass = null;
try {
serviceClass = Class.forName(resource.getServiceName());
} catch (ClassNotFoundException e) {
log.error("容器中未发现:{}接口类",resource.getServiceName());
return null;
}
String serviceName = resource.getServiceName().substring(resource.getServiceName().lastIndexOf(".")+1).toLowerCase();
Method[] methods = serviceClass.getDeclaredMethods();
Method methodTarget = null;
//获得方法
for (Method method : methods) {
if (method.getName().equals(resource.getMethodName())) {
methodTarget = method;
break;
}
}
// 未在接口类中找到方法
if (methodTarget == null) {
log.warn("{} not found in {}", resource.getMethodName(), resource.getServiceName());
return null;
}
//获得方法上的参数
Class<?>[] methodParamsClasss = methodTarget.getParameterTypes();
Class<?> methodParamClasssTarget = null;
for (Class<?> methodParamsClass : methodParamsClasss) {
if (methodParamsClass.getName().equals(resource.getMethodParam())) {
methodParamClasssTarget = methodParamsClass;
break;
}
}
//初始化代理类
Object proxy = initProxy(serviceClass, resource.getLoadbalance(), resource.getDubboVersion(), resource.getTimeout(), resource.getRetries());
if (proxy == null) {
log.warn("{} not found in proxy", resource.getServiceName());
return null;
}
//构建CacheWare对象
CacheWare cacheWare = CacheWare.builder()
.serviceName(serviceName)
.methodName(resource.getMethodName())
.method(methodTarget)
.methodParamsClass(methodParamClasssTarget)
.proxy(proxy)
.build();
return cacheWare;
}
@Override
public Object initProxy(Class<?> interfaceClass,
String loadbalance,
String version,
Integer timeout,
Integer retries) {
ReferenceConfig<Object> reference = new ReferenceConfig<Object>();
reference.setApplication(applicationConfig);
reference.setRegistry(registryConfig);
reference.setLoadbalance(EmptyUtil.isNullOrEmpty(loadbalance)?"random":loadbalance);
reference.setInterface(interfaceClass);
reference.setVersion(version);
reference.setTimeout(EmptyUtil.isNullOrEmpty(timeout)?20000:timeout);
reference.setCheck(false);
reference.setRetries(EmptyUtil.isNullOrEmpty(retries)?0:retries);
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
return cache.get(reference);
}
@Override
@PreDestroy
public void destoryCacheWare() {
executor.shutdownNow();
}
}
其主要负责:
1、传入参数处理
2、获得可执行缓存仓库
3、执行远程服务
4、处理返回结果
package com.itheima.shiro.web;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.itheima.shiro.base.BaseRequest;
import com.itheima.shiro.cache.CacheWareService;
import com.itheima.shiro.constant.GateWayConstant;
import com.itheima.shiro.pojo.CacheWare;
import com.itheima.shiro.response.MultiResponse;
import com.itheima.shiro.response.PageResponse;
import com.itheima.shiro.response.SingleResponse;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.view.JsonResult;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.websocket.server.PathParam;
import java.lang.reflect.Method;
/**
* @Description:网关统一入口
*/
@Controller
@Log4j2
public class GateWayController {
@Autowired
CacheWareService cacheWareService;
@RequestMapping(value = "{serviceName}/{methodName}", method = RequestMethod.POST)
@ResponseBody
public JsonResult postGateWay(@PathVariable("serviceName") String serviceName,
@PathVariable("methodName") String methodName,
@RequestBody BaseRequest baseRequest) throws Exception {
Object datas = baseRequest.getDatas();
JsonResult jsonResult = null;
if (EmptyUtil.isNullOrEmpty(serviceName)||EmptyUtil.isNullOrEmpty(methodName)){
jsonResult = JsonResult.builder()
.result(GateWayConstant.FAIL)
.msg("参数缺失")
.code(GateWayConstant.PARAMETERS_MISSING)
.build();
return jsonResult;
}
//1、传入参数处理
JSONObject datasJson = null;
if (!EmptyUtil.isNullOrEmpty(datas)){
datasJson = JSONObject.parseObject(JSONObject.toJSONString(datas));
}
//2、获得可执行缓存仓库可执行对象
CacheWare cacheWare = cacheWareService.queryCacheWare(serviceName, methodName);
if (EmptyUtil.isNullOrEmpty(serviceName)||EmptyUtil.isNullOrEmpty(methodName)){
jsonResult = JsonResult.builder()
.result(GateWayConstant.FAIL)
.msg("请求链接异常")
.code(GateWayConstant.URL_MISSING)
.build();
return jsonResult;
}
//3、执行远程服务
Object proxy = cacheWare.getProxy();
Method method = cacheWare.getMethod();
Class<?> methodParamsClass = cacheWare.getMethodParamsClass();
Object result;
if (EmptyUtil.isNullOrEmpty(methodParamsClass)){
result = method.invoke(proxy);
}else {
Object arguments = JSONObject.toJavaObject(datasJson, methodParamsClass);
result = method.invoke(proxy,arguments);
}
//4、处理返回结果
return convResult(result);
}
/**
* @Description 处理请求结果
*/
private JsonResult convResult(Object result) {
JsonResult jsonResult = JsonResult.builder()
.result(GateWayConstant.SUCCEED)
.msg("相应正常")
.code(GateWayConstant.SUCCEED_CODE)
.build();
if (EmptyUtil.isNullOrEmpty(result)) {
jsonResult = JsonResult.builder()
.result(GateWayConstant.FAIL)
.msg("返回结果为空")
.code(GateWayConstant.RESULT_ISNULLOREMPTY)
.build();
return jsonResult;
}
if (result instanceof SingleResponse) {
BeanUtils.copyProperties(result, jsonResult);
@SuppressWarnings("rawtypes")
SingleResponse singleResponse = (SingleResponse) result;
jsonResult.setDatas(singleResponse.getValue());
} else if (result instanceof MultiResponse) {
BeanUtils.copyProperties(result, jsonResult);
@SuppressWarnings("rawtypes")
MultiResponse multiResponse = (MultiResponse) result;
jsonResult.setDatas(multiResponse.getValues());
} else if (result instanceof PageResponse) {
BeanUtils.copyProperties(result, jsonResult);
PageResponse pageResponse = (PageResponse)result;
jsonResult.setDatas( pageResponse.getValues());
} else {
jsonResult = JsonResult.builder()
.result(GateWayConstant.FAIL)
.msg("返回结果格式不正确")
.code(GateWayConstant.RESULT_MISSING)
.build();
return jsonResult;
}
return jsonResult;
}
}
通过上面的模块依赖关系,我们可以看出,shiro-mgt管理平台也是依赖springboot-shiro-framework-client项目实现权限的校验,而他本身主要是负责对角色、资源、用户、过滤器链的CRUD,来实现各个网关平台的权限控制。
资源:
1、定义了网关systemcode,用以区分不同网关、系统
2、定义了访问的路径
3、定义了资源的唯一标识,作为过滤器过滤的标记
4、定义dubbo服务端接口的解析、同时为每个服务定义:轮训算法、超时时间、重试次数等参数,这些参数会在shiro-gateway中解析
角色:
1、定义角色的唯一标识,作为过滤器过滤的标记
2、为角色定义多个资源
用户:
1、用户基本信息
2、为用户定义多个角色
过滤器链:
1、为所有系统定义统一的过滤器链路管理(可以扩展:按资源类型那样为每个网关系统定义过滤器链)
2、保证过滤器器链的有序性