• 微服务之间的通讯安全(三)-JWT优化之权限控制


    上节我们使用JWT优化了认证机制,通过令牌可以解析出当前用户是谁,并且这个令牌可以在网关到微服务,微服务和微服务之间传递,现在我们来看一下权限的控制

    1、简单的ACL控制

    最简单的情况就是ACL(访问控制列表),能干什么都在scope里面,但是scope是针对客户端应用的,无法控制各个用户可以做什么,可以使用用户里的authorities来进行判断。我们只需要在程序里判断一下,访问这个方法有没有相应的权限就可以了。可以在springsecurity中进行配置,也可以使用注解,这里我们使用注解。

    1.1、添加@EnableGlobalMethodSecurity注解并开启prePostEnabled

    /**
     * 订单微服务
     *
     * @author caofanqi
     * @date 2020/1/31 14:22
     */
    @EnableResourceServer
    @SpringBootApplication
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class OrderApiApplication {
    
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApiApplication.class, args);
        }
    
        /**
         * 将OAuth2RestTemplate声明为spring bean,OAuth2ProtectedResourceDetails,OAuth2ClientContext springboot会自动帮我们注入
         */
        @Bean
        public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
            return new OAuth2RestTemplate(resource, context);
        }
    
    }

    1.2、在方法上可以使用@PreAuthorize注解进行方法前权限校验,下面的创建订单方法,需要令牌具有fly权限并且用户的角色是ROLE_ADMIN。

        @PostMapping
        @PreAuthorize("#oauth2.hasScope('write') and hasRole('ROLE_ADMIN')")
        public OrderDTO create(@RequestBody OrderDTO orderDTO, @AuthenticationPrincipal String username) {
            log.info("username is :{}", username);
            PriceDTO price = oAuth2RestTemplate.getForObject("http://127.0.0.1:9070/prices/" + orderDTO.getProductId(), PriceDTO.class);
            log.info("price is : {}", price.getPrice());
            return orderDTO;
        }

    1.3、启动各项目进行测试

      1.3.1、使用scope为read,write,并且用户的authorities里有ROLE_ADMIN,可以正常访问。

      1.3.2、将@PreAuthorize("#oauth2.hasScope('write') and hasRole('ROLE_ADMIN')") 修改为@PreAuthorize("#oauth2.hasScope('fly') and hasRole('ROLE_ADMIN')")或@PreAuthorize("#oauth2.hasScope('write') and hasRole('ROLE_USER')"),继续使用该令牌进行访问,均没有权限。

    2、在网关上做复杂的权限控制

      上面直接在方法上控制权限,适用于比较简单的场景,只有几个角色,就可以实现权限控制。但是没有办法应付复杂的场景,比如说用户的角色权限是动态变化的,上面的方式就无法进行控制了。下面我们来看一下,要实时的去做授权怎么来做。

    一般,我们对于复杂的权限控制,都是会有一个权限服务的,和订单服务、库存服务一样都是一个微服务;前端会有相关的页面可以对用户的角色权限实时进行修改,配置到数据库中,在同步到redis中,权限系统可以提供一个接口,用来来判断当前请求是否有相应的权限。请求发到网关经过校验令牌之后,网关去校验该请求的权限,有权限就转发,没有权限就返回。我们把认证服务器和权限服务整个一块称为安全中心。

    2.1、在网关上写一个权限控制服务,可以去安全中心进行权限判断。这里我们就不去实现权限服务了,使用随机数替代。

    /**
     * 权限控制
     * @author caofanqi
     * @date 2020/2/9 14:48
     */
    public interface PermissionService {
    
        /**
         * 判断当前请求是否有权限
         * @param request 请求
         * @param authentication 认证相关信息
         * @return boolean
         */
        boolean hasPermission(HttpServletRequest request, Authentication authentication);
    
    }
    
    /**
     * 权限控制实现类
     *
     * @author caofanqi
     * @date 2020/2/9 14:51
     */
    @Slf4j
    @Service
    public class PermissionServiceImpl implements PermissionService {
    
        /**
         * 在这里可以去安全中心,获取该请求是否具有相应的权限
         *
         * @param request        请求
         * @param authentication 认证相关信息
         */
        @Override
        public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
    
            //这里我们就不写具体的权限判断了,采用随机数模拟,百分之50的机率可以访问
            log.info("request uri : {}", request.getRequestURI());
            log.info("authentication : {}", ReflectionToStringBuilder.toString(authentication));
            boolean  hasPermission =  RandomUtils.nextInt() % 2 == 0;
            log.info("hasPermission is :{}",hasPermission);
            return hasPermission;
        }
    
    }

    2.2、将我们的服务添加到评估上下文中,使其可以被解析。在表达式中写permissionService时可以找到该bean。

    /**
     * 将权限服务表达式添加到评估上下文中
     *
     * @author caofanqi
     * @date 2020/2/9 14:58
     */
    @Component
    public class GatewayWebSecurityExpressionHandler extends OAuth2WebSecurityExpressionHandler {
    
        @Resource
        private PermissionService permissionService;
    
        @Override
        protected StandardEvaluationContext createEvaluationContextInternal(Authentication authentication, FilterInvocation invocation) {
            StandardEvaluationContext sc = super.createEvaluationContextInternal(authentication, invocation);
            sc.setVariable("permissionService",permissionService);
            return sc;
        }
        
    }

    2.3、在网关资源服务器配置类中进行配置

    /**
     * 网关资源服务器配置
     *
     * @author caofanqi
     * @date 2020/2/8 22:30
     */
    @Configuration
    @EnableResourceServer
    public class GatewayResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    
        @Resource
        private GatewayWebSecurityExpressionHandler gatewayWebSecurityExpressionHandler;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources
                    .resourceId("gateway")
                    //表达式处理器
                    .expressionHandler(gatewayWebSecurityExpressionHandler);
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    //放过申请令牌的请求不需要身份认证
                    .antMatchers("/token/**").permitAll()
                    //其他所有请求是否有权限,要通过permissionService的hasPermission方法进行判断
                    .anyRequest().access("#permissionService.hasPermission(request,authentication)");
        }
    
    }

    2.4、启动各项目进行创建订单测试

      可以正常访问时,gateway控制台打印

      没有权限访问403时,gateway控制台打印

       这说明权限控制已经是通过我们的PermissionService来控制的了。

    3、微服务之间的权限控制

      如果权限控制都在网关上管理,那么有可能就会出现,用户A可以访问订单服务,但是不能访问库存服务。但是用户A在访问订单服务的时候,订单服务可以访问库存服务,这样的话,就越权了。这个场景怎么解决呢?可以在每个微服务上都去调用安全中心判断权限,和网关上做法差不多,但是不建议这么做。

      推荐的做法是,细粒度的权限,每个请求可不可以访问在网关上做,可以覆盖95%-99%的权限。到了后面的微服务与微服务之间,我们只需要做粗粒度的黑白名单控制即可。比如所我们有一个跟钱相关的结算服务,可以控制只能订单服务来调用,其他服务不可以调用。后面学习sentinel进行控制。

     项目源码:https://github.com/caofanqi/study-security/tree/dev-jwt-permission

     

  • 相关阅读:
    JeePlus:代码结构
    JeePlus:项目部署
    JeePlus:Maven 安装配置
    JeePlus:目录
    框架-Java:JeePlus
    Java-JSP:EL表达式
    Template-FreeMarker:模板开发指南
    Template-FreeMarker:什么是 FreeMarker?
    FreeMarker:
    Template-FreeMarker:目录
  • 原文地址:https://www.cnblogs.com/caofanqi/p/12288634.html
Copyright © 2020-2023  润新知