shiro session_java:shiro应用篇——2

第十一章 分布式统一权限系统

1、系统需求

【1】前后端分离

在第十章中我们已经实现,使用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;        }    }}

【2】集中式会话

在第七章中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集中式管理~ */@Log4j2public 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 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 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 sessionIdRBucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+session.getId().toString());        sessionIdRBucket.delete();    }    @Override    public Collection getActiveSessions() {        return Collections.emptySet();      }    @Override    public void update(Session session) {        RBucket 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;    }}

【3】认证与鉴权服务化

第六章中,我们实现了realm的缓存机制,这里我们会把UserBridgeService使用dubbo服务化

50dc8b9d759fac7b8918183642b5f767.png

其目的使得实际项目中的认证与鉴权走dubbo,减少服务器压力

【4】动态过滤器链

在第十章中,我们加载过滤器链的方式

#静态资源不过滤/static/**=anon#登录链接不过滤/login/**=anon#访问/resource/**需要有admin的角色#/resource/**=role-or[MangerRole,SuperAdmin]#/role/** =jwt-roles[SuperAdmin]/resource/** =jwt-perms[role:listInitialize]#其他链接是需要登录的/**=kicked-out,jwt-authc

在统一鉴权系统中,我们不可能每次发布新的过滤器链,就去重启服务器,我们更希望可以动态管理过滤器链

【5】权限客户端

shiro-client作为jar的依赖,满足以下需求:

1、非侵入式:使用者只需要对jar依赖和做少量的配置,就可以达到统一鉴权的目标

2、可扩展性:用户除使用提供的过滤器外,可以轻松按自己的业务去定义过滤器

3、集中式管理:依赖jar之后,shiro-mgt后台可以同时管控多个平台的权限的认证、鉴权、及动态配置过滤器链

【6】网关平台

springboot-shiro-gateway:

1、依赖shiro-client项目作为权限的被控制层

2、实现dubbo传输协议到HTTP传输协议的转化,当然这里提供的为通用的转换方式。

3、可复制、复制后只需要在shiro-mgt后台中做简单的配置,就可以实现一个新网关的接入

2、架构设计

【1】系统网络通讯

7f669e3909899e998f6d9c1ff144eb2f.png

1、网关服务集群性,同时实现会话的统一管理

2、鉴权服务集群化,提供统一鉴权服务

3、管理后台集群化

【2】模块依赖关系

【1.1】springboot-shiro-parent

springboot-shiro-parent:项目统一jar和plugIn的POM定义

deb625428e57a5d10e39868c439c71d6.png

【1.2】springboot-shiro-gateway-handler

1、dubbo业务服务转换http通讯

​ 2、认证与鉴权服务化消费者

​ 3、生成业务服务化消费者

b8b9d09f76a9e9802f4b7965c946e317.png

【1.3】springboot-shiro-producer

​ 认证与鉴权服务化的生成者

599e3fc8e3b4778530cde3aea7baac0f.png

【1.4】springboot-shiro-mgt

​ 认证与鉴权服务化消费者

c127be45aff843421365a6dc9585b09b.png

【1.5】springboot-shiro-dubbo-app-handler

​ 生产业务服务化生产者

3262f9818b51ac54b053143e4364dff4.png

3、认证鉴权服务化

6f93a7130fcfd807e95a495335ae7a43.png

上面的图解中我们可以看到,这里服务化的为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 findRoleByUserId(String userId);    /**     * @Description 查询用户有那些资源     * @param userId 用户Id     * @return     */    List 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 findRoleByUserId(String userId) {        List list = userAdapter.findRoleByUserId(userId);        if (!EmptyUtil.isNullOrEmpty(list)){            return BeanConv.toBeanList(list, RoleVo.class);        }        return null;    }    @Override    public List findResourceByUserId(String userId) {        List 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 findRoleList(String cacheKeyRole, String userId) {        List roles = new ArrayList();        if (simpleCacheManager.getCache(cacheKeyRole) != null) {            roles = (List) simpleCacheManager.getCache(cacheKeyRole).get(cacheKeyRole);        } else {            roles = userAdapterFace.findRoleByUserId(userId);            if (roles.size() > 0) {                //用户角色存放到map                Map mapRole = new HashMap();                mapRole.put(cacheKeyRole, roles);                //新建SimpleMapCache实例并放入缓存管理器                SimpleMapCache cacheRole = new SimpleMapCache(cacheKeyRole, mapRole);                simpleCacheManager.createCache(cacheKeyRole, cacheRole);            }        }        List rolesLabel = new ArrayList();        for (RoleVo role : roles) {            rolesLabel.add(role.getLabel());        }        return rolesLabel;    }    @Override    public List findResourcesList(String cacheKeyResources,String userId) {        List resourcesList = new ArrayList();        if (simpleCacheManager.getCache(cacheKeyResources) != null) {            resourcesList = (List) simpleCacheManager.getCache(cacheKeyResources).get(cacheKeyResources);        } else {            resourcesList = userAdapterFace.findResourceByUserId(userId);            if (resourcesList.size() > 0) {                //用户资源存放到map                Map mapResource = new HashMap();                mapResource.put(cacheKeyResources, resourcesList);                //新建SimpleMapCache实例并放入缓存管理器                SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);                simpleCacheManager.createCache(cacheKeyResources, cacheResource);            }        }        List resourcesLabel = new ArrayList();        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 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 findResourcesIdsList(String cacheKeyResources,String userId) {        List resourcesList = new ArrayList();        if (simpleCacheManager.getCache(cacheKeyResources) != null) {            resourcesList = (List) simpleCacheManager.getCache(cacheKeyResources).get(cacheKeyResources);        } else {            resourcesList = userAdapterFace.findResourceByUserId(userId);            if (resourcesList.size() > 0) {                //用户资源存放到map                Map mapResource = new HashMap();                mapResource.put(cacheKeyResources, resourcesList);                //新建SimpleMapCache实例并放入缓存管理器                SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);                simpleCacheManager.createCache(cacheKeyResources, cacheResource);            }        }        List resourcesLabel = new ArrayList();        for (ResourceVo resources : resourcesList) {            resourcesLabel.add(resources.getId());        }        return resourcesLabel;    }    @Override    public void loadUserAuthorityToCache(ShiroUser user) {        String sessionId = user.getSessionId();        List roles = userAdapterFace.findRoleByUserId(user.getId());        //创建角色cachaeKey        String cacheKeyRole = CacheConstant.ROLE_KEY + sessionId;        //用户角色存放到map        Map mapRole = new HashMap();        mapRole.put(cacheKeyRole, roles);        //新建SimpleMapCache实例并放入缓存管理器        SimpleMapCache cacheRole = new SimpleMapCache(cacheKeyRole, mapRole);        simpleCacheManager.createCache(cacheKeyRole, cacheRole);        List resourcesList = userAdapterFace.findResourceByUserId(user.getId());        if (resourcesList.size() > 0) {            //创建资源cachaeKey            String cacheKeyResources = CacheConstant.RESOURCES_KEY + sessionId;            //用户资源存放到map            Map mapResource = new HashMap();            mapResource.put(cacheKeyResources, resourcesList);            //新建SimpleMapCache实例并放入缓存管理器            SimpleMapCache cacheResource = new SimpleMapCache(cacheKeyResources, mapResource);            simpleCacheManager.createCache(cacheKeyResources, cacheResource);        }    }}

通过上面的改造,我们可以发现:用户在认证与鉴权时走的都是dubbo的服务,而在实际业务项目中不会再去操作鉴权相关的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值