微服務之間的通訊安全(三)-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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM