【重構】Spring Cloud OAuth 無Token調用源碼封裝的一波三折

背景

重構-改善既有代碼的設計,重構的目的是是軟件更容易被理解和修改。git

書接上回Spring Security OAuth 微服務內部Token傳遞的源碼解析,本篇主要無token 調用過程當中,代碼的不斷完善及其重構過程spring

需求很簡單以下圖,若是資源服務器的提供的接口,客戶端不須要身份驗證即不須要攜帶合法令牌也能訪問,而且能夠實現遠程調用的安全性校驗。 api

初版本

資源服務器設置接口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 資源服務想對外暴露的接口,須要兩步
  1. 聲明在ResourceServerConfigurerAdapter 的 permitall
  2. 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
複製代碼

總結

歡迎關注咱們得到更多的好玩JavaEE 實踐安全

相關文章
相關標籤/搜索