Retrofit使用鉴权调用接口的最佳实践

395 阅读1分钟

引入依赖

2.3.5版本引入了@Logging注解,更加方便

<dependency>
    <groupId>com.github.lianjiatech</groupId>
    <artifactId>retrofit-spring-boot-starter</artifactId>
    <version>2.3.5</version>
</dependency>

启动类加入注解

@SpringBootApplication
@RetrofitScan("com.mason.api")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

定义鉴权 (无需鉴权的可以跳过)

@RetrofitClient(baseUrl = "${business.baseUrl}")
public interface IAuthService {

    /**
     * 假如鉴权是如下接口路由
     */
    @POST("/auth/token")
    InterfaceResponse<TokenDto> getToken(@Body TokenQo tokenQo);
}

定义被调用的接口

@RetrofitClient(baseUrl = "${business.baseUrl}")
@Sign(clientId = "${business.clientId}", clientSecret = "${business.clientSecret}", handler = SignInterceptor.class)
public interface IBusinessService {

    /**
     * 
     * @return Integer
     */
    @GET("/order")
    InterfaceResponse<OrderDto> getOrder(@Query("orderSn") String orderSn);

@Sign的代码内容

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@InterceptMark
public @interface Sign {
    /**
     * 密钥key
     * 支持占位符形式配置。
     *
     * @return String
     */
    String clientId();

    /**
     * 密钥
     * 支持占位符形式配置。
     *
     * @return String
     */
    String clientSecret();

    /**
     * 拦截器匹配路径
     *
     * @return String[]
     */
    String[] include() default {"/**"};

    /**
     * 拦截器排除匹配,排除指定路径拦截
     *
     * @return String[]
     */
    String[] exclude() default {};

    /**
     * 处理该注解的拦截器类
     * 优先从spring容器获取对应的Bean,如果获取不到,则使用反射创建一个!
     *
     * @return Class
     */
    Class<? extends BasePathMatchInterceptor> handler() default SignInterceptor.class;
}

对应@Sign的SignInterceptor拦截器

@Component
public class SignInterceptor extends BasePathMatchInterceptor {

    private String clientId;
    private String clientSecret;

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    @Resource
    private IAuthService authService;

    @Override
    public Response doIntercept(Chain chain) throws IOException {
        TokenQo tokenQo = new TokenQo();
        tokenQo.setClientId(clientId);
        tokenQo.setClientSecret(clientSecret);
        InterfaceResponse<TokenDto> tokenResponse = authService.getToken(tokenQo);

        Request request = chain.request();
        Request newReq = request.newBuilder()
                .addHeader("Content-Type", "application/json")
                .addHeader("Authorization", "Bearer " + tokenResponse.getData().getAccess_token())
                .build();
        // 发起请求
        return chain.proceed(newReq);
    }

}

注意, 别忘了加这个bean,防止序列化和反序列化出现问题

@Bean
@ConditionalOnMissingBean
public JacksonConverterFactory jacksonConverterFactory() {
    JavaTimeModule timeModule = new JavaTimeModule();
    timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER));
    timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER));

    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addSerializer(Long.class, ToStringSerializer.instance);

    JsonMapper mapper = JsonMapper.builder()
            .findAndAddModules()
            .addModule(timeModule)
            .addModule(simpleModule)
            .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
            .serializationInclusion(JsonInclude.Include.NON_NULL)
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .build();
    return JacksonConverterFactory.create(mapper);
}

另外一种方案推荐 forest

forest.dtflyx.com/pages/1.5.x…