Spring Security 官方建议使用Spring WebClient 来访问Oauth2资源服务器:
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
@Autowired
private WebClient webClient;
@Test
void test_2022_01_26_16_19_52() {
Object obj = webClient.get()
.uri("http://资源服务器中某个api的地址")
//以client credentials 客户端凭证的模式访问资源,这种模式简单很多,如果需要用户信息可以显式传递
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction
.clientRegistrationId("资源服务器在yml中的注册id"))
.retrieve()
.bodyToMono(Object.class)
//阻塞式调用
.block();
}
如果我们使用Oauth2来保护我们两个服务之间的调用,这样使用资源服务器的形式访问接口api既可,但是如果接口比较多,显然使用feign更为方便,但是官方提供Oauth2认证信息的附加是通过WebClinet添加的,而WebClinet是Spring-webflux 非阻塞式HTTP Client ,但是不管是open-feign 还是SpringCould-Feign 都不支持异步客户端,不过好在官方文档中提醒我们,可以使用社区的feign-reactor来使用异步客户端。
所以我们引入feign-reactor-webclient依赖
<dependency>
<groupId>com.playtika.reactivefeign</groupId>
<artifactId>feign-reactor-webclient</artifactId>
<version>3.1.5</version>
</dependency>
构建我们的 Feign接口
@Headers({"Accept: application/json"})
public interface XXXServiceApi {
String URI = "资源服务器地址/XXXServiceApi";
@RequestLine("GET" + URI + "/obj")
Mono<Result<Object>> getObj(@Param("id") Long id); //Result是自定义的通用返回类
@RequestLine("GET" + URI + "/void")
Mono<Result<Void>> doSomeThing(); //api本身无返回值,但是可能报异常,异常时api返回Result
}
其中,如果被调用的api通过抛出异常来中断执行,并且配置了全局异常处理器,那么最好不管是异常还是正常,返回结果都通过通用返回类来包装:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
private Integer code;
private List<String> messages;
private T data;
public Result<T> code(Integer code) {
this.code = code;
return this;
}
public Result<T> messages(Collection<String> messages) {
this.messages = new ArrayList<>(messages);
return this;
}
@SuppressWarnings("unchecked")
public <O> Result<O> data(O data) {
this.data = (T) data;
return (Result<O>) this;
}
public static <O> Result<O> ok() {
return new Result<O>().code(200).messages("success");
}
public static <O> Result<O> ok(O data) {
return ok().data(data);
}
public static <O> Result<O> err() {
return new Result<O>().code(-1);
}
}
通过code判断是否成功,在异常处理器中将错误信息全部放在messages字段, 方法返回值放在data字段中,这样feign就可以很方便的反序列化正常返回值和异常返回值。
然后只需要将WebClinet OAuth2配置 和 feign-reactor 结合使用即可 👇
@Bean
public XXXServiceApi xxxServiceApi(OAuth2AuthorizedClientManager authorizedClientManager) {
return WebReactiveFeign
.<XXXServiceApi>builder(WebClient.builder()
.apply(new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager).oauth2Configuration())
.defaultRequest(spec -> spec.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId("资源服务器在yml中的注册id")))
)
.target(XXXServiceApi.class, "资源服务器地址");
}
直接调用 XXXServiceApi ,或者再将XXXServiceApi 包装一层,阻塞调用并判断code不为200时抛出异常:
public abstract class FeignServiceUtil {
public static <T> T checkAndReturn(Mono<Result<T>> result) {
return checkReturn(result.block());
}
public static <T> T checkAndReturn(Result<T> result) {
if (result == null) {
return null;
}
if (result.getCode() != 200) {
List<String> err = new ArrayList<>(result.getMessages());
throw new RuntimeException(StringUtils.collectionToCommaDelimitedString(err));
}
return result.getData();
}
}