Spring Reactive serverWebExchange unable to get secure cookie - spring

I have a Rest controller in Spring Reactive defined as below:
#PostMapping("/a/b/load")
public Mono<Object> loadProductToMachine(#RequestBody ProductRequest productRequest,
ServerWebExchange serverWebExchange) {
//body code is ignored.
}
I am unable to get cookie information from the serverWebExchange when the cookie is in the following format
SESSION={{sid}}; Path=/; Secure; HttpOnly;
However, when the "Secure" is removed from the cookie, I am able to get the session id from the serverWebExchange.
SESSION={{sid}}; Path=/; HttpOnly;
How can I solve this problem? I need "Secure" field in the cookie. I send the request from Postman in my local environment.

Related

Invalid Cookie Header returned by GET controller in both POSTMAN and Insomnia

I have the following Spring controller class:
#CrossOrigin
#RestController
#Slf4j
public class RcmApi extends ApiController {
#Value("${rcmRestApiServer}")
private String rcmRestApiServer;
#GetMapping(value = "/rcm/api/v1/matter/{matterId}", produces = "application/json")
public ResponseEntity<String> getMatter(#PathVariable String matterId) throws Exception {
log.info("Received call to RCM api getMatter: {}", matterId);
return buildGetResponseEntity("/api/v1/cases/" + matterId + "/aggregate");
}
private ResponseEntity<String> buildGetResponseEntity(String target) throws Exception {
return buildResponseEntity(
new HttpGet(rcmRestApiServer + target), HttpClientBuilder.create().build());
}
}
The method buildResponseEntity() referenced by buildGetResponseEntity() is defined in the base class ApiController:
public ResponseEntity<String> buildResponseEntity(HttpUriRequest request, HttpClient client)
throws Exception {
HttpResponse response = client.execute(request);
return ResponseEntity.status(response.getStatusLine().getStatusCode())
.headers(convertHeaders(response.getAllHeaders()))
.body(EntityUtils.toString(response.getEntity()));
}
public HttpHeaders convertHeaders(Header[] responseHeaders) {
HttpHeaders headers = new HttpHeaders();
Arrays.stream(responseHeaders)
.forEach(header -> headers.add(header.getName(), header.getValue()));
return headers;
}
The String matterId that the top-level method getMatter() receives is of form uuid, e.g c445e164-842f-44ec-9e38-6ae3a99fefd8. Unfortunately, when testing this endpoint locally from my POSTMAN at localhost:8084/rcm/api/v1/matter/c445e164-842f-44ec-9e38-6ae3a99fefd8, I notice the following:
POSTMAN receives a 200 OK but with boilerplate HTML source for a redirect page.
More interestingly, the controller thread logs of an "Invalid Cookie Header" at WARN - level:
2022-07-18 20:05:52.331-04:00 INFO 60322 --- [reactor-http-nio-3] o.f.r.caseapi.gateway.controller.RcmApi : Received call to RCM api getMatter: c445e164-842f-44ec-9e38-6ae3a99fefd8
2022-07-18 20:05:56.803-04:00 WARN 60322 --- [reactor-http-nio-3] o.a.h.c.protocol.ResponseProcessCookies : Invalid cookie header: "Set-Cookie: AWSALB=pAa3xa4sTidJy1nU1HKgYZEGx55KVvoCyojb+0FWnPksfr8qSmfBLg052RiLhw7FmhDYzSxzikY7rKIhfisr6YCP08ubdoUcSjJqOf8UcndIpU7q9fQzqM13GTYA; Expires=Tue, 26 Jul 2022 00:05:54 GMT; Path=/". Invalid 'expires' attribute: Tue, 26 Jul 2022 00:05:54 GMT
2022-07-18 20:05:56.804-04:00 WARN 60322 --- [reactor-http-nio-3] o.a.h.c.protocol.ResponseProcessCookies : Invalid cookie header: "Set-Cookie: AWSALBCORS=pAa3xa4sTidJy1nU1HKgYZEGx55KVvoCyojb+0FWnPksfr8qSmfBLg052RiLhw7FmhDYzSxzikY7rKIhfisr6YCP08ubdoUcSjJqOf8UcndIpU7q9fQzqM13GTYA; Expires=Tue, 26 Jul 2022 00:05:54 GMT; Path=/; SameSite=None; Secure". Invalid 'expires' attribute: Tue, 26 Jul 2022 00:05:54 GMT
Thinking that POSTMAN was messing up the request cookie somehow, I have tried the exact same process through INSOMNIA, getting the exact same behavior. Any help appreciated.
Try to prepare your HttpClient like so:
HttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(RequestConfig.custom()
.setCookieSpec(CookieSpecs.STANDARD).build())
.build();

Spring OAuth2 - how to use the /oauth/authenticate endpoint?

So...I'm struggling to implement an authorization server with using Spring Boot OAuth2. For now I get a 403 response on:
GET oauth/authorize?username=demo&password=demo&client_id=demo&response_type=token
For the love of god, is the request okay? I would like to call this endpoint from a browser application and it should return an access_token and a refresh_token. Why do I need to provide a client_id for this? I'm on the edge of a mental breakdown because of this. How are you supposed to send a request to this endpoint?
The response is:
{
"timestamp": "2019-09-15T05:03:17.206+0000",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/oauth/authorize"
}
Edit:
My simplified question would be this: Is there an endpoint that comes with #EnableAuthorizationServer, and it works as I am imagining it? You provide a username and a password, and it returns an access_token and a refresh_token.
The answer is yes the endpoint is POST /oauth/token
With parameters :
username -> YOUR_USERNAME
password -> YOUR_PASSWORD
grant_type -> password
The clientId and the secret must be send in the Authorization header.
ClientId is just for user to accessing the server. so first create a server and then try to create client:
in server add this code:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("ClientId")
.secret("secret")
.authorizedGrantTypes("authorization_code")
.scopes("user_info")
.autoApprove(true);
}
Client : add you client id properly in spring property what you have kept in server

Spring security - Get SESSION cookie value in AuthenticationSuccessHandler

I know that spring security creates a cookies names SESSION on successful authentication. Is it possible to get hold of that cookie value in AuthenticationSuccessHandler.
I have a following implementation inside which I need that SESSION cookie value. I looked as response headers of HttpServletResponse, but they have XSRF-TOKEN set-cookie headers,
#Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {
// GET SESSION, COOKIE VALUE HERE
}
}
Can you please help.
The SESSION cookie is created by Spring Session's DefaultCookieSerializer, which is called every time a new Session is created, and not necessarily after successful authentication.
Spring Session's SessionRepositoryFilter wraps the HttpServletRequest in such a way that whenever you obtain an HttpSession from the request at any point in your application, you're actually getting a Spring Session object. However, this cookie is written to the response after your handler has been called, as you can see in SessionRepositoryFilter:
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession(); //the SESSION cookie is created if necessary
}
So if the session has just been created for this request...
The cookie won't be available in the HttpServletRequest because the cookie hasn't been sent yet (and so the browser couldn't have sent it)
The cookie won't be HttpServletResponse as a "Set-Cookie" header because it will be written after your application has handled the request.
However, you could get the cookie value:
String cookieValue = request.getSession().getId();
Note: The above code will force Spring Session to create a session backed Redis/Jdbc/etc that will be used later to generate the SESSION cookie.
I got it using the getSession().getId() method from request. My example is using the Webflux implementation with Kotlin but apparently works similar in HttpServletRequest implementation see https://javaee.github.io/javaee-spec/javadocs/javax/servlet/http/HttpServletRequest.html#getSession--
class AuthenticationSuccessHandler : ServerAuthenticationSuccessHandler {
private val location = URI.create("https://redirect.page")
private val redirectStrategy: ServerRedirectStrategy = DefaultServerRedirectStrategy()
override fun onAuthenticationSuccess(webFilterExchange: WebFilterExchange?, authentication: Authentication?): Mono<Void> {
val exchange = webFilterExchange!!.exchange
return exchange.session.flatMap {
it.id // 87b5639c-7404-48a1-b9da-3ca47691a962
this.redirectStrategy.sendRedirect(exchange, location)
}
}
}

Spring Boot CORS headers

I am new to CORS headers and implementing with Spring boot. I am enabling CORS header on POST service which accept request body.
First time preflight request is made which runs fine and return 200 but when actual post request is invoked, it always return 403 forbidden with response body "Invalid CORS request". I have read almost all spring docs and all google/stackoverflow discussions but could not find out what am I missing..huh..
In Below snippet I have tested by adding crossOrigin at top of class and top of method but no luck.
#CrossOrigin(origins = "https://domain/", allowCredentials = "false")
#RequestMapping(value = ApplicationConstants.URI_PATH)
class MainController {
#RequestMapping(value = '/postMethod', method = RequestMethod.POST)
Map<String, Object> postMethod(HttpServletResponse servletResponse,
#RequestBody(required = false) AccessToken requestedConsumerInfo) {...}
For POST method - Preflight request is invoked and result is 200 but main POST call returns 403.
Call with OPTIONS: Status code 200
Response headers (616 B)
Access-Control-Allow-Credentials true
Access-Control-Allow-Headers content-type
Access-Control-Allow-Methods POST
Access-Control-Allow-Origin https://domain
Allow GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
Cache-Control max-age=0, private, no-cache, …roxy-revalidate, no-transform
Connection close
Content-Length 0
Date Wed, 20 Dec 2017 17:57:14 GMT
Pragma no-cache
Server nginx/1.9.1
Strict-Transport-Security max-age=63072000; includeSubdomains;
Vary Origin,User-Agent
X-Frame-Options SAMEORIGIN
X-XSS-Protection 1; mode=block
Request headers (512 B)
Accept text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate, br
Accept-Language en-US,en;q=0.5
Access-Control-Request-Headers content-type
Access-Control-Request-Method POST
Connection keep-alive
Host domain
Origin https://domain
User-Agent Mozilla/5.0 (Windows NT 6.1; W…) Gecko/20100101 Firefox/57.0
Call with POST: Status code 403
Response headers (364 B)
Cache-Control max-age=0, private, no-cache, …roxy-revalidate, no-transform
Connection close
Content-Length 20
Date Wed, 20 Dec 2017 17:57:14 GMT
Pragma no-cache
Server nginx/1.9.1
Strict-Transport-Security max-age=63072000; includeSubdomains;
Vary User-Agent
X-Frame-Options SAMEORIGIN
X-XSS-Protection 1; mode=block
Request headers (2.507 KB)
Accept application/json, text/plain, */*
Accept-Encoding gzip, deflate, br
Accept-Language en-US,en;q=0.5
Connection keep-alive
Content-Length 102
Content-Type application/json
Cookie rxVisitor=1513720811976ARCUHEC…B4SL3K63V8|6952d9a33183e7bc|1
Host domain
Origin https://domain
Referer https://domain/home/account/register
User-Agent Mozilla/5.0 (Windows NT 6.1; W…) Gecko/20100101 Firefox/57.0
Since this was not working, I have also tested by adding global configurations alone and also along with above snippet but no luck.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping(ApplicationConstants.MEMBER_URL_PATH)
.allowedOrigins("https://domain/")
.allowedMethods(HttpMethod.GET.toString(),
HttpMethod.POST.toString(), HttpMethod.PUT.toString());
}
}
}
On the preflight OPTIONS request, the server should respond with all the following (looks like you're doing this already):
Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials (if cookies are passed)
On the actual POST request, you'll need to return at least Access-Control-Allow-Origin and Access-Control-Allow-Credentials. You're not currently returning them for the POST response.
I had the same issue, then used the annotation #CrossOrigin and it works fine, but just for GET, when I tried to make a POST I still got Cross Origin error, then this fixed for me:
Create an interceptor and added the Access Controll headers to the response.
(You might not need all of them)
public class AuthInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse httpResponse, Object handler, ModelAndView modelAndView)
throws Exception {
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
httpResponse.setHeader("Access-Control-Allow-Headers", "*");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Max-Age", "4800");
}
}
Then add the interceptor:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("++++++ WebConfig addInterceptors() ");
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
}
}
I hope this save you some time, it took me a while to get this working .

Spring REST api not working while hosted remotely

I have written a spring rest service. When i was running it on localhost it was running well
$.ajax({
url:"http://localhost:8080/api/v1.0/basicSignup",
type:"POST",
contentType:"application/json",
but when i tried & hosted on some remote server
$.ajax({
url:"http://X.X.X.X/api/v1.0/basicSignup",
type:"POST",
contentType:"application/json",
it throwing error
In chrome
XMLHttpRequest cannot load http://X.X.X.X/api/v1.0/basicSignup.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:8084' is therefore not allowed access.
In console i see that in Method tab it show options
In mozilla also it shows OPTIONS.
Response Headersview source
Allow GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Content-Length 0
Date Sat, 15 Aug 2015 15:15:07 GMT
Server Apache-Coyote/1.1
Request Headersview source
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-us,en;q=0.5
Access-Control-Request-He... content-type
Access-Control-Request-Me... POST
Cache-Control no-cache
Connection keep-alive
DNT 1
Host X.X.X.X
Origin null
Pragma no-cache
User-Agent Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0.1
As #JB's comment says, you might need to implement CORS. Basically, the single origin policy wouldn't allow JavaScript from one domain, say http://localhost:8084 to make AJAX calls to another domain, say http://X.X.X.X by default. There are some mechanisms for handling this, but people seem to prefer CORS, because generally it looks most convenient and powerful.
Spring Lemon exhibits how to use CORS in details. Below is an example from another project of mine:
In the client code, I would set up these ajax options initially (or along with each call)
$.ajaxSetup({ // Initialize options for AJAX calls
crossDomain: true,
xhrFields: {
withCredentials: true
}
...
});
At the server side, have a filter which will set the CORS headers. The latest version of Spring (which would come along with Spring Boot 1.3) has an easier way to configure CORS at the server side. But, in one project using Spring Boot 1.2, I would have it like this:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
#Value("${applicationUrl:http://localhost:8084}")
String applicationUrl;
public void doFilter(ServletRequest req,
ServletResponse res,
FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin",
applicationUrl); // "*" does not work when withCredentials = true;
response.setHeader("Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age",
"3600");
response.setHeader("Access-Control-Allow-Headers",
"x-requested-with,origin,content-type,accept,X-XSRF-TOKEN");
response.setHeader("Access-Control-Allow-Credentials", "true"); // needed when withCredentials = true;
HttpServletRequest request = (HttpServletRequest) req;
if (!request.getMethod().equals("OPTIONS"))
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
Set applicationUrl in application.properties, like this
applicationUrl: http://X.X.X.X

Resources