Spring Security, REST basic authentication issue - java

I got an issue related to the HTTP response header "Access-Control-Allow-Origin" when using basic authetication with Spring. When I authenticate manually, like the code bellow (I'm using REST):
#RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public void login(#RequestBody String body, HttpServletResponse response)
throws IOException {
try {
User user = gson.fromJson(body, User.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
usuario.getUsername(), usuario.getPassword());
authenticationManager.authenticate(token);
} catch (BadCredentialsException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
everything works fine, I receive the following HTTP response:
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Content-Type: text/html;charset=utf-8
Content-Length: 951
Date: Fri, 17 May 2013 19:14:36 GMT
as you can see, "Access-Control-Allow-Origin" is present on the response.
Everything is fine here. I can catch a 401 error in my ajax call.
But when the authentication is performed automatically, like the code bellow:
#RequestMapping(value = "/name", method = RequestMethod.POST, consumes = "application/json")
#PreAuthorize("hasRole('ROLE_CUSTOMER')")
public #ResponseBody String getName(HttpServletResponse response) throws IOException {
String json = null;
try {
User userSession = (User) SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
Customer customer = customerDao.getNameByUsername(userSession.getUsername());
json = gson.toJson(customer);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
return json;
}
the HTTP response is:
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
WWW-Authenticate: Basic realm="Spring Security Application"
Content-Type: text/html;charset=utf-8
Content-Length: 981
Date: Fri, 17 May 2013 19:41:08 GMT
There is no "Access-Control-Allow-Origin" in the response
Google Chrome console show the following error:
Origin null is not allowed by Access-Control-Allow-Origin
My ajax call does not return a 401 Unauthorized error, even though the HTTP response return it (response above), I receive an unknow error.
I figured out that for all browsers, I need a "Access-Control-Allow-Origin" in the HTTP response, otherwise they will generate some kind of silent error and my ajax call will fail (can't catch the 401 error). Actually, javascript will fail silently. XMLHttpRequest
does not accept an HTTP response without "Access-Control-Allow-Origin".
How can I make Spring inject this "Access-Control-Allow-Origin" in HTTP responses for basic authentication?
this is my Spring Security xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<security:http create-session="stateless" entry-point-ref="authenticationEntryPoint">
<security:intercept-url pattern="/customer/**" />
<security:http-basic />
<security:custom-filter ref="basicAuthenticationFilter"
after="BASIC_AUTH_FILTER" />
</security:http>
<bean id="basicAuthenticationFilter"
class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationEntryPoint" ref="authenticationEntryPoint" />
</bean>
<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
<property name="realmName" value="teste.com" />
</bean>
<!-- It is responsible for validating the user's credentials -->
<security:authentication-manager alias="authenticationManager">
<!-- It is responsible for providing credential validation to the AuthenticationManager -->
<security:authentication-provider>
<security:password-encoder ref="passwordEncoder" />
<security:jdbc-user-service
data-source-ref="mySQLdataSource"
users-by-username-query="select username, password, enabled from usuario where username = ?"
authorities-by-username-query="select username, papel from autoridade where username = ?" />
</security:authentication-provider>
</security:authentication-manager>
<bean class="org.springframework.security.crypto.password.StandardPasswordEncoder"
id="passwordEncoder" />
</beans>

Just found my own way:
First of all, I don't really remember why I put this line here, but it was messing up my code:
<security:http-basic />
Second, this answer show me the path: Handle unauthorized error message for Basic Authentication in Spring Security. I had to create a custom authentication entry point in order to send the Access-Control-Allow-Origin thing.
So this is my code now:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<security:http create-session="stateless"
entry-point-ref="authenticationEntryPoint">
<security:intercept-url pattern="/api/admin/**" />
<security:intercept-url pattern="/medico/**" />
<!-- <security:http-basic /> -->
<security:custom-filter ref="basicAuthenticationFilter"
after="BASIC_AUTH_FILTER" />
</security:http>
<bean id="basicAuthenticationFilter"
class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationEntryPoint" ref="authenticationEntryPoint" />
</bean>
<!--
<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
<property name="realmName" value="test.com" />
</bean> -->
<bean id="authenticationEntryPoint"
class="com.test.util.PlainTextBasicAuthenticationEntryPoint">
<property name="realmName" value="test.com" />
</bean>
<!-- It is responsible for validating the user's credentials -->
<security:authentication-manager alias="authenticationManager">
<!-- It is responsible for providing credential validation to the AuthenticationManager -->
<security:authentication-provider>
<security:password-encoder ref="passwordEncoder" />
<security:jdbc-user-service
data-source-ref="mySQLdataSource"
users-by-username-query="select username, password, enabled from usuario where username = ?"
authorities-by-username-query="select username, papel from autoridade where username = ?" />
</security:authentication-provider>
</security:authentication-manager>
<bean
class="org.springframework.security.crypto.password.StandardPasswordEncoder"
id="passwordEncoder" />
</beans>
package com.test.util;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
public class PlainTextBasicAuthenticationEntryPoint extends
BasicAuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.addHeader("Access-Control-Allow-Origin", "null");
response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\"");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP Status " + HttpServletResponse.SC_UNAUTHORIZED + " - " + authException.getMessage());
}
}
My http response now:
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: null
WWW-Authenticate: Basic realm="test.com"
Content-Length: 35
Date: Mon, 20 May 2013 20:05:03 GMT
HTTP Status 401 - Bad credentials
before the alteration, I got this error message:
OPTIONS http://localhost:8080/test/customer/name 200 (OK) jquery-1.8.2.min.js:2
XMLHttpRequest cannot load http://localhost:8080/test/customer/name. Origin null is not allowed by Access-Control-Allow-Origin.
and now as expected I get this one:
OPTIONS http://localhost:8080/test/customer/name 200 (OK) jquery-1.8.2.min.js:2
POST http://localhost:8080/test/customer/name 401 (Unauthorized)

Related

HTTP basic authentication and Spring security: custom failure response

I'm trying to implement a Rest web service with Jersey, Spring and Spring security (3.2.5).
I want the server to require the basic auth header for the request to the rest web service. If authentication succeeds, then jersey should handle the request. If it fails, I want to return an empty response with the status 403.
Currently my configuration is the following:
<bean id="myAuthenticationEntryPoint"
class="de.tuberlin.snet.baroudeur.Authentication.EntryPoint" />
<!-- HTTP basic authentication in Spring Security -->
<security:http create-session="stateless" auto-config="false"
disable-url-rewriting="true" entry-point-ref="myAuthenticationEntryPoint">
<security:intercept-url pattern="**" access="ROLE_USER" />
<security:http-basic />
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="username" password="password"
authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
and my EntryPoint:
public class EntryPoint extends BasicAuthenticationEntryPoint {
public void commence(ServletRequest request, ServletResponse response,
AuthenticationException authException) throws IOException,
ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
I tried a lot of things, like custom AuthenticationFailureHandler, or custom EntryPoint but nothing has worked so far.
Thanks
Edit:
My current solution only returns a 401 response with the basic tomcat templates and the browser asks for the user credentials.
When using the debugging mode, it seems that the commence() method of my custom entry point is never called, why is that?

Return JSON on unauthorized REST service request using Spring Security 'hasPermission()'

I am implementing method level restriction/authorization (on REST services) using Spring security.
I have used the spring expression language and implemented custom Expression Evaluator.
It is working fine for me. However, if an unauthorized user tries to access the service, it is responding with a login page. Since my application is REST based only, I want to return only JSON data for all the requests.
How do I make it return JSON instead of the login page?(eg: {status : Denied})
Here is the code Snippet:
CustomEvaluator
public boolean hasPermission(Authentication authentication, Object userId, Object permissionId) {
List<String> permList = new MyDAO().getPermissionsForUser((String) userId);
if(permList.contains(((String) permissionId))){
return true;
}else{
return false;
}
}
Service
#PreAuthorize("hasPermission(#userId, '3')")
public String testAuthorization(Object obj, String userId){
System.out.println("Has access to the service method....");
return "success";
}
Controller
public #ResponseBody String testAuthorization(Object o,#RequestParam("userId") String userId){
System.out.println("User ID = "+userId);
String abc = service.testAuthorization(o,userId);
return "{\"status\":\"success\"}";
}
spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<security:global-method-security secured-annotations="enabled" pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<!-- This is where we configure Spring-Security -->
<security:http auto-config="true" use-expressions="true" access-denied-page="/auth/auth/denied" >
<security:intercept-url pattern="/auth/auth/login" access="permitAll"/>
<security:intercept-url pattern="/auth/main/admin" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/auth/main/common" access="hasRole('ROLE_USER')"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref="customUserDetailsService">
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>
<!-- A custom service where Spring will retrieve users and their corresponding access levels -->
<bean id="customUserDetailsService" class="com.cjl.security.service.CustomUserDetailsService"/>
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator" ref="permissionEvaluator"/>
</bean>
<bean id="permissionEvaluator" class="com.cjl.security.evaluators.MethodPermissionEvaluator"/>
</beans>
What is happening is an AccessDeniedException is being thrown, so you want to configure your system to intercept that exception and instead return JSON.
You can set up an #ExceptionHandler method within your controller which catches the AccessDeniedException. However, you probably want to do the same thing in all your controllers, so if you are using Spring 3.2, you can use #ControllerAdvice annotation on a separate 'advice' class and then include the #ExceptionHandler method in there.
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler(AccessDeniedException.class)
#ResponseBody
public String exception(AccessDeniedException e) {
return "{\"status\":\"access denied\"}";
}
}

LDAP with Spring

I want to use LDAP in my application in order to authenticate
I used in my previous config the database to authenticate
this is my previous config :
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/test/**" access="hasRole('ADMIN')" />
<intercept-url pattern="/test1/**" access="hasRole('USER')" />
<form-login login-page="/index.htm" authentication-success-handler-ref="authenticationSuccessRedirecthandler"
default-target-url = "/test/MainHealthCertificat.htm"
authentication-failure-url="/index.htm?error=1"/>
<logout logout-success-url="/index.htm" />
</http>
<beans:bean class="com..CustomAuthenticationHandler" id="authenticationSuccessRedirecthandler"></beans:bean>
<authentication-manager>
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username, password, enabled from users where username=?"
authorities-by-username-query="select u.username, ur.authority from users u, user_roles ur where u.user_id = ur.user_id and u.username =? "
/>
</authentication-provider>
</authentication-manager>
</beans:beans>
this is my java class :
public class CustomAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
String adminTargetUrl = "/test/mypage.htm";
Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (roles.contains("ADMIN")) {
getRedirectStrategy().sendRedirect(request, response, adminTargetUrl);
}else {
super.onAuthenticationSuccess(request, response, authentication);
return;
}
}
}
No I want to use ldap to authenticate
I modified security-app-context.xml
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/test/**" access="hasRole('ADMIN')" />
<intercept-url pattern="/test1/**" access="hasRole('USER')" />
<form-login login-page="/index.htm" authentication-success-handler-ref="authenticationSuccessRedirecthandler"
default-target-url = "/test/MainHealthCertificat.htm"
authentication-failure-url="/index.htm?error=1"/>
<logout logout-success-url="/index.htm" />
</http>
<beans:bean class="com..CustomAuthenticationHandler" id="authenticationSuccessRedirecthandler"></beans:bean>
<security:authentication-manager>
<security:ldap-authentication-provider
user-search-filter="(uid={0})"
user-search-base="ou=users"
group-search-filter="(uniqueMember={0})"
group-search-base="ou=groups"
group-role-attribute="cn"
role-prefix="ROLE_">
</security:ldap-authentication-provider>
</security:authentication-manager>
<security:ldap-server url="ldap://192.168.0.88:389" manager-dn="uid=admin,ou=system" manager-password="secret" />
</beans:beans>
but when I test I have this error :
Caused by: org.springframework.ldap.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db0
honestly I'm lost in settings ldap parameter : ou dc, cn,
I need help to configure the parameter of ldap in security-app-context.xml
this is a correct parameter of ldap which should be used in security-app-context.xml
Base Provider URL
ldap://192.168.0.88:389
Base DN
DC=MINISTER,DC=FR
Principal
CN=LDAP Requester,OU=Users,OU=Technical Accounts,OU=P9 Accounts,DC=MINISTER,DC=FR
Credentials
minister$9999
Users
Authentication Search Filter
(&(objectClass=person)(mail=#email_address#))
Import Search Filter
(objectClass=person)
User Mapping
Screen Name
sAMAccountName
Password
userPassword
Email Address
mail
Full Name
cn
First Name
givenName
Middle Name
middleName
Last Name
sn
Group
memberOf
Groups
Import Search Filter
(&(objectClass=group)(|(cn=MinisterUsers)(cn=MinisterAdministrateurs)(cn=Minister_*)))
Group Mapping
Group Name
cn
Description
sAMAccountName
User
member
Export
Users DN DC=MINISTER,DC=FR
Groups DN DC=MINISTER,DC=FR
Error code data 52e means invalid credentials supplied.
Please try to remove these attributes manager-dn="uid=admin,ou=system" manager-password="secret"
in the below.
<security:ldap-server url="ldap://192.168.0.88:389" manager-dn="uid=admin,ou=system" manager-password="secret" />
And give a try again. These are LDAP administration credentials and are not needed for the user authentication. User authentication are done using the values you supplied during login and spring by default try to Bind to LDAP using the provided details.
Also try to enable debug by including < debug /> tag in your security-app-context.xml and also add log4j.properties. This would give plenty of useful information for debuging.

Overriding container response for spring-security BadCredentialsException

I have a trivial REST app that needs a few resources secured with HTTP Basic auth. I don't ever want to see container generated HTML in response to anything that a client of this app might do. So I have setup my SpringSecurity like this:
<http pattern="/api/**" auto-config="true" create-session="stateless" entry-point-ref="entryPoint">
<intercept-url pattern="/**" method="PUT" access="ROLE_ADMIN" />
<intercept-url pattern="/**" method="POST" access="ROLE_ADMIN" />
<intercept-url pattern="/**" method="DELETE" access="ROLE_ADMIN" />
<http-basic />
</http>
<http security="none" />
<authentication-manager>
<authentication-provider>
<user-service id="userDetailsService"
properties="classpath:META-INF/spring/users.properties"/>
</authentication-provider>
</authentication-manager>
My EntryPoint class looks like this:
public class BasicJsonEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException
) throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\"");
response.setStatus(SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("{\"status\":" + SC_UNAUTHORIZED + ", \"message\":\"" + authException.getMessage() + "\"}");
}
}
This works when I hit a protected resource/method combination and I get something like the following;
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
WWW-Authenticate: Basic realm="Admin"
Content-Length: 84
Date: Sun, 29 Sep 2013 11:50:48 GMT
{"status":401, "message":"Full authentication is required to access this resource"}
.. which is exactly what I want. However, if I supply invalid credentials in the Authorize: HTTP header, I get Tomcat's HTML response...
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
WWW-Authenticate: Basic realm="Admin"
Content-Type: text/html;charset=utf-8
Content-Length: 1019
Date: Sun, 29 Sep 2013 11:51:14 GMT
<html><head><title>Apache Tomcat/7.0.33 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 401 - Invalid basic authentication token</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>Invalid basic authentication token</u></p><p><b>description</b> <u>This request requires HTTP authentication.</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/7.0.33</h3></body></html>
What else do I have to override/implement in order to get the same JSON response here (ideally using the same class I already created)?
Many thanks,
The problem is in the BasicAuthenticationFilter, which is using it's own instance of BasicAuthenticationEntryPoint instead of your implementation. So you can tweak it a bit:
<http auto-config="true" create-session="stateless">
<intercept-url .../>
<http-basic />
<custom-filter ref="basicAuthenticationFilter" before="BASIC_AUTH_FILTER" />
</http>
<beans:bean id="basicAuthenticationFilter"
class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationEntryPoint" ref="entryPoint" />
</beans:bean>
<beans:bean id="entryPoint" class="mypackage.BasicJsonEntryPoint">
<beans:property name="realmName" value="realm"/>
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider>
...
</authentication-provider>
</authentication-manager>

Spring Ajax - #ResponseBody - Returning null response

In my spring webapp, I have an ajax servlet that answer json (using jackson):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<context:component-scan base-package="com.myapp.ajax" />
<util:list id="messageConvertersList">
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</util:list>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters" ref="messageConvertersList" />
</bean>
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang" />
</bean>
</mvc:interceptor>
</mvc:interceptors>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />
<bean id="handlerExceptionResolver" class="com.myapp.ajax.AjaxExceptionHandlerResolver">
<property name="exceptionHandler">
<bean class="com.myapp.ajax.AjaxExceptionHandler" />
</property>
<property name="messageConverters" ref="messageConvertersList" />
</bean>
I have the following ajax service:
#RequestMapping(value = "getLoggedUser")
#ResponseBody
public DtoUser getLoggedUser() {
return authenticationService.getLoggedUser();
}
When the user is logged in it returns something like:
{ userName : "jojo", email : "john.doe#email.com", firstName : "John", lastName : "Doe" }
When the user is not logged in, the expected behavior is to return
null
But it returns an empty response which is not a valid JSON response (and additionally with a bad Content-type header)
Why is this happening ?
Do I have solutions to obtain the expected behaviour ?
When the user is not logged in, the expected behavior is to return null
That's my expected behaviour because in both Java and Javascript/JSON, null is a valid value, which have a different mean than nothing, empty or error/exception.
I would expect that Spring answer the null response instead of handling it specifically.
In that case, the expected convertion for null (Java) would be null (JSON)
My expected conversion table:
Java Exception => HTTP Error Code
null => null
empty map / object => {}
void => no response
Why is this happening ?
For Spring, a controller returning null mean "No response" and not "a response which value is null". This rule applies to all controller methods including ones with #ResponseBody annotation.
This allow to write manually to the response without having something appended to the response later:
if (mySpecialCase) {
Writer writer = response.getWriter();
writer.write("my custom response");
return null;
} else {
return myObject;
}
So when returning null Spring write nothing to the response, nor Content-type header nor body.
Do I have solutions to obtain the expected behaviour ?
I made the following dirty hack: add a filter on my ajax path that write null to the response when no response have been commited.
public class AjaxEmptyResponseFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
if (!response.isCommitted()) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
Writer writer = response.getWriter();
writer.write("null");
writer.close();
response.flushBuffer();
}
}
}
This solution handle methods answering null and method answering nothing (void) the same way.
Do you have a session filter?
I think, you can bind a global ajax event for errors and make your respective validation there.
here is a example on a similar case: How to handle expired session using spring-security and jQuery?

Categories

Resources