Shiroの戦闘:インターフェイスのアクセス許可の制御、Shiroはデータパラレルの不正なセキュリティの脆弱性を解決

バックグラウンド

昨日、ネットワークセキュリティ旅団はセキュリティリークをスキャンし、致命的なセキュリティの抜け穴があったことを述べました:データインターフェースが権限を超えており、半月以内に修正が必要でした。データパラレルウルトラバイオレスは一般的です。まず、スーパー管理者でバックグラウンドシステムにログインし、次にメニューAに対応するURLを覚えています。通常の管理者を使用してログインしていますが、メニューAの権限はありませんが、メニューAのURLから直接アクセスできます。
データ並列処理の脆弱性の最も根本的な問題は、システムにインターフェースレベルでのアクセス制御がないことです。多くのシステムは、すべてのインターフェースのインターセプターとして機能し、それらがログインしているかどうかを確認するだけで、ユーザー、役割の許可、インターフェースを関連付けません。
次の画像は、ネットワークセキュリティチームのセキュリティ脆弱性レポートのスクリーンショットです。
ネットワークセキュリティチームのセキュリティ脆弱性レポートのスクリーンショット

以下は、Java Webフレームワークのソリューションです。

解決

Javaの背景の変更:

  1. pom.xmlはshiroに対応するjarパッケージを導入します:
		<!-- 对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>2.4.2.1-RELEASE</version>
        </dependency>
  1. アクセス許可の制御を必要とするインターフェースに次の注釈を追加します。
@RequiresPermissions("student:getStudent")
    @RequestMapping(value = "getStudent",method= RequestMethod.POST)
    public ModelAndView getStudent()
    {
    
    
    	return new ModelAndView("/student");
    }
  1. Application.ymlはshiroおよびredis情報を構成します。
spring:
  redis:
    shiro :
      host: 127.0.0.1
      port: 6379
      timeout : 0
      password:
  1. 統一された例外処理クラス:MyExceptionHandler.java
@Slf4j
public class MyExceptionHandler implements HandlerExceptionResolver {
    
    

    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
    
    
    	ex.printStackTrace();
        ModelAndView mv = new ModelAndView();
        FastJsonJsonView view = new FastJsonJsonView();
        Map<String, Object> attributes = new HashMap<String, Object>();
        if (ex instanceof UnauthenticatedException) {
    
    
            attributes.put("sucess", "false");
            attributes.put("info", "token错误");
        } else if (ex instanceof UnauthorizedException) {
    
    
            attributes.put("sucess", "false");
            attributes.put("info", "用户无权限");
        } else {
    
    
            attributes.put("sucess", "false");
            attributes.put("info", ex.getMessage());
            log.error("发生未处理的异常={}",ex.getMessage(),ex);
        }

        view.setAttributesMap(attributes);
        mv.setView(view);
        return mv;
    }
}

  1. 新しい権限コントロール:MyShiroRealm.java
public class MyShiroRealm extends AuthorizingRealm {
    
    
    @Resource
    private SysUserService sysUserService;
    @Autowired
    private SysRoleService sysRoleService;

    /**
     * 获取授权信息(把数据库中shiroID加入到shiro中管理,然后如果ctronler层方法加上@RequiresPermissions注解才会调用这个方法和数组进行比对)
	 * 只有当需要检测用户权限的时候才会调用此方法
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
	{
    
    
		//获取username
		SysUser sysUser=(SysUser) SecurityUtils.getSubject().getSession().getAttribute("sysUserLogin");
		List<SysPrivilege> sysPrivilegeList=sysPrivilegeList=sysRoleService.getSysPrivilegeByRoid(sysUser.getRoleId());
		// 用户权限列表
		Set<String> permsSet = new HashSet<>();
		for (SysPrivilege perms : sysPrivilegeList)
		{
    
    
			if (StringUtils.isBlank(perms.getShiroID()))
			{
    
    
				continue;
			}
			permsSet.addAll(Arrays.asList(perms.getShiroID().trim().split(",")));
		}
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		info.addStringPermissions(permsSet);
		return info;
	}

    /**
     * 获取身份验证信息
     * 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
    
    
//        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String username = (String) token.getPrincipal();
//        System.out.println(token.getCredentials());
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        SysUser userInfo = sysUserService.getByUserCode(username);
//        System.out.println("----->>userInfo="+userInfo);
        if (userInfo == null) {
    
    
            return null;
        }
        if (Integer.parseInt(userInfo.getStates()) == 1) {
    
     //账户冻结
            throw new LockedAccountException();
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                token.getCredentials(), //用户名
                userInfo.getPassword(),//salt=username+salt
                getName()  //realm name
        );
        return authenticationInfo;
    }

}
  1. 新しいshiro管理クラス:MySessionManager.java
public class MySessionManager extends DefaultWebSessionManager {
    
    

    private static final String AUTHORIZATION = "Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public MySessionManager() {
    
    
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
    
    
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //适合于安卓,ios的或者前后端分离情况,直接在head头中传入sessionId的值(sessionId的键名为Authorization)
        if (!StringUtils.isEmpty(id)) {
    
    
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
    
    
            //适用于浏览器访问,否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }

}
  1. 新しいshiro構成ルールクラス:ShiroConfig.java
@Configuration
public class ShiroConfig {
    
    

    @Value("${spring.redis.shiro.host}")
    private String host;
    @Value("${spring.redis.shiro.port}")
    private int port;
    @Value("${spring.redis.shiro.timeout}")
    private int timeout;
    @Value("${spring.redis.shiro.password}")
    private String password;

    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
    
    
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //注意过滤器配置顺序 不能颠倒
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
        filterChainDefinitionMap.put("/loginout", "logout");
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/jqueryEasyui/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/theme/**", "anon");
        filterChainDefinitionMap.put("/druid/**", "anon");
        filterChainDefinitionMap.put("/ajaxLogin", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/demoOrder/**r", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
        shiroFilterFactoryBean.setLoginUrl("/index");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授权界面;
//        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
    
    
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        return hashedCredentialsMatcher;
    }

    @Bean
    public MyShiroRealm myShiroRealm() {
    
    
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }


    @Bean
    public SecurityManager securityManager() {
    
    
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        return securityManager;
    }

    //自定义sessionManager
    @Bean
    public SessionManager sessionManager() {
    
    
        MySessionManager mySessionManager = new MySessionManager();
        mySessionManager.setSessionDAO(redisSessionDAO());
        return mySessionManager;
    }

    /**
     * 配置shiro redisManager
     * <p>
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public RedisManager redisManager() {
    
    
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setExpire(1800);// 配置缓存过期时间(登录是失效时间,0代表永不失效)
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * cacheManager 缓存 redis实现
     * <p>
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager() {
    
    
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * <p>
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
    
    
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    
    
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 注册全局异常处理
     * @return
     */
    @Bean(name = "exceptionHandler")
    public HandlerExceptionResolver handlerExceptionResolver() {
    
    
        return new MyExceptionHandler();
    }
}

フロントエンドの変更:

  1. フロントエンドは、独自のフレームワークに基づいて統一された最下位レベルの判断を行い、統一された許可例外ページにジャンプする必要があります。
  2. バックグラウンドのインターフェース構成も、shiroidの管理をサポートする必要があります。以下の効果:
    ここに画像の説明を挿入
    ここに画像の説明を挿入

送信元アドレス:

  1. 完全なソースコードアドレスを取得:https : //download.csdn.net/download/penggerhe/11670196
  2. 公式アカウントをフォローして無料で入手してください:
    ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/penggerhe/article/details/108225886