有状态的FeignClient(客户端与服务器之间的会话)

时间:2018-10-22 15:41:02

标签: spring-cloud setcookie jsessionid stateful spring-cloud-feign

我一直在与@FeignClient争夺Spring Cloud。 我们有根“网关” WAR来调用“后端”服务WAR,我们在其中缓存用户角色特定的元数据(该数据应该足够大,可以在每次调用时请求它,而要足够小,可以将其存储在RAM内存中)。

我需要客户端能够处理Set-Cookie,然后在客户端会话处于活动状态时将JSESSIONID放到服务器端。因此,我相信我需要为特定的网关用户将所有服务器端的sessionIds存储在某个会话范围的bean中。像

"client1" => "8D06349922CD77D1CE68F78F4FAE04C5" 
"client2" => "another session id"

上网冲浪后,我找到了ApacheHttpClient和Spring RestTemplate的解决方案。关于Feign的唯一一件事就是可以获取@RequestHeader(“ Cookie”)作为远程函数的参数。

好吧,我写了一些粗鲁的代码来达到目的:

HttpMessageConverter httpMessageConverter = new HttpMessageConverter() {
            @Override
            public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
                HttpHeaders headers = inputMessage.getHeaders();
                logger.info("headers: {}", headers);
                List<String> setCookie = headers.get("Set-Cookie");
                logger.info("setCookie: {}", setCookie);
                if(setCookie != null){
                    String jsId = setCookie.get(0).split(";")[0].split("=")[1];
                    logger.info("jsId Object read: {}", jsId);
                    cookies.setSessionId("key", jsId);
                }
                return delegate.read(clazz,inputMessage);
            }
};

.....

RequestInterceptor jsessionFeignRequestInterceptor = new RequestInterceptor() {
    @Override
    public void apply(RequestTemplate template) {
        String sessionId = cookies.getSessionId("key");
        logger.info("THE JSESSIONID: {}", sessionId);
        if(sessionId != null) {
            template.header("Cookie", "JSESSIONID=" + sessionId);
        }
    }
};

....

 Feign.builder()
    .encoder(new SpringEncoder(new HttpMessageConverters(httpMessageConverter)))
    .requestInterceptor(jsessionFeignRequestInterceptor)
    .requestInterceptor(oauth2FeignRequestInterceptor)........

但是我觉得这很奇怪。

是否可能还有其他“合适的选择”来实现类似的功能?

注意:我们正在使用OAuth2 Spring Auth Server。

谢谢。

1 个答案:

答案 0 :(得分:0)

通过将OkHttpClient与自定义拦截器结合使用,我已经做了类似的事情。

Builder builder = Feign
    .builder()
    .client(new OkHttpClient(new okhttp3.OkHttpClient.Builder().addInterceptor(new SessionIdRequestInterceptor()).build()));

OkHttpClient也支持Authenticator接口,但是仅在401 http状态下调用。由于我的身份验证服务器重定向到带有200或302的登录页面,因此我必须对身份验证(即拦截器)进行自定义验证。这是我的实现:

class SessionIdRequestInterceptor implements okhttp3.Interceptor {

    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
        okhttp3.Request authenticatedRequest = authenticateRequest(chain.request());
        okhttp3.Response response = chain.proceed(authenticatedRequest);
        if (isAuthenticated(response)) {
            saveSessionId(response);
        } else if (jSessionId != null) {
            jSessionId = null;
            response.body().close();
            authenticatedRequest = authenticateRequest(authenticatedRequest);
            response = chain.proceed(authenticatedRequest);
        }
        if (!isAuthenticated(response)) {
            throw new PermissionDeniedException("Failed Authentication");
        }
        return response;
    }

    private void saveSessionId(okhttp3.Response response) {
        String setCookie = response.header("Set-Cookie");
        if (setCookie != null) {
            jSessionId = setCookie.split(";")[0].split("=")[1];
        }
    }

    private boolean isAuthenticated(okhttp3.Response response) {
        return !response.request().url().encodedPath().contains("/cas-server/login");
    }

    private okhttp3.Request authenticateRequest(okhttp3.Request request) {
        okhttp3.Request.Builder builder = request.newBuilder();
        if (jSessionId != null) {
            builder.addHeader("Cookie", "JSESSIONID=" + jSessionId);
        } else {
            builder
                .addHeader("Authorization", "Basic " + Base64
                    .getEncoder()
                    .encodeToString((configuration.getUser() + ":" + configuration.getClearPassword()).getBytes()));
        }
        return builder.build();
    }
}