[ Shiro ] SpringBoot 集成 Shiro 與 Redis 實現權限控制,統一會話管理

SpringBoot 集成 Shiro 實現權限管理

完整項目地址:https://gitee.com/aoxiaobao/Shiro-study

上一篇:[ JWT ] SpringBoot 集成 JWT 實現 token 鑑權


pom依賴

<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-core</artifactId>
	<version>1.3.2</version>
</dependency>
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
	<version>1.3.2</version>
</dependency>

修改登錄方法


  • 密碼採用shiro提供的 Md5Hash 方法加密
  • 參數一:加密的內容
  • 參數二:鹽(加密的混淆字符串)(用戶登錄的用戶名)
  • 參數三:加密次數
    public String login(String username,String password){
        // 構造登錄令牌
        try{
            /**
             * 密碼加密:
             *  shiro提供的md5加密
             *  Md5Hash:
             *      參數一:加密的內容
             *      參數二:鹽(加密的混淆字符串)(用戶登錄的用戶名)
             *      參數三:加密次數
             */
            // 密碼加密
//            password = new Md5Hash(password,username,3).toString();

            UsernamePasswordToken upToken = new UsernamePasswordToken(username,password);
            System.out.println(upToken);
            // 獲取subject
            Subject subject = SecurityUtils.getSubject();

            // 獲取session
            String sid = (String) subject.getSession().getId();
            System.out.println(sid);
            // 調用subject進行登錄
            subject.login(upToken);

            return "登錄成功:"+sid;
        } catch (Exception e){
            return "用戶名或密碼錯誤";
        }
    }

添加自定義 realm


  • 該類繼承 AuthorizingRealm
  • 重寫認證方法:AuthenticationInfo
  • 重寫授權方法:AuthorizationInfo
/**
 * 自定義reallm
 */
public class CustomerRealm extends AuthorizingRealm {
    @Override
    public void setName(String name){
        super.setName("customRealm");
    }
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermService permService;
    /**
     * 授權方法
     *  操作的時候,判斷用戶是否具有響應的權限
     *      先認證 -- 安全數據
     *      再授權 -- 根據安全數據獲取用戶具有的所有操作權限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 獲取已認證的用戶數據
        User user = (User) principalCollection.getPrimaryPrincipal(); // 得到唯一安全數據
        // 根據用戶數據獲取去用戶的權限信息(所有角色,所有權限)
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> roles = new HashSet<>(); // 所有角色
        Set<String> perms = new HashSet<>(); // 所有權限
//        for(Role role : user.getRoles()){
//            roles.add(role.getName());
//            for(Permission perm : role.getPermissions()){
//                perms.add(perm.getCode());
//            }
//        }
        Role role = roleService.findByName(user.getUsername());
        Permission permission = permService.findByName(user.getUsername());
        System.out.println(role.getRoleName());
        System.out.println(permission.getPermName());
        roles.add(role.getRoleName());
        perms.add(permission.getPermName());
        info.setStringPermissions(perms);
        info.setRoles(roles);
        return info;
    }

    /**
     * 認證方法
     * 參數:傳遞的用戶名密碼
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 獲取登錄的用戶名密碼(token)
        UsernamePasswordToken upToken  = (UsernamePasswordToken) authenticationToken;
        String username = upToken.getUsername();
        String password = new String(upToken.getPassword());

        // 根據用戶名查詢數據庫
        User user = userService.findByName(username);

        // 判斷用戶名是否存在或密碼是否一致
        if(user != null && user.getPassword().equals(password)){
            // 如果一致,返回安全數據
            // 構造方法:安全數據,密碼,realm域名
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
            return info;
        }

        // 不一致,返回null(拋出異常)
        return null;
    }
}

配置 SecurityManager


詳細的說明都寫在註釋裏了

/**
 * @author aowei
 */
@Configuration
public class ShiroConfiguration {
    // 創建realm
    @Bean
    public CustomerRealm getRealm(){
        return new CustomerRealm();
    }
    // 創建安全管理器
    @Bean
    public SecurityManager getSecurityManager(CustomerRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 將自定義的realm交給安全管理器統一調度管理
        securityManager.setRealm(realm);

        // 將自定義的會話管理器註冊到安全管理器中
        securityManager.setSessionManager(sessionManager());

        // 將自定義的redis緩存管理器註冊到安全管理器
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }
    /**
     * 在web程序中,shiro進行權限控制全部是通過一組過濾器集合進行控制
     * 配置shiro的過濾器工廠,設置對應的過濾條件和跳轉條件
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        // 創建過濾器工廠
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        // 設置安全管理器
        filterFactory.setSecurityManager(securityManager);
        // 通用配置(跳轉登錄頁面,未授權頁面)
        filterFactory.setLoginUrl("/loginUrl"); // 跳轉url地址
        filterFactory.setUnauthorizedUrl("/unAuth");// 未授權的url
        // 設置管理器集合

        /**
         * 設置所有的過濾器:有順序map
         *  key = 攔截的url地址
         *  value = 過濾器類型
         */
        Map<String,String> filterMap = new LinkedHashMap<>();
        filterMap.put("/home","anon"); // 當前請求url地址可匿名訪問

        // 具有某種權限才能訪問
//        filterMap.put("/user/client","perms[user-client]");

        // 具有某種角色才能訪問
//        filterMap.put("/user/admin","roles[admin]");
        filterMap.put("/user/**","authc"); // 當前請求地址必須認證之後可訪問
        // 設置過濾器
        filterFactory.setFilterChainDefinitionMap(filterMap);
        return filterFactory;
    }

    // 開啓對shiro 註解的支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
    // 開啓對shiro 註解的支持
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator app=new DefaultAdvisorAutoProxyCreator();
        app.setProxyTargetClass(true);
        return app;
    }

shiro 鑑權


1、基於註解

    @RequiresPermissions("user-home")  -- 訪問此方法必須具有的權限
    @RequiresRoles("admin")  --  訪問此方法必須具有的角色

2、基於攔截器

Map<String,String> filterMap = new LinkedHashMap<>();
	filterMap.put("/home","anon"); // 當前請求url地址可匿名訪問
	// 具有某種權限才能訪問
	filterMap.put("/user/client","perms[user-client]");
	// 具有某種角色才能訪問
	filterMap.put("/user/admin","roles[admin]");
	filterMap.put("/user/**","authc"); // 當前請求地址必須認證之後可訪問
	// 設置過濾器
filterFactory.setFilterChainDefinitionMap(filterMap);

shiro 兩種鑑權方式的區別

  • 過濾器:如果權限信息不匹配 跳轉到setUnauthorizedUrl地址
  • 註解;如果權限信息不匹配,拋出異常

Shiro結合redis的統一會話管理


  • (1)shiroredis 整合
<dependency>
   <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.0.0</version>
</dependency>
  • (2)修改 springboot 配置文件,添加 redis 的設置
spring.redis.host=192.168.43.121
spring.redis.port=6379
  • (3)自定義 shiro 的會話管理器
/**
 * 自定義sessionManager
 * @author aowei
 */
public class CustomSessionManager extends DefaultWebSessionManager {
    /**
     * 頭信息中具有sessionId
     *  請求頭:Authorization:sessionId
     *
     *  指定sessionId的獲取方式
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        // 獲取請求頭Authorization中的數據
        String id = WebUtils.toHttp(request).getHeader("Authorization");
        if(StringUtils.isEmpty(id)){
            // 如果沒有攜帶,生成新的sessionId
            return super.getSessionId(request,response);
        }else {
            // 返回sessionId
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,"header");
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
            return id;
        }
    }
}
  • (4)配置shiro 基於redis 的會話管理
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    /**
     * redis 的控制器,操作redis
     */
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        return redisManager;
    }

    /**
     * sessionDao
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO sessionDAO = new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
        return sessionDAO;
    }

    /**
     * 會話管理器
     */
    public DefaultWebSessionManager sessionManager(){
        CustomSessionManager sessionManager = new CustomSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        // 禁用cookie
//        sessionManager.setSessionIdCookieEnabled(false);
        // 禁用url重寫
//        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }
    /**
     * 緩存管理器
     */
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

統一異常處理


  • 自定義的公共異常處理器
  • 聲明異常處理器
  • 對異常統一處理
/**
 * @author aowei
 */
@ControllerAdvice
public class BaseExceptionHandler {
    @ExceptionHandler(value = AuthorizationException.class)
    @ResponseBody
    public String error(HttpServletRequest request, HttpServletResponse response,AuthorizationException exception){
        return "未授權";
    }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章