![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAjM2EzLcd3LcJzLcJzdllmVldWYtl2Pn5GcuUHbkV2chRnM4IzLcZTN3kjNwQzLcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
背景
重構-改善既有代碼的設計,重構的目的是是軟體更容易被了解和修改。
書接上回Spring Security OAuth 微服務内部Token傳遞的源碼解析,本篇主要無token 調用過程中,代碼的不斷完善及其重構過程。
需求很簡單如下圖,如果資源伺服器的提供的接口,用戶端不需要身份驗證即不需要攜帶合法令牌也能通路,并且可以實作遠端調用的安全性校驗。
第一版本
資源伺服器設定接口permitall,配置ignore url 即可
ignore-urls:
- /actuator/**
- /v2/api-docs
複制
保證A對外暴露,A --> B 暴露的服務接口安全
- 自定義 @Inner
- 校驗邏輯,判斷接口請求中是否含有 XX 請求頭
/**
* @author lengleng
* <p>
* 服務間接口不鑒權處理邏輯
*/
@Slf4j
@Aspect
@Component
@AllArgsConstructor
public class PigxSecurityInnerAspect {
private final HttpServletRequest request;
@SneakyThrows
@Around("@annotation(inner)")
public Object around(ProceedingJoinPoint point, Inner inner) {
String header = request.getHeader(SecurityConstants.FROM);
if (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) {
log.warn("通路接口 {} 沒有權限", point.getSignature().getName());
throw new AccessDeniedException("Access is denied");
}
return point.proceed();
}
}
複制
- 網關請求含有XX 的請求頭,避免僞造
public class PigxRequestGlobalFilter implements GlobalFilter, Ordered {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
/**
* Process the Web request and (optionally) delegate to the next
* {@code WebFilter} through the given {@link GatewayFilterChain}.
*
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 清洗請求頭中from 參數
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> {httpHeaders.remove(SecurityConstants.FROM);})
.build();
return chain.filter(exchange.mutate()
.request(newRequest.mutate()
.header(HEADER_NAME, basePath)
.build()).build());
}
@Override
public int getOrder() {
return -1000;
}
}
複制
- 接口使用,首先聲明 B服務的這個接口對外暴露
ignore-urls:
- /info/*
複制
- 接口使用,然後在 B服務的這個接口 添加@Inner注解
@Inner
@GetMapping("/info/{username}")
public R info(@PathVariable String username) {
}
複制
重構
- 上邊第一版本的問題是,對于A/B 資源服務想對外暴露的接口,需要兩步
- 聲明在ResourceServerConfigurerAdapter 的 permitall
- B服務要再次添加@inner 注解
實作@Inner 一步到位到位
- 在ignoreU月曆 擷取全部Controller 中,标志@Inner 注解的請求,自動維護到忽略的URL,減少開發配置
public class PermitAllUrlProperties implements InitializingBean {
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
@Autowired
private WebApplicationContext applicationContext;
@Getter
@Setter
private List<String> ignoreUrls = new ArrayList<>();
@Override
public void afterPropertiesSet() {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 擷取方法上邊的注解 替代path variable 為 *
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
Optional.ofNullable(method)
.ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, StringPool.ASTERISK))));
});
}
}
複制
- 核心是通過RequestMappingHandlerMapping 擷取全部的路由配置,然後對 Requestmappint 設定的URL 進行規則替換,然後添加到 ignoreurl中,然後在注入到 ResourceServerConfigurerAdapter 進行permitall
- 使用時候,如果是外部暴露
@Inner(value=false)
複制
- 如果僅是服務内部調用暴露
@Inner
複制