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

個人公衆號: MarkerHub,網站: https://markerhub.com

更多精選文章請點擊:Java筆記大全.md前端

小Hub領讀:

有源碼的教程,不會的同窗下載源碼,根據教程學一下哈~java


做者:Sans_

juejin.im/post/5d087d605188256de9779e64mysql

一. 說明

Shiro 是一個安全框架, 項目中主要用它作認證, 受權, 加密, 以及用戶的會話管理, 雖然 Shiro 沒有 SpringSecurity 功能更豐富, 可是它輕量, 簡單, 在項目中一般業務需求 Shiro 也都能勝任.react

二. 項目環境

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

數據表 (SQL 文件在項目中): 數據庫中測試號的密碼進行了加密, 密碼皆爲 123456git

Maven 依賴以下:github

<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>

配置以下:web

# 配置端口
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 等在這裏省略, 請參考源碼redis

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

建立 SHA256Util 加密工具spring

建立 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> 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 生成器

三. 編寫 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 類

建立 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 註解一共有五個:

通常狀況下咱們在項目中作權限控制, 使用最多的是 RequiresPermissions 和 RequiresRoles, 容許存在多個角色和權限, 默認邏輯是 AND, 也就是同時擁有這些才能夠訪問方法, 能夠在註解中以參數的形式設置成 OR

示例

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

示例

建立 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 就會失效了

當第一次訪問接口後咱們能夠看到緩存中已經有權限數據了, 在次訪問接口的時候, Shiro 會直接去緩存中拿取權限, 注意訪問接口時候要設置請求頭.

ADMIN 這個號如今沒有 sys:info:all 這個權限的, 因此沒法訪問 getInfoAll 接口, 咱們要動態分配權限後, 要清掉緩存, 在訪問接口時候, Shiro 會去從新執行受權方法, 以後再次把權限和角色數據放入緩存中

訪問添加權限測試接口, 由於是測試, 我把增長權限的用戶 ADMIN 寫死在裏面了, 權限添加後, 調用工具類清掉緩存, 咱們能夠發現, Redis 中已經沒有緩存了

再次訪問 getInfoAll 接口, 由於緩存中沒有數據, Shiro 會從新受權查詢權限, 攔截經過

六. 項目源碼

https://gitee.com/liselotte/s...

https://github.com/xuyulong20...


(完)

推薦閱讀

Java筆記大全.md

太讚了,這個Java網站,什麼項目都有!https://markerhub.com

這個B站的UP主,講的java真不錯!

太讚了!最新版Java編程思想能夠在線看了!

相關文章
相關標籤/搜索