How to properly configure ldap authentication in spring boot? Every time I log in, it redirects to /login after calling /auth - spring-boot

NOTE
Suggested answer didn't work.
I have the following configuration in WebSecurityConfigurerAdapter:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("somevalue")
.userSearchFilter("somevalue")
.contextSource().url("somevalue").port("somevalue")
.managerDn("somevalue").managerPassword("somevalue");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/*.css", "/*.html", "/*.js", "/*.jpg").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.loginProcessingUrl("/auth")
.permitAll();
}
And this is my configuration in WebMvcConfigurer:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/*.css", "/*.html", "/*.js", "/*.jpg")
.addResourceLocations("classpath:/static/");
registry.addResourceHandler("/", "/login", "/myapp")
.addResourceLocations("classpath:/static/index.html")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
#Override
protected Resource getResource(String resourcePath, Resource location) {
if (resourcePath.startsWith("/api") || resourcePath.startsWith("/api".substring(1))) {
return null;
}
return location.exists() && location.isReadable() ? location : null;
}
});
}
WHen I click on login button I send the following request in angular:
this.http.post(`localhost:8080/auth`, {username: 'user', password: 'password'})...
But I only got redirected to /login. Here's the network console:
auth 302
login 200
Here's the request (/auth) headers and body:
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 47
Content-Type: application/json
Cookie: JSESSIONID=*****somecookie*****
Host: localhost:8080
Origin: http://localhost:8080
Referer: http://localhost:8080/login
Payload:
{username: "user", password: "password"}
General:
Request URL: http://localhost:8080/auth
Request Method: POST
Status Code: 302
Remote Address: [::1]:8080
Referrer Policy: no-referrer-when-downgrade
Response headers:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0
Date: Fri, 01 Mar 2019 10:49:09 GMT
Expires: 0
Location: http://localhost:8080/login
Pragma: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
And here's when it redirects to /login:
General:
Request URL: http://localhost:8080/login
Request Method: GET
Status Code: 200
Remote Address: [::1]:8080
Referrer Policy: no-referrer-when-downgrade
Request:
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Cookie: JSESSIONID=*****somecookie*****
Host: localhost:8080
Referer: http://localhost:8080/login
Basically, the response body is the content of my index.html.
And in the console it says:
error: {error: SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse...
headers: e {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: "Http failure during parsing for http://localhost:8080/login"
name: "HttpErrorResponse"
ok: false
status: 200
statusText: "OK"
url: "http://localhost:8080/login"
Also, is my ldap config correct?
How to properly configure for ldap authentication?
UPDATE
As per recommendation, I changed my code to send the body as http params. The request is now this:
Request Headers:
Content-Type: application/x-www.form-urlencoded;charset=UTF-8
Form Data
username=user&password=password
It still the same issue

The answer to my question is the missing csrf. If I don't disable csrf, spring security seems to look for this, otherwise, my request gets rerouted back to login page. I fixed by adding the following:
http.csrf().disable()
For the Request method 'POST' not supported during /login, in my AuthenticationSuccessHandler.onAuthenticationSuccess(), I put
request.getRequestDispatcher(targetURI).forward(request, response);
Instead, I just set the reponse with a string value:
response.getOutputStream().println(mapper.writeValueAsString(data));

Related

XtextServices Servlet returns CORS-Error with HTTP-Code 404 but not other REST-Controllers

I currently try to set up a Xtext web editor with "Context Mapper" as DSL.
The backend is a basic Spring Boot backend with the Context Mapper DSL and Context Mapper LSP (Language Server).
The frontend is a VueJS frontend made with the help of this guide. It utilizes everything Eclipse Xtext generates and ports it to a VueJS website.
My only issue now is that the "/xtext-service/*" API return CORS errors with HTTP code 404 while the console of the backend prints absolutely nothing.
The Servlet looks like every other XtextServlet:
#WebServlet(name = "XtextServices", urlPatterns = "/xtext-service/*")
#SuppressWarnings("all")
public class ContextMappingDSLServlet extends XtextServlet {
private DisposableRegistry disposableRegistry;
#Override
public void init() {
try {
super.init();
final Injector injector = new ContextMappingDSLWebSetup().createInjectorAndDoEMFRegistration();
this.disposableRegistry = injector.<DisposableRegistry>getInstance(DisposableRegistry.class);
} catch (Throwable _e) {
throw Exceptions.sneakyThrow(_e);
}
}
#Override
public void destroy() {
if ((this.disposableRegistry != null)) {
this.disposableRegistry.dispose();
this.disposableRegistry = null;
}
super.destroy();
}
}
The requests are also the standard requests the Xtext generated web editor invokes and the occurrences request goes through it seems:
Response:
HTTP/1.1 404
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:8081
Access-Control-Allow-Credentials: true
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 21 Oct 2022 16:03:19 GMT
Keep-Alive: timeout=60
Connection: keep-alive
Request:
GET /xtext-service/occurrences?resource=1e26589b.cml&caretOffset=1 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,de;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Cookie: JSESSIONID=CDE6CC29EE123451F3DF3FF174337EE3
Host: localhost:8080
Origin: http://localhost:8081
Pragma: no-cache
Referer: http://localhost:8081/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
sec-ch-ua: "Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
The requests to http://localhost:8080/xtext-service/occurrences?resource=1e26589b.cml&caretOffset=1 returns 404 while update throws
Access to XMLHttpRequest at 'http://localhost:8080/xtext-service/update?resource=1e26589b.cml' from origin 'http://localhost:8081' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CORS however is enabled:
#Configuration
#EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedMethods(CorsConfiguration.ALL)
.allowedHeaders(CorsConfiguration.ALL)
.allowedOriginPatterns(CorsConfiguration.ALL)
.allowCredentials(true);
}
}
The non-servlet APIs do work without any issues, but the Xtext-service servlet doesn't, and I don't know why.
#ServletComponentScan is enabled.

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 .

Dart CORS doesn't work

Hello I want to make an request to my Spring server. Now I'm getting an error because of an restricted CORS option.
So I added an filter because the annotations doensn't work:
#Component
public class CORSFilter implements Filter {
public CORSFilter() {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}}
Now my problem is, that the cors filter won't work on an dart request.
On an normal browser request the header is set but not in the dart http request.
Is there any solution which could fix this problem?
Update 23.09.2016:
Here is the http://pastebin.com/9KNfx7Jd
The problem is that the filter is not affected to this http call.
Only when I access the file via URL in the browser it works.
Here with ajax:
Remote Address:127.0.0.1:8090
Request URL:http://localhost:8090/time/time/login
Request Method:OPTIONS
Status Code:401 Unauthorized
Response Headers
view source
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Connection:keep-alive
Content-Length:114
Content-Type:text/html;charset=UTF-8
Date:Fri, 23 Sep 2016 12:57:55 GMT
Expires:0
Pragma:no-cache
Server:WildFly/10
Set-Cookie:JSESSIONID=ZIkzLq-iALC6CDx7r6LhPz_8PiD05Q9ufod6GluZ.ccn6dc2; path=/time
WWW-Authenticate:Basic realm="Realm"
X-Content-Type-Options:nosniff
X-Frame-Options:DENY
X-Powered-By:Undertow/1
X-XSS-Protection:1; mode=block
Request Headers
view source
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:content-type
Access-Control-Request-Method:GET
Connection:keep-alive
Host:localhost:8090
Origin:http://localhost:8080
Referer:http://localhost:8080/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.104 (Dart) Safari/537.36
And here without:
Remote Address:127.0.0.1:8090
Request URL:http://localhost:8090/time/time/login
Request Method:GET
Status Code:200 OK
Response Headers
view source
Access-Control-Allow-Origin:*
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Connection:keep-alive
Content-Length:5
Content-Type:text/html;charset=ISO-8859-1
Date:Fri, 23 Sep 2016 13:10:36 GMT
Expires:0
Pragma:no-cache
Server:WildFly/10
Set-Cookie:JSESSIONID=nQFjGB2m7ovHVT9VUnhtCJSXZvEZV4WWH0YCrgFk.ccn6dc2; path=/time
X-Content-Type-Options:nosniff
X-Frame-Options:DENY
X-Powered-By:Undertow/1
X-XSS-Protection:1; mode=block
Request Headers
view source
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Authorization:Basic c2tvYmxlcjpTMW1vbjUyNzli
Cache-Control:max-age=0
Connection:keep-alive
Cookie:JSESSIONID=oHJ4GvQ8pFNv8HSujI49NRXQxoVSVMM580sSrvJW.ccn6dc2
Host:localhost:8090
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.104 (Dart) Safari/537.36
Edit 26.09.2016:
Okay I changed now my SecurityConfig to this:
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll();
http.authorizeRequests().antMatchers("/**").authenticated();
}
now the filter is beeing called but I get now a new error: Response for preflight has invalid HTTP status code 401
Headers:
Access-Control-Allow-Origin:*
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Connection:keep-alive
Content-Length:114
Content-Type:text/html;charset=UTF-8
Date:Mon, 26 Sep 2016 12:30:39 GMT
It looks like your filter is not applied for OPTIONS requests.
A comment to this blog post indicates that OPTIONS requests need to be enabled explicitly:
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
One "gotcha" that I found when working with CORS with Spring MVC (when using a Filter or HandlerInterceptor) and Spring Security is that you need to explicitly permit all OPTIONS requests to properly handle the pre-flight. The W3C specification for CORS says that pre-flight requests should not send credentials, however I have found that some browsers do send the credentials, and others don't. So if you don't permitAll OPTIONS you get a 403 if the browser is not sending the credentials.
Will pre-flights requests be something that will need to be specifically configured when using Spring Security or will the pre-flight be handled before the filter chain?
See also
Disable Spring Security for OPTIONS Http Method
Enable CORS for OPTIONS request using Spring Framework
How to handle HTTP OPTIONS with Spring MVC?
How to handle HTTP OPTIONS requests in Spring Boot?
Spring and HTTP Options request
Ok I worked around with disabling the web security for chromium.
Thanks to all of you for helping me :)

WebSocket with Spring backend loses connection after a while, onclose is not called

In our spring application most of the controllers are protected with oauth security. Websockets are behind basic. Before accessing websocket logged user asks for username and hashed password for websocket connection. Both are going to be generated, but for now for testing purposes it always returns the same creditentials.
URL for info looks as follows:
https://user:debaee4affbeaba909a184066981d55a#localhost:8000/project-name/chat/info
WebSocket is opened properly. We can send few messages and they go trough broker and are displayed to the users. Here's request info from chrome tools:
Remote Address:127.0.0.1:8000
Request URL:https://benny:debaee4affbeaba909a184066981d55a#localhost:8000/project-name/chat/033/7szz8k_f/xhr_send
Request Method:POST
Status Code:204 No Content
Response Headers:
HTTP/1.1 204 No Content
server: Apache-Coyote/1.1
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: 0
strict-transport-security: max-age=31536000 ; includeSubDomains
x-frame-options: DENY
access-control-allow-origin: https://localhost:8000
access-control-allow-credentials: true
vary: Origin
content-type: text/plain;charset=UTF-8
date: Mon, 15 Jun 2015 08:22:43 GMT
Connection: keep-alive
Request Headers:
POST /project-name/chat/033/7szz8k_f/xhr_send HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Content-Length: 143
Origin: https://localhost:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Referer: https://localhost:8000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8,pl;q=0.6
Cookie: JSESSIONID=FF967D3DD1247C1D572C15CF8A3D5E8E; i18next=en; language=pl; tmhDynamicLocale.locale=%22pl-pl%22
["SEND\npriority:9\ndestination:/random/chat/1/FUNNY\ncontent-length:49\n\n{\"message\":\"sfsdf\",\"display\":\"The great wizard.\"}\u0000"]
But after a minute or so when sending another request we get 404 response. It doesn't matter if any SEND requests were issued before. We can write 50+ messages in that time span and then we get 404.
Sample 404 request data follows:
Remote Address:127.0.0.1:8000
Request URL:https://hill:debaee4affbeaba909a184066981d55a#localhost:8000/project-name/chat/033/7szz8k_f/xhr_send
Request Method:POST
Status Code:404 Not Found
Response Headers:
HTTP/1.1 404 Not Found
server: Apache-Coyote/1.1
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: 0
strict-transport-security: max-age=31536000 ; includeSubDomains
x-frame-options: DENY
content-length: 0
date: Mon, 15 Jun 2015 08:24:17 GMT
Connection: keep-alive
Request Headers:
POST /project-name/chat/033/7szz8k_f/xhr_send HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Content-Length: 143
Origin: https://localhost:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Referer: https://localhost:8000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8,pl;q=0.6
Cookie: JSESSIONID=FF967D3DD1247C1D572C15CF8A3D5E8E; i18next=en; language=pl; tmhDynamicLocale.locale=%22pl-pl%22
Request Payload:
["SEND\npriority:9\ndestination:/random/chat/1/FUNNY\ncontent-length:49\n\n{\"message\":\"yhgfh\",\"username\":\"The great wizard.\"}\u0000"]
When setting up stomp we setup function to react onclose:
socket.client = new SockJS(targetUrl);
socket.stomp = Stomp.over(socket.client);
socket.stomp.connect({}, startListener);
socket.stomp.onclose = reconnect;
With reconnect function looking as this(it's in AngularJS):
var reconnect = function() {
$log.debug('Reconnect called');
$timeout(function() {
initialize();
}, this.RECONNECT_TIMEOUT);
};
But the function is never called.
Controller for Chat is pretty simple:
#Controller
public class StageChatController {
#Inject
private SimpMessagingTemplate template;
#Inject
private ChatMessageRepository chatMessageRepository;
#MessageMapping("/chat/{channel}/{type}")
public void sendMessage(#DestinationVariable Long channel, #DestinationVariable ChatType type, ChatMessageDto message) {
ChatMessage chatMessage = new ChatMessage();
chatMessage.setDatestamp(LocalDateTime.now());
chatMessage.setMessage(message.getMessage());
chatMessage.setChannelId(channel);
chatMessage.setChatType(type);
chatMessage.setDisplayName(message.getDisplay());
chatMessage = this.chatMessageRepository.save(chatMessage);
this.template.convertAndSend("/channel/" + project + "/" + type, chatMessage);
}
Security for chat overrides oauth security for chat urls:
#Configuration
#EnableWebSecurity
#Order(2)
static class BasicAccessConfig extends WebSecurityConfigurerAdapter {
#Inject
private OAuth2ClientContextFilter oauth2ClientContextFilter;
#Value("${project.name.chat.token}")
private String chat_token;
#Override
protected void configure(HttpSecurity http) throws Exception {
//#formatter:off
http
.requestMatcher(new AntPathRequestMatcher("/chat/**/*"))
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic()
.and()
.anonymous().disable()
.csrf().disable()
.addFilterBefore(this.oauth2ClientContextFilter, SecurityContextPersistenceFilter.class);
;
//#formatter:on
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("hill").password(this.chat_token).authorities("read_chat");
}
}

Spring/Eureka/Feign - FeignClient setting Content-Type header to application/x-www-form-urlencoded

When I use a FeignClient it is setting the Content-Type to application/x-www-form-urlencoded instead of application/json;charset=UTF-8.
If I use a RestTemplate to send the same message the message header Content-Type is correctly set to application/json;charset=UTF-8.
Both the FeignClient and RestTemplate are using Eureka for service discovery, and I discovered this problem by debugging the HTTP message received by the server.
The controller on the server side looks like this:
#RestController
#RequestMapping("/site/alarm")
public class SiteAlarmController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<RaiseAlarmResponseDto> raiseAlarm(#RequestBody RaiseSiteAlarmRequestDto requestDto) {
...
}
My FeignClient interface in the service that calls the alarm looks like this:
#FeignClient("alarm-service")
public interface AlarmFeignService {
#RequestMapping(method = RequestMethod.POST, value = "/site/alarm")
RaiseAlarmResponseDto raiseAlarm(#RequestBody RaiseSiteAlarmRequestDto requestDto);
}
The HTTP message headers from the FeignClient are:
Accept: */*
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.7.0_60
Host: smit005s-MacBook-Pro.local:9120
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 323
The alarm service doesn't like the Content-Type and throws the following exception:
2015-04-22 12:12:28.580 thread="qtp1774842986-25" class="org.eclipse.jetty.servlet.ServletHandler" level="WARN"
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is feign.FeignException: status 415 reading AlarmFeignService#raiseAlarm(RaiseSiteAlarmRequestDto); content:
{"timestamp":1429701148576,"status":415,"error":"Unsupported Media Type","exception":"org.springframework.web.HttpMediaTypeNotSupportedException","message":"Unsupported Media Type","path":"/site/alarm"}
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978) ~[spring-webmvc-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857) ~[spring-webmvc-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618) ~[tomcat-embed-core-8.0.20.jar:8.0.20]
...
... /* commented rest of stack out */
...
If I change the client side code to use a RestTemplate as follows:
#Service
public class AlarmService {
#Autowired
private RestTemplate restTemplate;
...
public void send(RaiseSiteAlarmRequestDto alarm) {
RaiseAlarmResponseDto result = restTemplate.postForObject("http://alarm-service/site/alarm",
raiseSiteAlarmRequestDto, RaiseAlarmResponseDto.class);
}
}
It works with the RestTemplate, the alarm-service receives the message and processes it successfully. The message headers sent by the RestTemplate are:
Accept: application/json, application/*+json
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.7.0_60
Host: smit005s-MacBook-Pro.local:9120
Connection: keep-alive
Content-Length: 323
The answer was to do as #spencergibb suggests; use the consumes directive in the #RequestMapping annotation on the FeignClient interface. This Spring/Netflix documentaition also has an example.
So for example the #FeignClient interface declaration in the client is now:
#FeignClient("alarm-service")
public interface AlarmFeignService {
#RequestMapping(method = RequestMethod.POST, value = "/site/alarm", consumes = "application/json"))
RaiseAlarmResponseDto raiseAlarm(RaiseSiteAlarmRequestDto requestDto);
}
Note this is only necessary on the client side and the server side controller does not need to have this change.
Would be nice if this was done by default on the #FeignClient and then it would be the consistent with RestTemplate and the server side controller #RequestMapping annotation. Maybe that can be done in a future release of spring-cloud.

Resources