Feign接口内部调用进行权限校验

在目前微服务的体系下,我们服务url的访问有两种,一种是从网关(gateway,zuul)路由进来,还有一种就是通过feign接口内部调用,那么结合spring-security就存在以下几种场景:
1.外部请求从gateway访问,需要鉴权(任何CURD的操作).这是目前最常见的方式,用户正常登录提供token访问接口,我们不再需要做其他处理.
2.外部请求从gateway访问,不需要鉴权(eg:刷新验证码,短信验证码获取…). 此时我们需要将放行的接口加入到ignore-urls配置里,可以不需要做鉴权处理
3.内部服务使用feign调用,不需要鉴权(根据id查询信息…). 也需要将url加入到ignore-urls中,配置放行,但是这样一来,不仅仅是这个服务可以访问这个接口,其他服务也可以,甚至是外部请求也可以访问到,这样就会有很大的安全问题.

鉴于上述第三种情况,我们配置了ignore-url和Feign,此时该接口不需要鉴权,服务内部通过Feign访问,服务外部通过url也可以访问,所以我们需要加入一种@RequestHeader(CommonConstants.WHERE)的处理方式。即在接口方法中,对头部进行判断,只有请求带上相应的Header参数时,才允许通过访问,否则抛出异常。那这时候其实我们在外网通过Gateway访问的时候,也可以手动带上这个Header参数,来达到这个目的。所以我们便在Gateway中设置了一个GlobalFilter过滤器,对来自外网通过Gateway手动拼接的参数进行过滤与清洗,具体代码见com.lfs.gateway.filter.LfsRequestGlobalFilter:

@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 请求头中where 参数删除
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> httpHeaders.remove(CommonConstants.WHERE)).build();

    // 2. 重写StripPrefix
    addOriginalRequestUrl(exchange, request.getURI());
    String rawPath = request.getURI().getRawPath();
    String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/")).skip(1L)
            .collect(Collectors.joining("/"));
    ServerHttpRequest newRequest = request.mutate().path(newPath).build();
    exchange.getAttributes().put(REQUEST_URL_ATTR, newRequest.getURI());
    return chain.filter(exchange.mutate().request(newRequest.mutate().build()).build());
}

下面是Inner注解的代码:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {
/**
* 是否AOP统一处理(可以理解为是否仅允许Feign之间调用)
*
* @return false, true
*/
boolean value() default true;

/**
* 需要特殊判空的字段(预留)
*
* @return {}
*/
String[] field() default {};
}
接下来是框架中加入环绕切面:

@Slf4j
@Aspect
@AllArgsConstructor
public class SecurityInnerAspect {
private final HttpServletRequest request;

@SneakyThrows
@Around("@annotation(inner)")
public Object around(ProceedingJoinPoint point, Inner inner) {
String header = request.getHeader(CommonConstans.WHERE);
if (inner.value() && !StrUtil.equals(CommonConstans.IN, header)) {
log.warn(“访问接口 {} 没有权限”, point.getSignature().getName());
throw new BizException(“Access is denied”);
}
return point.proceed();
}
}
合理的使用@Inner注解

在我们的代码中,可以直接使用Inner注解的,下面结合Feign做一个简单的示例,比如获取用户信息这个接口:

在接口上使用@Inner注解,使得url无需鉴权
/**
* 获取指定用户全部信息
*
* @return 用户信息
/
@Inner
@GetMapping("/info/{userId}")
public Result getUserInfo(@PathVariable Integer userId) {
SysUser user = userService.getOne(Wrappers.query()
.lambda().eq(SysUser::getUserId, userId));
if (user == null) {
return Result.fail(null, String.format(“用户信息为空 %s”, userId));
}
return Result.success(userService.findUserInfo(user));
}
编写Feign接口
@FeignClient(contextId = “userService”, value = CommonConstans.USER_SERVER)
public interface UserService {
/
*
* 通过用户id查询用户、角色信息
*
* @param 用户id
* @param from 调用标志
* @return R
/
@GetMapping("/user/info/{userId}")
R getUserInfo(@PathVariable(“userId”) Integer userId
, @RequestHeader(CommonConstants.WHERE) String where);
}
Feign-Client中调用接口,带上CommonConstants.IN参数为内部识别
/
*
* 用户密码登录
*
* @param userId用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
@SneakyThrows
public UserDetails loadUserByUserId(Integer userId) {
R result = userService.getUserInfo(userId, CommonConstants.IN);
UserDetails userDetails = getUserDetails(result);
cache.put(userId, userDetails);
return userDetails;
}

现在"/info/{userId}" 这个uri从网关外部我们访问是报错的(一般来说服务都是走网关暴露接口),而Feign内部带上参数是可以正常访问的.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

huayang183

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值