shiro实战:接口权限控制,shiro解决数据平行越权安全漏洞

   日期:2020-09-03     浏览:120    评论:0    
核心提示:昨天被网安大队扫描安全漏铜,提到有个致命的安全漏洞:数据平行越权,要求半个月内整改完毕。数据平行越权用大白话说就是:我先用超级管理员登录后台系统,然后我记住了菜单A对应的URL。我在用普通管理员登录,但是没有菜单A的权限,但是我可以直接通过菜单A的URL进行访问。数据平行越权的漏洞最根本问题是,系统没有针对接口级别的权限控制。好多系统都是所有接口做个拦截器,只校验了是否登录,并没有把用户、角色权限和接口给关联起来。以下讲下java web框架的解决思路。

文章目录

  • 背景
  • 解决方案
    • JAVA后台变更:
    • 前端变更:
  • 源码地址:

背景

昨天被网安大队扫描安全漏铜,提到有个致命的安全漏洞:数据接口平行越权,要求半个月内整改完毕。数据平行越权用大白话说就是:我先用超级管理员登录后台系统,然后我记住了菜单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;

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

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

    
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    
    @Bean(name = "exceptionHandler")
    public HandlerExceptionResolver handlerExceptionResolver() {
        return new MyExceptionHandler();
    }
}

前端变更:

  1. 前端需要根据自己的框架,作统一的底层判断然后跳到统一的权限异常页面。
  2. 后台界面配置也需要支持对shiroid的管理。如下效果:

源码地址:

获取完整源码地址: https://download.csdn.net/download/penggerhe/11670196

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服