SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录



一. 说明

Shiro是一个安全框架,项目中主要用它做认证,授权,加密,以及用户的会话管理,虽然Shiro没有SpringSecurity功能更丰富,但是它轻量,简单,在项目中通常业务需求Shiro也都能胜任.

二. 项目环境

  • MyBatis-Plus版本: 3.1.0
  • SpringBoot版本:2.1.5
  • JDK版本:1.8
  • Shiro版本:1.4
  • Shiro-redis插件版本:3.1.0

数据表(SQL文件在项目中):数据库中测试号的密码进行了加密,密码皆为123456

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

Maven依赖如下:


























































<code><dependencies>        <dependency>            <groupid>org.springframework.boot/<groupid>            <artifactid>spring-boot-starter-web/<artifactid>        /<dependency>        <dependency>            <groupid>mysql/<groupid>            <artifactid>mysql-connector-java/<artifactid>            <scope>runtime/<scope>        /<dependency>                <dependency>            <groupid>org.springframework.boot/<groupid>            <artifactid>spring-boot-starter-aop/<artifactid>        /<dependency>                <dependency>            <groupid>org.projectlombok/<groupid>            <artifactid>lombok/<artifactid>            <optional>true/<optional>        /<dependency>                <dependency>            <groupid>org.springframework.boot/<groupid>            <artifactid>spring-boot-starter-data-redis-reactive/<artifactid>        /<dependency>                <dependency>            <groupid>com.baomidou/<groupid>            <artifactid>mybatis-plus-boot-starter/<artifactid>            <version>3.1.0/<version>        /<dependency>                <dependency>            <groupid>com.alibaba/<groupid>            <artifactid>druid/<artifactid>            <version>1.1.6/<version>        /<dependency>                <dependency>            <groupid>org.apache.shiro/<groupid>            <artifactid>shiro-spring/<artifactid>            <version>1.4.0/<version>        /<dependency>                <dependency>            <groupid>org.crazycake/<groupid>            <artifactid>shiro-redis/<artifactid>            <version>3.1.0/<version>        /<dependency>                <dependency>            <groupid>org.apache.commons/<groupid>            <artifactid>commons-lang3/<artifactid>            <version>3.5/<version>        /<dependency>/<dependencies>/<code>

配置如下:












































<code># 配置端口server:  port: 8764spring:  # 配置数据源  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/my_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false    username: root    password: root    type: com.alibaba.druid.pool.DruidDataSource  # Redis数据源  redis:    host: localhost    port: 6379    timeout: 6000    password: 123456    jedis:      pool:        max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)        max-wait: -1      # 连接池最大阻塞等待时间(使用负值表示没有限制)        max-idle: 10      # 连接池中的最大空闲连接        min-idle: 5       # 连接池中的最小空闲连接# mybatis-plus相关配置mybatis-plus:  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)  mapper-locations: classpath:mapper/*.xml  # 以下配置均有默认值,可以不设置  global-config:    db-config:      #主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";      id-type: auto      #字段策略 IGNORED:"忽略判断"  NOT_NULL:"非 NULL 判断")  NOT_EMPTY:"非空判断"      field-strategy: NOT_EMPTY      #数据库类型      db-type: MYSQL  configuration:    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射    map-underscore-to-camel-case: true    # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段    call-setters-on-nulls: true    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl/<code> 

三. 编写项目基础类

用户实体,Dao,Service等在这里省略,请参考源码

编写Exception类来处理Shiro权限拦截异常























<code>/** * @Description 自定义异常 * @Author Sans * @CreateTime 2019/6/15 22:56 */@ControllerAdvicepublic class MyShiroException {    /**     * 处理Shiro权限拦截异常     * 如果返回JSON数据格式请加上 @ResponseBody注解     * @Author Sans     * @CreateTime 2019/6/15 13:35     * @Return Map<object> 返回结果集     */    @ResponseBody    @ExceptionHandler(value = AuthorizationException.class)    public Map<string> defaultErrorHandler(){        Map<string> map = new HashMap<>();        map.put("403","权限不足");        return map;    }}/<string>/<string>/<object>/<code>

创建SHA256Util加密工具


















<code>/** * @Description Sha-256加密工具 * @Author Sans * @CreateTime 2019/6/12 9:27 */public class SHA256Util {    /**  私有构造器 **/    private SHA256Util(){};    /**  加密算法 **/    public final static String HASH_ALGORITHM_NAME = "SHA-256";    /**  循环次数 **/    public final static int HASH_ITERATIONS = 15;    /**  执行加密-采用SHA256和盐值加密 **/    public static String sha256(String password, String salt) {        return new SimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();    }}/<code>

创建Spring工具




























<code>/** * @Description Spring上下文工具类 * @Author Sans * @CreateTime 2019/6/17 13:40 */@Componentpublic class SpringUtil implements ApplicationContextAware {    private static ApplicationContext context;    /**     * Spring在bean初始化后会判断是不是ApplicationContextAware的子类     * 如果该类是,setApplicationContext()方法,会将容器中ApplicationContext作为参数传入进去     * @Author Sans     * @CreateTime 2019/6/17 16:58     */    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        context = applicationContext;    }    /**     * 通过Name返回指定的Bean     * @Author Sans     * @CreateTime 2019/6/17 16:03     */    public static  T getBean(Class beanClass) {        return context.getBean(beanClass);    }}/<code>

创建Shiro工具



















































































<code>/** * @Description Shiro工具类 * @Author Sans * @CreateTime 2019/6/15 16:11 */public class ShiroUtils {    /** 私有构造器 **/    private ShiroUtils(){}    private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);    /**     * 获取当前用户Session     * @Author Sans     * @CreateTime 2019/6/17 17:03     * @Return SysUserEntity 用户信息     */    public static Session getSession() {        return SecurityUtils.getSubject().getSession();    }    /**     * 用户登出     * @Author Sans     * @CreateTime 2019/6/17 17:23     */    public static void logout() {        SecurityUtils.getSubject().logout();    }    /**    * 获取当前用户信息    * @Author Sans    * @CreateTime 2019/6/17 17:03    * @Return SysUserEntity 用户信息    */    public static SysUserEntity getUserInfo() {      return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();    }    /**     * 删除用户缓存信息     * @Author Sans     * @CreateTime 2019/6/17 13:57     * @Param  username  用户名称     * @Param  isRemoveSession 是否删除Session     * @Return void     */    public static void deleteCache(String username, boolean isRemoveSession){        //从缓存中获取Session        Session session = null;        Collection<session> sessions = redisSessionDAO.getActiveSessions();        SysUserEntity sysUserEntity;        Object attribute = null;        for(Session sessionInfo : sessions){            //遍历Session,找到该用户名称对应的Session            attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);            if (attribute == null) {                continue;            }            sysUserEntity = (SysUserEntity) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();            if (sysUserEntity == null) {                continue;            }            if (Objects.equals(sysUserEntity.getUsername(), username)) {                session=sessionInfo;            }        }        if (session == null||attribute == null) {            return;        }        //删除session        if (isRemoveSession) {            redisSessionDAO.delete(session);        }        //删除Cache,在访问受限接口时会重新授权        DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();        Authenticator authc = securityManager.getAuthenticator();        ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);    }}/<session>/<code>

创建Shiro的SessionId生成器


















<code>/** * @Description 自定义SessionId生成器 * @Author Sans * @CreateTime 2019/6/11 11:48 */public class ShiroSessionIdGenerator implements SessionIdGenerator {    /**     * 实现SessionId生成     * @Author Sans     * @CreateTime 2019/6/11 11:54     */    @Override    public Serializable generateId(Session session) {        Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);        return String.format("login_token_%s", sessionId);    }}/<code>

四. 编写Shiro核心类











































































<code>/** * @Description Shiro权限匹配和账号密码匹配 * @Author Sans * @CreateTime 2019/6/15 11:27 */public class ShiroRealm extends AuthorizingRealm {    @Autowired    private SysUserService sysUserService;    @Autowired    private SysRoleService sysRoleService;    @Autowired    private SysMenuService sysMenuService;    /**     * 授权权限     * 用户进行权限验证时候Shiro会去缓存中找,如果查不到数据,会执行这个方法去查权限,并放入缓存中     * @Author Sans     * @CreateTime 2019/6/12 11:44     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();        SysUserEntity sysUserEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal();        //获取用户ID        Long userId =sysUserEntity.getUserId();        //这里可以进行授权和处理        Set<string> rolesSet = new HashSet<>();        Set<string> permsSet = new HashSet<>();        //查询角色和权限(这里根据业务自行查询)        List<sysroleentity> sysRoleEntityList = sysRoleService.selectSysRoleByUserId(userId);        for (SysRoleEntity sysRoleEntity:sysRoleEntityList) {            rolesSet.add(sysRoleEntity.getRoleName());            List<sysmenuentity> sysMenuEntityList = sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId());            for (SysMenuEntity sysMenuEntity :sysMenuEntityList) {                permsSet.add(sysMenuEntity.getPerms());            }        }        //将查到的权限和角色分别传入authorizationInfo中        authorizationInfo.setStringPermissions(permsSet);        authorizationInfo.setRoles(rolesSet);        return authorizationInfo;    }        /**     * 身份认证     * @Author Sans     * @CreateTime 2019/6/12 12:36     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {        //获取用户的输入的账号.        String username = (String) authenticationToken.getPrincipal();        //通过username从数据库中查找 User对象,如果找到进行验证        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法        SysUserEntity user = sysUserService.selectUserByName(username);        //判断账号是否存在        if (user == null) {            throw new AuthenticationException();        }        //判断账号是否被冻结        if (user.getState()==null||user.getState().equals("PROHIBIT")){            throw new LockedAccountException();        }        //进行验证        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(                user,                                  //用户名                user.getPassword(),                    //密码                ByteSource.Util.bytes(user.getSalt()), //设置盐值                getName()        );        //验证成功开始踢人(清除缓存和Session)        ShiroUtils.deleteCache(username,true);        return authenticationInfo;    }}/<sysmenuentity>/<sysroleentity>/<string>/<string>/<code> 

创建SessionManager类




































<code>/** * @Description 自定义获取Token * @Author Sans * @CreateTime 2019/6/13 8:34 */public class ShiroSessionManager extends DefaultWebSessionManager {    //定义常量    private static final String AUTHORIZATION = "Authorization";    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";    //重写构造器    public ShiroSessionManager() {        super();        this.setDeleteInvalidSessions(true);    }    /**     * 重写方法实现从请求头获取Token便于接口统一     * 每次请求进来,Shiro会去从请求头找Authorization这个key对应的Value(Token)     * @Author Sans     * @CreateTime 2019/6/13 8:47     */    @Override    public Serializable getSessionId(ServletRequest request, ServletResponse response) {        String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);        //如果请求头中存在token 则从请求头中获取token        if (!StringUtils.isEmpty(token)) {            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);            return token;        } else {            //否则按默认规则从cookie取token            return super.getSessionId(request, response);        }    }}/<code>

创建ShiroConfig配置类













































































































































































<code>/** * @Description Shiro配置类 * @Author Sans * @CreateTime 2019/6/10 17:42 */@Configurationpublic class ShiroConfig {    private final String CACHE_KEY = "shiro:cache:";    private final String SESSION_KEY = "shiro:session:";    private final int EXPIRE = 1800;    //Redis配置    @Value("${spring.redis.host}")    private String host;    @Value("${spring.redis.port}")    private int port;    @Value("${spring.redis.timeout}")    private int timeout;    @Value("${spring.redis.password}")    private String password;    /**     * 开启Shiro-aop注解支持     * @Attention 使用代理方式所以需要开启代码支持     * @Author Sans     * @CreateTime 2019/6/12 8:38     */    @Bean    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);        return authorizationAttributeSourceAdvisor;    }    /**     * Shiro基础配置     * @Author Sans     * @CreateTime 2019/6/12 8:42     */    @Bean    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        shiroFilterFactoryBean.setSecurityManager(securityManager);        Map<string> filterChainDefinitionMap = new LinkedHashMap<>();        // 注意过滤器配置顺序不能颠倒        // 配置过滤:不会被拦截的链接        filterChainDefinitionMap.put("/static/**", "anon");        filterChainDefinitionMap.put("/userLogin/**", "anon");        filterChainDefinitionMap.put("/**", "authc");        // 配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据        shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);        return shiroFilterFactoryBean;    }    /**     * 安全管理器     * @Author Sans     * @CreateTime 2019/6/12 10:34     */    @Bean    public SecurityManager securityManager() {        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        // 自定义Ssession管理        securityManager.setSessionManager(sessionManager());        // 自定义Cache实现        securityManager.setCacheManager(cacheManager());        // 自定义Realm验证        securityManager.setRealm(shiroRealm());        return securityManager;    }    /**     * 身份验证器     * @Author Sans     * @CreateTime 2019/6/12 10:37     */    @Bean    public ShiroRealm shiroRealm() {        ShiroRealm shiroRealm = new ShiroRealm();        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());        return shiroRealm;    }    /**     * 凭证匹配器     * 将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置     * @Author Sans     * @CreateTime 2019/6/12 10:48     */    @Bean    public HashedCredentialsMatcher hashedCredentialsMatcher() {        HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();        // 散列算法:这里使用SHA256算法;        shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);        // 散列的次数,比如散列两次,相当于 md5(md5(""));        shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);        return shaCredentialsMatcher;    }    /**     * 配置Redis管理器     * @Attention 使用的是shiro-redis开源插件     * @Author Sans     * @CreateTime 2019/6/12 11:06     */    @Bean    public RedisManager redisManager() {        RedisManager redisManager = new RedisManager();        redisManager.setHost(host);        redisManager.setPort(port);        redisManager.setTimeout(timeout);        redisManager.setPassword(password);        return redisManager;    }    /**     * 配置Cache管理器     * 用于往Redis存储权限和角色标识     * @Attention 使用的是shiro-redis开源插件     * @Author Sans     * @CreateTime 2019/6/12 12:37     */    @Bean    public RedisCacheManager cacheManager() {        RedisCacheManager redisCacheManager = new RedisCacheManager();        redisCacheManager.setRedisManager(redisManager());        redisCacheManager.setKeyPrefix(CACHE_KEY);        // 配置缓存的话要求放在session里面的实体类必须有个id标识        redisCacheManager.setPrincipalIdFieldName("userId");        return redisCacheManager;    }    /**     * SessionID生成器     * @Author Sans     * @CreateTime 2019/6/12 13:12     */    @Bean    public ShiroSessionIdGenerator sessionIdGenerator(){        return new ShiroSessionIdGenerator();    }    /**     * 配置RedisSessionDAO     * @Attention 使用的是shiro-redis开源插件     * @Author Sans     * @CreateTime 2019/6/12 13:44     */    @Bean    public RedisSessionDAO redisSessionDAO() {        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();        redisSessionDAO.setRedisManager(redisManager());        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());        redisSessionDAO.setKeyPrefix(SESSION_KEY);        redisSessionDAO.setExpire(expire);        return redisSessionDAO;    }    /**     * 配置Session管理器     * @Author Sans     * @CreateTime 2019/6/12 14:25     */    @Bean    public SessionManager sessionManager() {        ShiroSessionManager shiroSessionManager = new ShiroSessionManager();        shiroSessionManager.setSessionDAO(redisSessionDAO());        return shiroSessionManager;    }}/<string>/<code> 

五. 实现权限控制

Shiro可以用代码或者注解来控制权限,通常我们使用注解控制,不仅简单方便,而且更加灵活.Shiro注解一共有五个:

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

一般情况下我们在项目中做权限控制,使用最多的是RequiresPermissions和RequiresRoles,允许存在多个角色和权限,默认逻辑是AND,也就是同时拥有这些才可以访问方法,可以在注解中以参数的形式设置成OR

示例:





<code>//拥有一个角色就可以访问@RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)//拥有所有权限才可以访问@RequiresPermissions(value={"sys:user:info","sys:role:info"},logical = Logical.AND)/<code>

使用顺序:Shiro注解是存在顺序的,当多个注解在一个方法上的时候,会逐个检查,知道全部通过为止,默认拦截顺序是:RequiresRoles->RequiresPermissions->RequiresAuthentication->RequiresUser->RequiresGuest

示例:




<code>//拥有ADMIN角色同时还要有sys:role:info权限@RequiresRoles(value={"ADMIN")@RequiresPermissions("sys:role:info")/<code>

创建UserRoleController角色拦截测试类

















































































<code>/** * @Description 角色测试 * @Author Sans * @CreateTime 2019/6/19 11:38 */@RestController@RequestMapping("/role")public class UserRoleController {    @Autowired    private SysUserService sysUserService;    @Autowired    private SysRoleService sysRoleService;    @Autowired    private SysMenuService sysMenuService;    @Autowired    private SysRoleMenuService sysRoleMenuService;    /**     * 管理员角色测试接口     * @Author Sans     * @CreateTime 2019/6/19 10:38     * @Return Map<string> 返回结果     */    @RequestMapping("/getAdminInfo")    @RequiresRoles("ADMIN")    public Map<string> getAdminInfo(){        Map<string> map = new HashMap<>();        map.put("code",200);        map.put("msg","这里是只有管理员角色能访问的接口");        return map;    }    /**     * 用户角色测试接口     * @Author Sans     * @CreateTime 2019/6/19 10:38     * @Return Map<string> 返回结果     */    @RequestMapping("/getUserInfo")    @RequiresRoles("USER")    public Map<string> getUserInfo(){        Map<string> map = new HashMap<>();        map.put("code",200);        map.put("msg","这里是只有用户角色能访问的接口");        return map;    }    /**     * 角色测试接口     * @Author Sans     * @CreateTime 2019/6/19 10:38     * @Return Map<string> 返回结果     */    @RequestMapping("/getRoleInfo")    @RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)    @RequiresUser    public Map<string> getRoleInfo(){        Map<string> map = new HashMap<>();        map.put("code",200);        map.put("msg","这里是只要有ADMIN或者USER角色能访问的接口");        return map;    }    /**     * 登出(测试登出)     * @Author Sans     * @CreateTime 2019/6/19 10:38     * @Return Map<string> 返回结果     */    @RequestMapping("/getLogout")    @RequiresUser    public Map<string> getLogout(){        ShiroUtils.logout();        Map<string> map = new HashMap<>();        map.put("code",200);        map.put("msg","登出");        return map;    }}/<string>/<string>/<string>/<string>/<string>/<string>/<string>/<string>/<string>/<string>/<string>/<string>/<code>

创建UserMenuController权限拦截测试类










































































































<code>/** * @Description 权限测试 * @Author Sans * @CreateTime 2019/6/19 11:38 */@RestController@RequestMapping("/menu")public class UserMenuController {    @Autowired    private SysUserService sysUserService;    @Autowired    private SysRoleService sysRoleService;    @Autowired    private SysMenuService sysMenuService;    @Autowired    private SysRoleMenuService sysRoleMenuService;        /**     * 获取用户信息集合     * @Author Sans     * @CreateTime 2019/6/19 10:36     * @Return Map<string> 返回结果     */    @RequestMapping("/getUserInfoList")    @RequiresPermissions("sys:user:info")    public Map<string> getUserInfoList(){        Map<string> map = new HashMap<>();        List<sysuserentity> sysUserEntityList = sysUserService.list();        map.put("sysUserEntityList",sysUserEntityList);        return map;    }    /**     * 获取角色信息集合     * @Author Sans     * @CreateTime 2019/6/19 10:37     * @Return Map<string> 返回结果     */    @RequestMapping("/getRoleInfoList")    @RequiresPermissions("sys:role:info")    public Map<string> getRoleInfoList(){        Map<string> map = new HashMap<>();        List<sysroleentity> sysRoleEntityList = sysRoleService.list();        map.put("sysRoleEntityList",sysRoleEntityList);        return map;    }    /**     * 获取权限信息集合     * @Author Sans     * @CreateTime 2019/6/19 10:38     * @Return Map<string> 返回结果     */    @RequestMapping("/getMenuInfoList")    @RequiresPermissions("sys:menu:info")    public Map<string> getMenuInfoList(){        Map<string> map = new HashMap<>();        List<sysmenuentity> sysMenuEntityList = sysMenuService.list();        map.put("sysMenuEntityList",sysMenuEntityList);        return map;    }    /**     * 获取所有数据     * @Author Sans     * @CreateTime 2019/6/19 10:38     * @Return Map<string> 返回结果     */    @RequestMapping("/getInfoAll")    @RequiresPermissions("sys:info:all")    public Map<string> getInfoAll(){        Map<string> map = new HashMap<>();        List<sysuserentity> sysUserEntityList = sysUserService.list();        map.put("sysUserEntityList",sysUserEntityList);        List<sysroleentity> sysRoleEntityList = sysRoleService.list();        map.put("sysRoleEntityList",sysRoleEntityList);        List<sysmenuentity> sysMenuEntityList = sysMenuService.list();        map.put("sysMenuEntityList",sysMenuEntityList);        return map;    }    /**     * 添加管理员角色权限(测试动态权限更新)     * @Author Sans     * @CreateTime 2019/6/19 10:39     * @Param  username 用户ID     * @Return Map<string> 返回结果     */    @RequestMapping("/addMenu")    public Map<string> addMenu(){        //添加管理员角色权限        SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity();        sysRoleMenuEntity.setMenuId(4L);        sysRoleMenuEntity.setRoleId(1L);        sysRoleMenuService.save(sysRoleMenuEntity);        //清除缓存        String username = "admin";        ShiroUtils.deleteCache(username,false);        Map<string> map = new HashMap<>();        map.put("code",200);        map.put("msg","权限添加成功");        return map;    }}/<string>/<string>/<string>/<sysmenuentity>/<sysroleentity>/<sysuserentity>/<string>/<string>/<string>/<sysmenuentity>/<string>/<string>/<string>/<sysroleentity>/<string>/<string>/<string>/<sysuserentity>/<string>/<string>/<string>/<code>

创建UserLoginController登录类































































<code>/** * @Description 用户登录 * @Author Sans * @CreateTime 2019/6/17 15:21 */@RestController@RequestMapping("/userLogin")public class UserLoginController {    @Autowired    private SysUserService sysUserService;    /**     * 登录     * @Author Sans     * @CreateTime 2019/6/20 9:21     */    @RequestMapping("/login")    public Map<string> login(@RequestBody SysUserEntity sysUserEntity){        Map<string> map = new HashMap<>();        //进行身份验证        try{            //验证身份和登陆            Subject subject = SecurityUtils.getSubject();            UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword());            //验证成功进行登录操作            subject.login(token);        }catch (IncorrectCredentialsException e) {            map.put("code",500);            map.put("msg","用户不存在或者密码错误");            return map;        } catch (LockedAccountException e) {            map.put("code",500);            map.put("msg","登录失败,该用户已被冻结");            return map;        } catch (AuthenticationException e) {            map.put("code",500);            map.put("msg","该用户不存在");            return map;        } catch (Exception e) {            map.put("code",500);            map.put("msg","未知异常");            return map;        }        map.put("code",0);        map.put("msg","登录成功");        map.put("token",ShiroUtils.getSession().getId().toString());        return map;    }    /**     * 未登录     * @Author Sans     * @CreateTime 2019/6/20 9:22     */    @RequestMapping("/unauth")    public Map<string> unauth(){        Map<string> map = new HashMap<>();        map.put("code",500);        map.put("msg","未登录");        return map;    }}/<string>/<string>/<string>/<string>/<code>

六. POSTMAN测试

登录成功后会返回TOKEN,因为是单点登录,再次登陆的话会返回新的TOKEN,之前Redis的TOKEN就会失效了

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

当第一次访问接口后我们可以看到缓存中已经有权限数据了,在次访问接口的时候,Shiro会直接去缓存中拿取权限,注意访问接口时候要设置请求头.

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

ADMIN这个号现在没有sys:info:all这个权限的,所以无法访问getInfoAll接口,我们要动态分配权限后,要清掉缓存,在访问接口时候,Shiro会去重新执行授权方法,之后再次把权限和角色数据放入缓存中

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

访问添加权限测试接口,因为是测试,我把增加权限的用户ADMIN写死在里面了,权限添加后,调用工具类清掉缓存,我们可以发现,Redis中已经没有缓存了

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录

再次访问getInfoAll接口,因为缓存中没有数据,Shiro会重新授权查询权限,拦截通过

SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录


(完)


分享到:


相關文章: