How to create spring cloud gateway filter with synchronous http request - spring

I'm creating a GateWayFilter to authenticate a request with a ticket.
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//If you want to build a "pre" filter you need to manipulate the
//request before calling chain.filter
ServerHttpRequest serverHttpRequest = exchange.getRequest();
ServerHttpRequest.Builder builder = serverHttpRequest.mutate();
//use builder to manipulate the request
List<String> tickets = serverHttpRequest.getQueryParams().get(TICKET);
if (CollectionUtils.isEmpty(tickets)) {
return onError(exchange);
}
String ticket = tickets.get(0);
// todo use MONO?
// todo add cache
final ApiCaller apiCaller;
try {
apiCaller = HttpRequestUtil.getObject(
"http://localhost:9001/authapi/authentication/ticket/" + ticket, ApiCaller.class);
} catch (IOException e) {
log.warn("failed authenticate ticket: {}", ticket, e);
return onError(exchange);
}
if (apiCaller == null || StringUtils.isBlank(apiCaller.getLipCode())) {
log.info("not valid ticket: {}", ticket);
return onError(exchange);
}
ApiCallerUtil.addApiCaller(builder, apiCaller);
return chain.filter(exchange.mutate().request(builder.build()).build());
}
the problem is I use a blocking http request in the method,
Question 1: Is there any disadvantage of using the blocking HTTP request?
Question 2:How can I chang to reactor http request to get the ApiCaller Information?

Related

Okhttp3.14 Stream closed

I have some usage issue about okhttp in 3.14.9 release
if i want add LoggingInterceptor for each request, how can i get response body, which can only consume once?
And follow is my attemp
public class LoggingRequestInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
log.debug(
"{}, {}, {}, {}, {}, {}, {}",
request.url(),
request.method(),
JSONUtil.toJsonStr(request.body()),
request.headers(),
dup.body() == null ? null : dup.body().string());
return response;
}
}
It will throw exception of stream closed, how to fix it?
Use peekBody for this
val client = OkHttpClient.Builder()
.addInterceptor {
val response = it.proceed(it.request())
println(response.peekBody(1000000).string())
response
}
.build()
I do research for this issue. We can use buffer, which is in RequestBody-source-getBuffer.
Working code is below:
public String getResponseBody(Response response) {
try {
ResponseBody responseBody = response.body();
if (!ObjectUtil.isNull(responseBody)) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.getBuffer();
return buffer.clone().readString(UTF8);
}
} catch (IOException e) {
log.error("get response body failed: ", e);
}
return null;
}
From ernest-kiwele:
Using a try with a resources block with the response causes this "closed" problem when the response body is read outside of the try block:
try (Response response = client.newCall(request.build()).execute()) {
return response;
} //response.close() called implicitly by the JVM
The fix is to restructure the code to only use the response within the try block.

How to handle the http response from an api call efficiently using spring boot

When we fire an api call to a 3rd party service, we can get different HTTP responses (200, 404 etc.). How can we handle them in a standard way?
private ResponseEntity<ResultHolder> responseEntity;
public ResponseEntity<ResultHolder> serviceTest(String searchText, String countryCode) {
logger.info("Service started");
String url = prepareUrl(searchText,countryCode); //custom method
HttpHeaders header = new HttpHeaders();
prepareHeader(header); //custom method
HttpEntity<String> requestEntity = new HttpEntity<String>(header);
try {
logger.info("Calling the API");
responseEntity = restClient.exchange(url,
HttpMethod.GET,
requestEntity,
ResultHolder.class);
}catch (Exception e) {
logger.error("Exception while calling the API "+ e);
//Here I am trying to get the value of response code and handle based on that
//Is this the right way to solve the problem?
if(responseEntity.getStatusCodeValue() != 200) {
responseEntity = new ResponseEntity<ResultHolder>(
HttpStatus.BAD_REQUEST);
}
}
logger.info("Service Ended");
return responseEntity;
}
What if I want to display distinct custom messages for server side errors and for user errors like 'No Internet Connection'.
Kindly help me to understand the good practises in this area.
Thank you

Is there a simpler exception handling technique for Spring?

I have read about controller based exceptions using #ExceptionHandler.
I have read about global exception handling using #ControllerAdvice.
I have also read about extending HandlerExceptionResolver for more in-depth exception handling.
However, what I would ideally like to do is be able to throw a global exception with parameters that dictate a JSON response returned to the client, at any layer in my application.
For instance:
throw new CustomGlobalException(HttpStatus.UNAUTHORISED, "This JWT Token is not Authorised.")
throw new CustomGlobalException(HttpStatus.FORBIDDEN, "This JWT Token is not valid.")
This would then return a JSON response based on the model I've created, along with the status, such as :
{
"success" : "false",
"message" : "This JWT Token is not Authorised."
}
And for this to be returned as a REST response from my controller.
Is something like this possible? Or Do I have to go through the process of making custom error exceptions for everything as described in the documentation.
To clarify, I require the exception to interrupt whatever the ongoing process is, perhaps fetching data from the database, and immediately return the given exception to the client. I have a web mvc setup.
Further details:
#ControllerAdvice
#RequestMapping(produces = "application/json")
public class GlobalExceptionHandler {
#ExceptionHandler(CustomException.class)
public ResponseEntity<Object> handleCustomException(CustomException ex,
WebRequest request) {
Map<String, Object> response = new HashMap<>();
response.put("message", ex.getMessage());
return new ResponseEntity<>(response, ex.getCode());
}
}
Exception thrown here:
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain
filterChain) throws ServletException, IOException {
logger.debug("Filtering request for JWT header verification");
try {
String jwt = getJwtFromRequest(request);
logger.debug("JWT Value: {}", jwt);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken
(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
logger.error("No Valid JWT Token Provided");
throw new CustomException(HttpStatus.UNAUTHORIZED, "No Valid JWT Token Provided");
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
This doesn't exactly do what you want to achieve, but the simplest way of doing almost what you want (and is cleaner, IMO), is to simply define an exception like the following:
#ResponseStatus(HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException {
public UnauthorisedException(String message) {
super(message);
}
}
Now every time such an exception is thrown (not returned) from a controller method (directly or indirectly), you'll get a response such as
{
"timestamp": "2018-06-24T09:38:51.453+0000",
"status": 401,
"error": "Unauthorized",
"message": "This JWT Token is not Authorised.",
"path": "/api/blabla"
}
And of course the actual status code of the HTTP response will also be 401.
You can also throw a ResponseStatusException, which is more generic and allows you to use the same exception type and pass the status as argument. But I find it less clean.
Following my post on how to handle exception here, you can write your own handler something like this,
class CustomGlobalException {
String message;
HttpStatus status;
}
#ExceptionHandler(CustomGlobalException.class)
public ResponseEntity<Object> handleCustomException(CustomGlobalException ex,
WebRequest request) {
Map<String, Object> response = new HashMap<>();
response.put("success", "false");
response.put("message", ex.getMessage());
return new ResponseEntity<>(response, ex.getStatus());
}
Code mentioned above will handle CustomGlobalException occurred any layer of code.
Since Spring 5 and Above, a ResponseStatusException (spring framework provided) would be better.
Please refer to spring-response-status-exception

Spring SSO with facebook filter and JWT

here is my problem.
I got a first authentication with mail and JWT with Spring boot 1.5.3.
=> works perfectly
Then i made a SSO filter to allow facebook tokens
The thing is, on first authentication it's ok. My server get the Token, then check with fb that says ok then it says ok to my client.
After that if i don't encode my token with my JWT token enhancer, my server says that it is not able to decode it as JSON.
Just that i know, i would normally not have to encode myself as it should be done automatically after my chain filter if i say ok ??
This code works but i've done the jwt myself, is that possible i've missed something ????
public class MyOAuth2ClientAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {
public MyOAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
log.info("[attemptAuthentication facebook]");
Authentication result = null;
try {
String token = request.getHeader("oauth_token");
oauth2ClientContext.setAccessToken(new DefaultOAuth2AccessToken(token));
result = super.attemptAuthentication(request, response);
if(result.isAuthenticated()) {
FacebookService facebookService = new BasicFacebookService(token);
User fbUser = facebookService.getUser();
if(fbUser == null) {
throw new IllegalArgumentException(" fb user cannot be null");
}
if(!userService.isLoginExists(fbUser.getId())) {
CreateSocialUserModel model = new CreateSocialUserModel(
token,
DateUtil.getNow(),
"facebook");
userService.createSocialUser(model, fbUser);
}
//--- Create custom JWT token from facebook token
UserInfoTokenServices tokenService = new UserInfoTokenServices(
"https://graph.facebook.com/me",
facebookProperties.getAppId());
OAuth2AccessToken enhancedToken = jwtTokenEnhancer.enhance(oauth2ClientContext.getAccessToken(),
tokenService.loadAuthentication(oauth2ClientContext.getAccessToken().getValue()));
TokenResponse tokenResponse = new TokenResponse(enhancedToken.getValue(),
enhancedToken.getTokenType(),
enhancedToken.getRefreshToken() != null ? enhancedToken.getRefreshToken().getValue() : "");
ObjectMapper mapper = new ObjectMapper();
String jsonTokenEnhancedJack = mapper.writeValueAsString(tokenResponse);
response.addHeader("Content-Type", "application/json");
response.getWriter().flush();
response.getWriter().print(jsonTokenEnhancedJack);
}
return result;
} catch (Exception e) {
log.info("error");
log.error("error", e);
e.printStackTrace();
} finally {
return result;
}
}
}
Thank you in advance
As asked by Son Goku just putting some code to help him
First you have to put the filter like this
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable();
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new MyOAuth2ClientAuthenticationProcessingFilter("/user/social");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(Oauth2facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(new UserInfoTokenServices(
"https://graph.facebook.com/me",
facebookProperties.getAppId()
));
return facebookFilter;
}
After that you can use the OAuth2ClientAuthenticationProcessingFilter as in first question.
Also, in first question i customized this method a lot, it works but i was surprised to not find this as easy as these library use to be.
Hope it helps you, i have struggled a bit too on this.
Maybe now, spring boot handle this much more easily.

Why OAuth2AccessTokenSupport always send POST request ??

I'm working with a Spring Boot + Spring Security OAuth2 to consume the Restful Oauth2 service.
Our Oauth2 service is always expects HTTP GET But OAuth2AccessTokenSupport always sending HTTP POST.
Result:
resulted in 405 (Method Not Allowed); invoking error handler
protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource,
MultiValueMap<String, String> form, HttpHeaders headers) throws OAuth2AccessDeniedException {
try {
this.authenticationHandler.authenticateTokenRequest(resource, form, headers);
this.tokenRequestEnhancer.enhance(request, resource, form, headers);
AccessTokenRequest copy = request;
ResponseExtractor delegate = getResponseExtractor();
ResponseExtractor extractor = new ResponseExtractor(copy, delegate) {
public OAuth2AccessToken extractData(ClientHttpResponse response) throws IOException {
if (response.getHeaders().containsKey("Set-Cookie")) {
this.val$copy.setCookie(response.getHeaders().getFirst("Set-Cookie"));
}
return ((OAuth2AccessToken) this.val$delegate.extractData(response));
}
};
return ((OAuth2AccessToken) getRestTemplate().execute(getAccessTokenUri(resource, form), getHttpMethod(),
getRequestCallback(resource, form, headers), extractor, form.toSingleValueMap()));
} catch (OAuth2Exception oe) {
throw new OAuth2AccessDeniedException("Access token denied.", resource, oe);
} catch (RestClientException rce) {
throw new OAuth2AccessDeniedException("Error requesting access token.", resource, rce);
}
}
<b>protected HttpMethod getHttpMethod() {
return HttpMethod.POST;
}</b>
protected String getAccessTokenUri(OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form) {
String accessTokenUri = resource.getAccessTokenUri();
if (this.logger.isDebugEnabled()) {
this.logger.debug(new StringBuilder().append("Retrieving token from ").append(accessTokenUri).toString());
}
StringBuilder builder = new StringBuilder(accessTokenUri);
String separator;
if (getHttpMethod() == HttpMethod.GET) {
separator = "?";
if (accessTokenUri.contains("?")) {
separator = "&";
}
for (String key : form.keySet()) {
builder.append(separator);
builder.append(new StringBuilder().append(key).append("={").append(key).append("}").toString());
separator = "&";
}
}
return builder.toString();
}
Can Anyone explain me why OAuth2AccessTokenSupport always returns POST and
How to send HTTP GET request
To enable GET requests for the token endpoint, you need to add the following in your AuthorizationServerConfigurerAdapter:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
As for why only POST by default: I think that is due to GET requests potentially sending username and password information as request params (this is certainly the case for password grant). These may well be visible in web server logs, while POST body data is not.
Indeed the RFC for OAuth2 declares that the client must use HTTP POST when requesting an access token (https://www.rfc-editor.org/rfc/rfc6749#section-3.2)

Resources