How to return JSON response for unauthorized AJAX calls instead of login page as AJAX response? - session

I have implemented Spring Security in my application. Whenever someone tries to access any url if authentication is required for these urls user will be redirected to login page. Now, if AJAX call is made for any such url I would like to return JSON response instead of login page's HTML as AJAX response. How can I do that ?

You have to create json for this i am doing here with .net
var url="url";
$.ajax({
type: "get",
dataType: "json",
data:url,
async: true,
url: "testCall",//this can be your api or any server side call
success: function (data) {
},
failure: function () {
alert(textStatus);
}
});
//here is server side code for creating json
[WebMethod(EnableSession = true)]
[ScriptMethod(UseHttpGet = true)]
public void testCall(string url)
{
Context.Response.Write()//here your will just hard code the json data
//it will receive by ajax success method.
}

Faced the same thing not long ago, came out with this solution.
You'll have to redefine the authentication entry point to handle the exception and returning a proper JSON response.
First create a class for your response. Needs to be a POJO.
public class MyErrorResponse {
// your stuff here, and getters / setters
}
Then go define the authentication entry point
public class MyBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
private List<HttpMessageConverter<Object>> messageConverters = new ArrayList<>();
private MediaType retrieveRequestMediaType(HttpServletRequest request) {
String accept = request.getHeader("accept");
if(Strings.isNullOrEmpty(accept))
accept = MediaType.APPLICATION_JSON_VALUE;
MediaType requestMediaType = MediaType.valueOf(accept);
return requestMediaType;
}
private HttpMessageConverter<Object> retrieveMessageConverter(List<HttpMessageConverter<Object>> messageConverters, Class<?> clazz, MediaType mediaType) {
for (HttpMessageConverter<Object> httpMessageConverter : messageConverters) {
if(httpMessageConverter.canWrite(clazz, mediaType)) {
return httpMessageConverter;
}
}
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.warn(String.format("Unauthorized access with session id '%s'", request.getSession().getId()));
MyErrorResponse esponse = new MyErrorResponse();
// populate your response object with all the info you need
MediaType mediaType = MediaType.APPLICATION_JSON;
try{
mediaType = retrieveRequestMediaType(request);
} catch(InvalidMediaTypeException imte) {
// log, do nothing
}
// getting the best fitting message converter, according to the "accept" header of the request
HttpMessageConverter<Object> httpMessageConverter = retrieveMessageConverter(messageConverters, MyErrorResponse.class, mediaType);
if(httpMessageConverter == null) {
log.info("Could not find specific handler. Using JSON.");
httpMessageConverter = retrieveMessageConverter(messageConverters, MyErrorResponse.class, MediaType.APPLICATION_JSON);
}
response.setStatus(HttpStatus.UNAUTHORIZED.value());
ServletServerHttpResponse serverHttpResponse = new ServletServerHttpResponse(errorResponse);
httpMessageConverter.write(response, mediaType, serverHttpResponse);
}
}
Once you got your bean set up, time to wire it up in the security context:
<beans:bean class="[fully qualified name of the entry point class]" id="myBasicAuthenticationEntryPoint">
<beans:property name="messageConverters">
<beans:list>
<!-- add message converters here -->
<!-- Spring provide lots of them, google it -->
</beans:list>
</beans:property>
</beans:bean>
<http use-expressions="true">
<http-basic entry-point-ref="myBasicAuthenticationEntryPoint" />
<!-- add other stuff here, if needed -->
</http>
Hope it helps

Related

How to fix multipart/form-data MediaType not being set with Jackson Spring MVC

I'm trying to send a Product and product images from Angular 7 frontend to a SpringMVC backend.
To add support for Multipart files I've added this bean inside my AppConfig.
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
Since I want to receive the Product object separately inside the controller I'm using #RequestPart to fetch both separately like this:
#RequestMapping(value = "save", method = RequestMethod.POST)
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
On the frontend I'm adding the image to FormData like this:
let formData = new FormData();
formData.append('product', new Blob([JSON.stringify(this.product)],{ type: "application/json" }));
// I iterate and append all the images like this
formData.append('image[]', this.images, this.images.name);
this.http.post(this.appService.getApiUrl() + "api/product/save/", product);
The problem is that whenever I submit the form, I get this exception as a response: HTTP Status 415 – Unsupported Media Type.
I tried debugging this issue by setting breakpoints inside CommonsMultipartResolver class and after tracing the request through the code I've found that when the getSupportedMediaTypes() is called it returns only two media types:
application/json
application/*+json
Inside the following method in AbstractHttpMessageConverter:
protected boolean canRead(#Nullable MediaType mediaType) {
if (mediaType == null) {
return true;
} else {
Iterator var2 = this.getSupportedMediaTypes().iterator();
MediaType supportedMediaType;
do {
if (!var2.hasNext()) {
return false;
}
supportedMediaType = (MediaType)var2.next();
} while(!supportedMediaType.includes(mediaType));
return true;
}
}
Finding this I tried adding MediaType.MULTIPART_FORM_DATA like this inside AppConfig:
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
List<MediaType> types = new ArrayList<>();
types.add(MediaType.APPLICATION_JSON);
types.add(MediaType.APPLICATION_JSON_UTF8);
types.add(MediaType.MULTIPART_FORM_DATA);
((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(types);
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(hibernate5Module);
}
}
}
But it still wouldn't work. When the app starts up, I do see the constructor of AbstractJackson2HttpMessageConverter being called with my MediaTypes but they get overwritten by more calls to the same constructor after it.
Is there any way I can get the MediaType to persist? I might be looking in the wrong direction so any insight will be helpful.
The Jackson library is required on the classpath. Spring does not declare this by default. Make sure that at least com.fasterxml.jackson.core:jackson-databind is available in the classpath of the Spring MVC application. Example for Apache Maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Example for the jackson.version value: 2.9.0
1) You need to give input data supported at server end. Since you are sending File, means server is consuming the Multipart Data.
For multipart we need to set consumes = "multipart/form-data"
#RequestMapping(value = "save", method = RequestMethod.POST, consumes = "multipart/form-data")
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
2) Since form is sending multipart data we need to set content-type at front end too in http header in post call.
content-type: multipart/form-data"

Error handling on controller SpringMVC

I am developing an application in jax-rs and spring mvc.
I want to notify my client each time when an default error is occured like
400, 403, 404, 405 and 415.
Controller
#Controller
#RequestMapping("/customer")
public class CustomerController {
#Autowired
CustomerService customerService;
// ........xxxxxx..............xxxxxxx................xxxxxxx.............//
#CrossOrigin
#RequestMapping(value = "/",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String fetchCustomer() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(customerService.fetchAllCustomer());
}
// ........xxxxxx..............xxxxxxx................xxxxxxx.............//
}
Client
$http({
method: "GET",
contentType: "application/json",
url: baseUrl + '/customer'
}).success(function (response) {
console.log(response);
// you can also use
console.log(JSON.stringify(response);
}).error(function (response) {
console.log(response);
});
When i request a service from client i want to send response back with status code and custom message.
Example
When i defind method = post on controller and from client i send request as get service should return message like
error:{
Status Code: 405,
Message: Invalid Method
url: error/405
}
Check this out for reference.
Define a method for handling the specific error scenario and annotate it as #ExceptionHandler. The exception in your scenario (request method not supported) is HttpRequestMethodNotSupportedException.class. You can create more generic handler methods using Throwable, Exception etc.
In order to prevent duplication of error handling across controllers, one convenient way is to define all handlers in single class and use #ControllerAdvice on that. This way, all handlers will be applied to all controllers.
Do not return a String but return a org.springframework.http.ResponseEntity.
You can add status codes to this object
ResponseEntity<String> responseEntity = new ResponseEntity<String>("This is a response", HttpStatus.INTERNAL_SERVER_ERROR);
return responseEntity;
So your method signature will also change as below
#RequestMapping(value = "/", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ResponseEntity<String> fetchCustomer() throws JsonProcessingException {
try {
String str = new ObjectMapper().writeValueAsString(customerService.fetchAllCustomer());
return new ResponseEntity<String>(str, HttpStatus.OK);
}
catch (Exception e) {
return new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
If there is an error, you can either use controller advice or catch the exception and update the ResponseEntity appropriately

Spring AOP with RequestDispatcher causes recursive calls

Spring-servlet.xml:
<aop:config>
<aop:advisor advice-ref="interceptor" pointcut="#annotation(Validator)"/>
</aop:config>
<bean id="interceptor" class="org.aopalliance.intercept.MethodInterceptor" />
MethodInterceptor invoke():
if (!valid){
RequestDispatcher rd = request.getRequestDispatcher(errorView);
rd.forward(request, response);
}
Working flow of control:
My interceptor is called before any Spring controller method that is annotated with the Validator annotation. The intention is to validate the request, if validation fails, forward the request to a different view. This is usually working. If there is an error (!valid), the RequestDispatcher.forward is called. This causes another Spring controller method to be called which ultimately shows the error view. This normally works.
Issue:
For some Spring controllers, my RequestDispatcher's errorView causes the request to be forwarded back to the same method causing an infinite loop (invoke()gets called over and over). I think this is because of how the Spring controller's request mappings (see below) are set up.
Error view: #RequestMapping(value = URL, params="error")
Normal view: #RequestMapping(value = URL, params="proceed")
So when the first request is routed it's got 'proceed' in the request params. Then when there's an error and the RequestDispatcher forwards to the view with the 'error' param in the query string, it should forward to the "Error view" method above, but it doesn't. It always forwards to the 'proceed' method causing an infinite loop on the MethodInterceptor invoke(). This seems to be because the 'proceed' parameter is still in the HttpServletRequest. However this isn't easy to fix because the whole point of the interceptor is that it has no knowledge of the Spring controller itself - it only knows if an error occurred, and that it should forward to the error view if an error occurred.
Workaround:
Using the request mappings below, it fixes the issue. This is probably because the HttpServletRequest parameter is overwritten when using the key=value notation.
Error view: #RequestMapping(value = URL, params="view=error")
Normal view: #RequestMapping(value = URL, params="view=proceed")
Question
How can I "properly" fix the issue without resorting to the workaround shown above? Is there a more standard way to forward to the correct spring controller?
Solution#1:
Having configured as following:
Error view: #RequestMapping(value = URL, params="error")
Normal view: #RequestMapping(value = URL, params="proceed")
You could try for redirect as follows:
MethodInterceptor invoke():
if (!valid){
// RequestDispatcher rd = request.getRequestDispatcher(errorView);
// rd.forward(request, response);
response.sendRedirect(errorView);
}
Drawback: the browser would make a second request, therefore the old method parameters are no longer in the httpservletrequest.
WorkArround: To Avoid drawback, You could use Spring MVC Flash Attribute. You could follow this tutorial to know how Flash Attribute works.
Refs:FlashAttributesExample
Solution#2:
How can I "properly" fix the issue without resorting to the workaround
shown above? Is there a more standard way to forward to the correct
spring controller?
You could incorporate by implementing you own RequestMappingHandlerAdapter.
Solution#3:
Here is the code for the aspect:
public class RequestBodyValidatorAspect {
private Validator validator;
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
private void controllerInvocation() {
}
#Around("controllerInvocation()")
public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
Annotation[][] argAnnotations = method.getParameterAnnotations();
String[] argNames = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if (hasRequestBodyAndValidAnnotations(argAnnotations[i])) {
validateArg(args[i], argNames[i]);
}
}
return joinPoint.proceed(args);
}
private boolean hasRequestBodyAndValidAnnotations(Annotation[] annotations) {
if (annotations.length < 2)
return false;
boolean hasValid = false;
boolean hasRequestBody = false;
for (Annotation annotation : annotations) {
if (Valid.class.isInstance(annotation))
hasValid = true;
else if (RequestBody.class.isInstance(annotation))
hasRequestBody = true;
if (hasValid && hasRequestBody)
return true;
}
return false;
}
#SuppressWarnings({"ThrowableInstanceNeverThrown"})
private void validateArg(Object arg, String argName) {
BindingResult result = getBindingResult(arg, argName);
validator.validate(arg, result);
if (result.hasErrors()) {
throw new HttpMessageConversionException("Validation of controller input parameter failed",
new BindException(result));
}
}
private BindingResult getBindingResult(Object target, String targetName) {
return new BeanPropertyBindingResult(target, targetName);
}
#Required
public void setValidator(Validator validator) {
this.validator = validator;
}
}
One limitation with this work-around is that it can only apply a single validator to all controllers. You can also avoid it.
public class TypeMatchingValidator implements Validator, InitializingBean, ApplicationContextAware {
private ApplicationContext context;
private Collection<Validator> validators;
public void afterPropertiesSet() throws Exception {
findAllValidatorBeans();
}
public boolean supports(Class clazz) {
for (Validator validator : validators) {
if (validator.supports(clazz)) {
return true;
}
}
return false;
}
public void validate(Object target, Errors errors) {
for (Validator validator : validators) {
if (validator.supports(target.getClass())) {
validator.validate(target, errors);
}
}
}
private void findAllValidatorBeans() {
Map<String, Validator> validatorBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, Validator.class, true, false);
validators = validatorBeans.values();
validators.remove(this);
}
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
}
Spring XML configuration file using the validator aspect and the meta-validator together:
<!-- enable Spring AOP support -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- declare the validator aspect and inject the validator into it -->
<bean id="validatorAspect" class="com.something.RequestBodyValidatorAspect">
<property name="validator" ref="validator"/>
</bean>
<!-- inject the validator into the DataBinder framework -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer" p:validator-ref="validator"/>
</property>
</bean>
<!-- declare the meta-validator bean -->
<bean id="validator" class="com.something.TypeMatchingValidator"/>
<!-- declare all Validator beans, these will be discovered by TypeMatchingValidator -->
<bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="com.something.PersonValidator"/>
<bean class="com.something.AccountValidator"/>
Resources Refs:scottfrederick:pring-3-Validation-Aspect
Solution#4:
Yet another solution for form validation using aop , you can check the blog: form-validation-using-aspect-oriented-programming-aop-in-spring-framework

Jersey not triggering ContainerRequestFilter

I'm trying to use a ContainerRequestFilter to enforce some authentication on a Grizzly based Jersey application.
I create my own resource config by extending PackagesResourceConfig:
public class MainResourceConfig extends PackagesResourceConfig {
public MainResourceConfig() {
super("za.co.quinn.ws");
Map<String, Object> properties = getProperties();
properties.put(
"com.sun.jersey.spi.container.ContainerRequestFilter",
"com.sun.jersey.api.container.filter.LoggingFilter;" + MainRequestFilter.class.getName()
);
properties.put(
"com.sun.jersey.spi.container.ContainerResponseFilters",
"com.sun.jersey.api.container.filter.LoggingFilter;" + MainResponseFilter.class.getName()
);
}
}
The request filter is for authentication:
#Inject
Authorization authorization;
#Override
public ContainerRequest filter(ContainerRequest request) {
if (!request.getRequestUri().getPath().endsWith(".wadl"))
authorization.authorize(request);
return request;
}
The response filter is for headers:
#Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
response.getHttpHeaders().add("Access-Control-Allow-Origin", "*");
response.getHttpHeaders().add("Access-Control-Allow-Methods", "GET, HEAD, POST, DELETE, PUT");
response.getHttpHeaders().add("Access-Control-Allow-Headers", "Authorization, Content-Type");
return response;
}
The MainResponseFilter does get triggered but not the ContainerRequestFilter.
"The MainResponseFilter does get triggered but not the ContainerRequestFilter."
Look at your properties, mainly the send of it, and compare them
...container.ContainerRequestFilter"
...container.ContainerResponseFilters"
You're missing an s for the request filters. To avoid misspellings like this, you can use constants
ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS
ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS
You could also do
getContainerRequestFilters().add(new YourRequestFilter());
getContainerResponseFilters().add(new YourResponseFilter());

AJAX returns 404 in Spring MVC

ViewResolver (my jsp is in the right folder as specified on prefix value):
<!-- Resolves views selected for rendering by #Controllers -->
<!-- to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
Servlet mapping:
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>*.fst</url-pattern>
</servlet-mapping>
Controller:
#Controller
public class HomeController {
private static final Logger logger =
LoggerFactory.getLogger(HomeController.class);
#RequestMapping("/home")
public ModelAndView home(String user, HttpServletRequest request) {
logger.info("Home controller has been executed");
ModelAndView mv = new ModelAndView();
mv.addObject("userName", user);
mv.addObject("controllerName", request.getRequestURI());
mv.setViewName("home");
return mv;
}
#RequestMapping(value = "/testAjax", method = RequestMethod.POST)
public String testAjax(#RequestParam("memberId") String id,
HttpServletRequest request, HttpServletResponse response,
Locale locale, Model model) {
logger.info("Text Ajax action has been executed. My Parameter is " + id);
return id;
}
}
After turning on Tomcat 8 server on STS IDE, accessing this web with this url http://localhost:8080/home.fst works okay.
But on the page, calling AJAX like below throws a 404 error:
$.ajax({
type: "POST",
url: "/testAjax.fst",
data: {"memberId" : "test"},
success: function (result) {
console.log(result)
}
});
This is console error log:
POST http://localhost:8080/testAjax.fst 404 (Not Found)
k.cors.a.crossDomain.send jquery-2.1.3.min.js:4
n.extend.ajaxhome.fst:11 (anonymous function) jquery-2.1.3.min.js:3
n.event.dispatch jquery-2.1.3.min.js:3
r.handle
Strange thing is that it calls testAjax controller just fine and there's no error log on server.
logger.info("Text Ajax action has been executed. My Parameter is " + id);
When textAjax action is invoked by my AJAX, the log is printed as well. I checked it out with debug point too (it broke alright).
What seems to be the matter??
Everything's good just Add #ResponseBody annotation in your method and also I suggest you to change your request method POST to GET
Spring
#RequestMapping(value = "/testAjax", method = RequestMethod.GET) //Made Change
#ResponseBody //added
public String testAjax(#RequestParam("memberId") String id, HttpServletRequest request, HttpServletResponse response, Locale locale, Model model) {
logger.info("Text Ajax action has been executed. My Parameter is " + id);
return id;
}
JQuery
$.ajax({
type: "GET", //Made Change
url:"/testAjax.fst",
data: {"memberId" : "test"},
success: function (result) {
console.log(result)
}
});

Resources