SpringBoot 整合Shiro實現動態權限加載更新+Session共享+單點登錄


作者:Sans_

juejin.im/post/5d087d605188256de9779e64

一.說明

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依賴如下:

<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>
        <!-- AOP依賴,一定要加,否則權限攔截驗證不生效 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- lombok插件 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <!-- mybatisPlus 核心庫 -->
        <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>
        <!-- Shiro 核心依賴 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!-- Shiro-redis插件 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- StringUitlS工具 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
</dependencies>

配置如下:

# 配置端口
server:
  port: 8764
spring:
  # 配置數據源
  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

二.編寫項目基礎類

用戶實體,Dao,Service等在這里省略,請參考源碼

編寫Exception類來處理Shiro權限攔截異常

SpringBoot 整合Shiro實現動態權限加載更新+Session共享+單點登錄

 

創建SHA256Util加密工具

 

SpringBoot 整合Shiro實現動態權限加載更新+Session共享+單點登錄

創建Spring工具

/**
 * @Description Spring上下文工具類
 * @Author Sans
 * @CreateTime 2019/6/17 13:40
 */
@Component
public 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<T> beanClass) {
        return context.getBean(beanClass);
    }
}

創建Shiro工具

/**
 * @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);
    }
}

創建Shiro的SessionId生成器

SpringBoot 整合Shiro實現動態權限加載更新+Session共享+單點登錄

三.編寫Shiro核心類

創建Realm用於授權和認證

/**
 * @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;
    }
}

創建SessionManager類

SpringBoot 整合Shiro實現動態權限加載更新+Session共享+單點登錄

創建ShiroConfig配置類

/**
 * @Description Shiro配置類
 * @Author Sans
 * @CreateTime 2019/6/10 17:42
 */
@Configuration
public 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, 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;
    }
}

四.實現權限控制

Shiro可以用代碼或者注解來控制權限,通常我們使用注解控制,不僅簡單方便,而且更加靈活.Shiro注解一共有五個:

SpringBoot 整合Shiro實現動態權限加載更新+Session共享+單點登錄

一般情況下我們在項目中做權限控制,使用最多的是RequiresPermissions和RequiresRoles,允許存在多個角色和權限,默認邏輯是AND,也就是同時擁有這些才可以訪問方法,可以在注解中以參數的形式設置成OR

示例

 

SpringBoot 整合Shiro實現動態權限加載更新+Session共享+單點登錄

使用順序:Shiro注解是存在順序的,當多個注解在一個方法上的時候,會逐個檢查,知道全部通過為止,默認攔截順序是:RequiresRoles->RequiresPermissions->RequiresAuthentication->
RequiresUser->RequiresGuest

示例

SpringBoot 整合Shiro實現動態權限加載更新+Session共享+單點登錄

創建UserRoleController角色攔截測試類

/**
 * @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,Object> 返回結果
     */
    @RequestMapping("/getAdminInfo")
    @RequiresRoles("ADMIN")
    public Map<String,Object> getAdminInfo(){
        Map<String,Object> map = new HashMap<>();
        map.put("code",200);
        map.put("msg","這里是只有管理員角色能訪問的接口");
        return map;
    }

    /**
     * 用戶角色測試接口
     * @Author Sans
     * @CreateTime 2019/6/19 10:38
     * @Return Map<String,Object> 返回結果
     */
    @RequestMapping("/getUserInfo")
    @RequiresRoles("USER")
    public Map<String,Object> getUserInfo(){
        Map<String,Object> map = new HashMap<>();
        map.put("code",200);
        map.put("msg","這里是只有用戶角色能訪問的接口");
        return map;
    }

    /**
     * 角色測試接口
     * @Author Sans
     * @CreateTime 2019/6/19 10:38
     * @Return Map<String,Object> 返回結果
     */
    @RequestMapping("/getRoleInfo")
    @RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)
    @RequiresUser
    public Map<String,Object> getRoleInfo(){
        Map<String,Object> 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,Object> 返回結果
     */
    @RequestMapping("/getLogout")
    @RequiresUser
    public Map<String,Object> getLogout(){
        ShiroUtils.logout();
        Map<String,Object> map = new HashMap<>();
        map.put("code",200);
        map.put("msg","登出");
        return map;
    }
}

創建UserMenuController權限攔截測試類

/**
 * @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,Object> 返回結果
     */
    @RequestMapping("/getUserInfoList")
    @RequiresPermissions("sys:user:info")
    public Map<String,Object> getUserInfoList(){
        Map<String,Object> 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,Object> 返回結果
     */
    @RequestMapping("/getRoleInfoList")
    @RequiresPermissions("sys:role:info")
    public Map<String,Object> getRoleInfoList(){
        Map<String,Object> 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,Object> 返回結果
     */
    @RequestMapping("/getMenuInfoList")
    @RequiresPermissions("sys:menu:info")
    public Map<String,Object> getMenuInfoList(){
        Map<String,Object> 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,Object> 返回結果
     */
    @RequestMapping("/getInfoAll")
    @RequiresPermissions("sys:info:all")
    public Map<String,Object> getInfoAll(){
        Map<String,Object> 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,Object> 返回結果
     */
    @RequestMapping("/addMenu")
    public Map<String,Object> addMenu(){
        //添加管理員角色權限
        SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity();
        sysRoleMenuEntity.setMenuId(4L);
        sysRoleMenuEntity.setRoleId(1L);
        sysRoleMenuService.save(sysRoleMenuEntity);
        //清除緩存
        String username = "admin";
        ShiroUtils.deleteCache(username,false);
        Map<String,Object> map = new HashMap<>();
        map.put("code",200);
        map.put("msg","權限添加成功");
        return map;
    }
}

創建UserLoginController登錄類

/**
 * @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,Object> login(@RequestBody SysUserEntity sysUserEntity){
        Map<String,Object> 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,Object> unauth(){
        Map<String,Object> map = new HashMap<>();
        map.put("code",500);
        map.put("msg","未登錄");
        return map;
    }
}

五.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共享+單點登錄

六.項目源碼

https://gitee.com/liselotte/spring-boot-shiro-demo


https://github.com/xuyulong2017/my-java-demo

 

推薦閱讀(點擊即可跳轉閱讀)

 

1. SpringBoot內容聚合

2. 面試題內容聚合

3. 設計模式內容聚合

4. Mybatis內容聚合

5. 多線程內容聚合


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM