alibaba / Sentinel

A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)
https://sentinelguard.io/
Apache License 2.0
22.32k stars 8k forks source link

[BUG] 系统规则与授权规则不生效 #3214

Open fushichenmu opened 1 year ago

fushichenmu commented 1 year ago

Issue Description

Type: bug report

Describe what happened

基于毕业版本的SpringCloudAlibaba+SpringCloud+SpringBoot下配置并使用Sentinel的系统规则与授权规则不生效。

三个基础版本如图所示: image Sentinel相关依赖如图所示: image 定义测试用的Controller类: `package com.nriet.cipas.controller.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.nriet.cipas.common.util.exception.ExceptionUtils; import com.nriet.cipas.environment.annotation.NotControllerResponseAdvice; import com.nriet.cipas.service.service.IUserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController @Slf4j public class UserController { @Autowired private IUserService userService;

@GetMapping("/user/getUserByName")
@SentinelResource(value = "/user/getUserByName",blockHandler = "blockHandlerGetUserByName")
public String getUserByName(@RequestParam("userName") String userName){
    return userService.selectUserByName(userName);
}

public String blockHandlerGetUserByName(String userName, BlockException ex)
{
    ExceptionUtils.extracted(ex);
    return "";
}

@GetMapping("/user/{userId}")
@SentinelResource(value = "/user/userId",blockHandler = "blockHandler")
public String userId(@PathVariable("userId") String userId){
    return "checked!";
}

public String blockHandler(String userId, BlockException ex)
{
    ExceptionUtils.extracted(ex);
    return "";
}

@GetMapping("/user/authority")
@SentinelResource(value = "/user/authority",blockHandler = "blockHandlerAuthority")
public String authority(@RequestParam("userId") String userId){
    return "passed!";
}

public String blockHandlerAuthority(String userId, BlockException ex)
{
    ExceptionUtils.extracted(ex);
    return "";
}

} `

其他方法1: `package com.nriet.cipas.common.util.exception;

import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import com.alibaba.csp.sentinel.slots.system.SystemBlockException;

public class ExceptionUtils {

public  static void extracted(BlockException ex) {
    if(ex instanceof FlowException) {
        throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_FLOW_ERROR.getCode(), "selectUserByName" + "方法被限流了,请稍后重试");
    } else if (ex instanceof DegradeException) {
        throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_DEGRADE_ERROR.getCode(), "selectUserByName" + "方法被降级熔断了,请稍后重试");
    } else if (ex instanceof ParamFlowException) {
        throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_PARAMS_ERROR.getCode(), "selectUserByName" + "方法触发热点参数限流,请稍后重试");
    } else if (ex instanceof AuthorityException) {
        throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_AUTH_ERROR.getCode(), "selectUserByName" + "方法访问权限不足,请联系服务管理员");
    } else if (ex instanceof SystemBlockException) {
        throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_SYSTEM_BLOCK_ERROR.getCode(), "selectUserByName" + "方法触发系统保护了,请稍后重试");
    }
}

} `

全局异常处理类: `package com.nriet.cipas.environment.exception;

import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import com.alibaba.csp.sentinel.slots.system.SystemBlockException; import com.nriet.cipas.common.util.exception.ApplicationDefinedException; import com.nriet.cipas.common.util.exception.ExceptionEnum; import com.nriet.cipas.instance.vo.result.ResultVO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.List; import java.util.stream.Collectors;

@ControllerAdvice @ResponseBody //统一在类前面加上@ResponseBody注解,这样在方法上就不需要添加了,返回的数据都是json格式的 public class GlobalExceptionAdvice { private Logger logger = LoggerFactory.getLogger(GlobalExceptionAdvice.class);

/**
 * 使用form data方式调用接口,校验异常抛出 BindException
 * @param e
 * @return
 */
@ExceptionHandler({BindException.class})
public ResultVO<?> BindExceptionHandler(BindException e) {
    List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
    List<String> collect = fieldErrors.stream()
            .map(DefaultMessageSourceResolvable::getDefaultMessage)
            .collect(Collectors.toList());
    logger.error(getMsg(e));
    return new ResultVO<>(ExceptionEnum.ARGUMENT_ERROR.getCode(),ExceptionEnum.ARGUMENT_ERROR.getMsg() , collect.toString());
}

/**
 * 处理参数校验异常。使用 json 请求体调用接口,校验异常抛出 MethodArgumentNotValidException
 * 调用/globalExceptionTest/addUser接口测试。
 * 传入:
 * {"account":"222sada99999999999","password":"3","email":"a519991925"}
 * 报错为:
 * {
 *  "code": 108,
 *  "message": "参数不合法",
 *  "data": "[用户id不能为空, 邮箱格式不正确, 密码长度必须是6-16个字符, 账号长度必须是6-11个字符]"
 * }
 * @param e
 * @return
 */
@ExceptionHandler({MethodArgumentNotValidException.class})
public ResultVO<?> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
    // 从异常对象中拿到ObjectError对象
    List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
    List<String> collect = fieldErrors.stream()
            .map(DefaultMessageSourceResolvable::getDefaultMessage)
            .collect(Collectors.toList());
    logger.error(getMsg(e));
    logger.error(collect.toString());
    return new ResultVO<>(ExceptionEnum.ARGUMENT_ERROR.getCode(),ExceptionEnum.ARGUMENT_ERROR.getMsg() , collect);
}

/**
 * 处理业务自定义异常
 * @param e
 * @return
 */
@ExceptionHandler(value = ApplicationDefinedException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> ApplicationDefinedExceptionHandler(ApplicationDefinedException e){
    logger.error(getMsg(e));
    return new ResultVO<>(e.getCode(),e.getMessage(),null);
}

@ExceptionHandler(value = FlowException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> flowExceptionHandler(FlowException e){
    logger.error(getMsg(e));
    return new ResultVO<>(ExceptionEnum.SENTINEL_FLOW_ERROR.getCode(),ExceptionEnum.SENTINEL_FLOW_ERROR.getMsg(),null);
}

@ExceptionHandler(value = DegradeException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> degradeExceptionHandler(DegradeException e){
    logger.error(getMsg(e));
    return new ResultVO<>(ExceptionEnum.SENTINEL_DEGRADE_ERROR.getCode(),ExceptionEnum.SENTINEL_DEGRADE_ERROR.getMsg(),null);
}

@ExceptionHandler(value = ParamFlowException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> paramFlowException(ParamFlowException e){
    logger.error(getMsg(e));
    return new ResultVO<>(ExceptionEnum.SENTINEL_PARAMS_ERROR.getCode(),ExceptionEnum.SENTINEL_PARAMS_ERROR.getMsg(),null);
}

@ExceptionHandler(value = AuthorityException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> authorityExceptionHandler(AuthorityException e){
    logger.error(getMsg(e));
    return new ResultVO<>(ExceptionEnum.SENTINEL_AUTH_ERROR.getCode(),ExceptionEnum.SENTINEL_AUTH_ERROR.getMsg(),null);
}

@ExceptionHandler(value = SystemBlockException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> systemBlockExceptionHandler(SystemBlockException e){
    logger.error(getMsg(e));
    return new ResultVO<>(ExceptionEnum.SENTINEL_SYSTEM_BLOCK_ERROR.getCode(),ExceptionEnum.SENTINEL_SYSTEM_BLOCK_ERROR.getMsg(),null);
}

/**
 * 处理其它异常
 * @param e
 * @return
 */
@ExceptionHandler(value = Exception.class)
//ResponseStatus没有必要了,因为我们在全局异常处理类中已经设置了,这里统一用200即可

// @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) public ResultVO<?> exceptionHandler(Exception e){ logger.error(getMsg(e)); return new ResultVO<>(ExceptionEnum.UNKNOW_ERROR.getCode(),ExceptionEnum.UNKNOW_ERROR.getMsg(),null); }

private static String getMsg(Exception e) {
    StackTraceElement[] stackTrace = e.getStackTrace();
    StringBuffer sb = new StringBuffer();
    sb.append(e.toString()).append("\r\n");
    for (StackTraceElement stackTraceElement : stackTrace) {
        sb.append("\tat ").append(stackTraceElement.getClassName()).append(".")
                .append(stackTraceElement.getMethodName())
                .append("(").append(stackTraceElement.getFileName()).append(":").append(stackTraceElement.getLineNumber())
                .append(")\r\n");
    }
    return sb.toString();
}

} `

origin解析类: `package com.nriet.cipas.environment.configuration;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component public class RequestOriginParserDefinition implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { // 当前 流控应用 放在了请求参数里面,可以放到的地方有很多,比如 参数/请求头/session/等等 return httpServletRequest.getParameter("sourceName"); } } `

bootstrap.properties相关Sentinel配置: `#sentinel spring.cloud.sentinel.transport.dashboard=localhost:8885 spring.cloud.sentinel.transport.clientIp=${spring.cloud.client.ip-address} spring.cloud.sentinel.transport.port=18719 spring.cloud.sentinel.web-context-unify=false spring.cloud.sentinel.eager=true

spring.cloud.sentinel.datasource.flow.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.flow.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.flow.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.flow.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.flow.nacos.data-id=${spring.application.name}-flow-rules spring.cloud.sentinel.datasource.flow.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.flow.nacos.data-type=json spring.cloud.sentinel.datasource.flow.nacos.rule-type=flow

spring.cloud.sentinel.datasource.degrade.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.degrade.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.degrade.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.degrade.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.degrade.nacos.data-id=${spring.application.name}-degrade-rules spring.cloud.sentinel.datasource.degrade.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.degrade.nacos.data-type=json spring.cloud.sentinel.datasource.degrade.nacos.rule-type=degrade # spring.cloud.sentinel.datasource.system.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.system.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.system.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.system.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.system.nacos.data-id=${spring.application.name}-system-rules spring.cloud.sentinel.datasource.system.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.system.nacos.data-type=json spring.cloud.sentinel.datasource.system.nacos.rule-type=system # spring.cloud.sentinel.datasource.authority.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.authority.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.authority.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.authority.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.authority.nacos.data-id=${spring.application.name}-authority-rules spring.cloud.sentinel.datasource.authority.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.authority.nacos.data-type=json spring.cloud.sentinel.datasource.authority.nacos.rule-type=authority # spring.cloud.sentinel.datasource.param-flow.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.param-flow.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.param-flow.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.param-flow.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.param-flow.nacos.data-id=${spring.application.name}-param-flow-rules spring.cloud.sentinel.datasource.param-flow.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.param-flow.nacos.data-type=json spring.cloud.sentinel.datasource.param-flow.nacos.rule-type=param-flow`

Sentinel-dashboard版本为1.8.0,可以读取、新增、修改、删除该应用程序所有配置。 image

应用服务启动正常,相关测试接口测试正常(包括限流、降级、热点三个规则,都能正常使用) image

Describe what you expected to happen

无论通过手动修改Nacos上的关于系统规则与授权规则的配置文件、还是通过Dashboard修改,都无法生效。 image image

例如以20并发调用测试接口,检验系统规则在并发数为3的生效情况,shiji实际并未触发: image image image

例如配置授权规则,以白名单方式强制限定仅有“cipas-example2”的应用才能调用测试接口,也无法生效。 image image

请问该如何处理这种问题?

fushichenmu commented 1 year ago

像是RequestOriginParser,我查了很多资料,我使用的是com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser来做也无法使得授权规则生效

sadiea commented 11 months ago

不生效是因为在抛异常的时候,内部处理有个空引用,异常就被吞掉了 企业微信截图_94a25004-1784-4f72-8d83-b0c274ea17d4