I have an HTTP GET endpoint that accepts some query parameters:
#GetMapping("/cat")
public ResponseEntity<Cat> getCat(#RequestParam("catName") String catName){ ...
If the clients will send additional query parameters, the endpoint will ignore them.
GET .../cat?catName=Oscar Getting Oscar
GET .../cat?catName=Oscar&gender=male Getting Oscar
GET .../cat?catName=Oscar&x=y Getting Oscar
I want to reject HTTP requests that will send additional query parameters:
GET .../cat?catName=Oscar OK
GET .../cat?catName=Oscar&gender=male Reject (HTTP error code XYZ)
GET .../cat?catName=Oscar&x=y Reject (HTTP error code XYZ)
I can change the signature of the method to accept a map and validate the values in the map as suggested here.
Is there a way to do with while keeping the cleaner and self explained method signature?
You can try to implement a HandlerInterceptor and validate such rule in preHandle(). If the request contains the query parameters that does not defined in the controller method , you just throw a specific type of Exception and configure a #ControllerAdvice to handle this exception. Something like :
public class FooHandlerInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
Set<String> allowQueryParams = Stream.of(hm.getMethodParameters())
.map(p -> p.getParameterAnnotation(RequestParam.class))
.map(req -> req.value())
.collect(toSet());
for (String currentRequestParamName : request.getParameterMap().keySet()) {
if (!allowQueryParams.contains(currentRequestParamName)) {
throw new FooRestException();
}
}
}
return true;
}
}
And the #ControllerAdvice to handle the Exception :
#ControllerAdvice
public class FooExceptionHandler {
#ExceptionHandler(FooRestException.class)
public ResponseEntity<Object> handle(FooRestException ex) {
return new ResponseEntity<>("Some query parameter are not defined", HttpStatus.BAD_REQUEST);
}
}
Finally register FooHandlerInterceptor to use it :
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FooHandlerInterceptor());
}
}
I just show you the idea. You can further tweak the codes in HandlerInterceptor if you want such checking is only applied to a particular controller method.
Related
Whenever a request is made, I need to get the request URI for some internal calculations.
For some time I've been doing it like this:
public Mono<Response> example(ServerHttpRequest req) { ... }
And then using req.getURI(), but that becomes a pain once you need to pass it down multiple times. I need the URI object to extract scheme, schemeSpecificPart, host, port from it.
Is there a way to get these properties without extracting them from a request?
UPD: I see that for Web MVC there are convenient methods to retrieve request URI. But I need the same for reactive stack (netty).
It can be achieved by creating WebFilter that puts ServerHttpRequest into the Context:
#Component
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveRequestContextFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
return chain
.filter(exchange)
.contextWrite(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));
}
}
Additionally, create a class that provides static access to request data:
public class ReactiveRequestContextHolder {
public static final Class<ServerHttpRequest> CONTEXT_KEY = ServerHttpRequest.class;
public static Mono<ServerHttpRequest> getRequest() {
return Mono.deferContextual(Mono::just).map(ctx -> ctx.get(CONTEXT_KEY));
}
public static Mono<URI> getURI() {
return getRequest().map(HttpRequest::getURI);
}
}
Methods can be accessed through the class name directly without having to instantiate them. Just be aware that it should not be accessed before the filter is executed.
Example of usage:
#RestController
#RequestMapping
public class TestController {
#GetMapping("/test")
public Mono<URI> test() {
return ReactiveRequestContextHolder.getURI();
}
}
Reference
You can try this :
public Mono<Response> example(WebRequest request){
System.out.println(request.getDescription(false));
.......
}
You can turn this false to true in getDescription as false will only give you the Uri which i think is the only thing you need.
You can inject it in any bean.
#Autowired
private HttpServletRequest request;
i'm trying to use cache (caffeine) with Spring boot and im having a problem. I need to check the header "header-name" in every call but application is caching it so after first request with the right header, dont matter what header i send and the application will not check it and is just returning data from the cache, is there anyway that i can force spring to check header and then get data from cache?
#GetMapping("/request-a")
#Cacheable(cacheNames = "cachename", key = "#root.methodName")
public ResponseEntity<?> makeRequest(#RequestHeader("header-name") String headerName) {
this.authConfig.headerCheck(headerName);
/*
code
*/
}
I already used header "Cache-Control:no-cache" and didnt resolve my problem.
Thanks in advance.
Edit1: method "headerCheck" just check if its equal to another String or not null.
Found a solution:
Create a classe that implements HandlerInterceptor and use preHandle method.
#Component
public class CheckHeaderInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// validate what you want, on error return false
// if everything its ok, return true
}
}
Then register the handler with:
public class WebMvcConfig implements WebMvcConfigurer {
#Autowired
private CheckHeaderInterceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("url that you wannna use handler");
}
}
I am using Spring Boot 2.3.8 for creating rest services. I have a requirement to validate the request headers such that it should have certain headers otherwise throw error. This should be common for all the methods or services. I tried below,
public ResponseEntity<Object> addEmployee(
#RequestHeader(name = "header1", required = true) String header1,
#RequestHeader(name = "header2", required = true) String header2,
#RequestBody Employee employee)
throws Exception
{
But I need to add this for all the methods in all the controllers. If this is the case how can I throw an error like "Header1 missing in request headers" / "header2 missing in request headers" for all the services globally ?
For global use you can register an interceptor.
#Component
public class MyHandlerInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3) throws Exception {
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model) throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//here check headers contained in request object
if (request.getHeader("header1") == null || request.getHeader("header2") == null) {
response.getWriter().write("something");
response.setStatus(someErrorCode);
return false;
}
return true;
}
And then register it
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Autowired
private MyHandlerInterceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}
This is what filters are for. You want to filter out requests based on a header being there or not and return an error if its missing.
Extend OncePerRequestFilter optionally override shouldNotFilter if you do not want it to be used for all request OR you could implement a normal Filter and use FilterRegistrationBean to register it only for a specific path.
then in the filter you could throw an exception and let Spring figure out how to display that, or actually set the response to something meaningful.
If you have Zuul in place then you can read the request header attributes there in pre route and on validation failure reply back to the request with error populated.
This is a cross-cutting concern and should be done using AOP. Since you're using Spring, you can do the following:
Create an annotation called ValidateHeaders:
public #interface ValidateHeaders {}
Create a #Before advice that intercepts methods annotated with #ValidateHeaders:
#Before("#annotation(com.somepackage.ValidateHeaders)")
public void controllerProxy(JoinPoint jp) {
Object[] args = jp.getArgs();
//validation logic, throw exception if validation fails
}
Note that you'll have to extract the fields using reflection as:
Annotation[][] pa = ms.getMethod().getParameterAnnotations();
You can iterate through all the annotations and search for request headers as:
if(annotations[i][j].annotationType().getName().contains("RequestHeader")) {
RequestHeader requestHeader = (RequestHeader) m.getParameterAnnotations()[i][j];
//now access fields in requestHeader to do the validation
}
Here's an article that can get you started with Advice types:
https://www.baeldung.com/spring-aop-advice-tutorial
I use #ControllerAdvice to handle all my app exceptions :
#ControllerAdvice
public class ExceptionHandlingController {
#ExceptionHandler({UnauthorizedException.class})
public String unauthorizedException() {
.........
}
#ExceptionHandler({UnauthorizedAjaxException.class})
#ResponseBody
public void unauthorizedAjaxException() {
.........
}
#ExceptionHandler({Exception.class})
public String globalException(){
.........
}
}
And somewhere in my code i do throw new UnauthorizedException();
#Around("#annotation(Authenticated)")
public Object profilingAuthentication(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
if( request.getSession().getAttribute("idContact") == null ) {
if( "XMLHttpRequest".equals(request.getHeader("X-Requested-With")) )
throw new UnauthorizedAjaxException();
throw new UnauthorizedException();
}
return pjp.proceed();
}
But sadly Spring MVC appears to be acting random by using the most generic case (Exception) rather than more specific ones (UnauthorizedException for example). And sometimes he choose the correct one !
How the order works works ? and is there any way to specify the order ?
UnauthorizedException is a custom exception
public class UnauthorizedException extends Exception {
public UnauthorizedException(){
super();
}
public UnauthorizedException(String message){
super(message);
}
}
UPDATE
i found out that the order it's not rondom actually the methods who throw UnauthorizedException works normally but the others not !
#Authenticated
#RequestMapping(value="/favoris")
public String favoris(ModelMap model, HttpServletRequest request)
throws UnauthorizedException {
....
}
#Authenticated
#RequestMapping(value="/follow")
public String follow(ModelMap model, HttpServletRequest request) {
.....
}
So i have to add throws UnauthorizedException manually or there is some other solution ?
we are using exception handler in following way and never order get mixed and it work as expected.
So it could be possible if you will use it as following example then it will solve your problems
**********handler class******************
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = Exception.class)
public boolean handle1(Exception exc) {
System.out.println("#####Global Exception###" + exc);
exc.printStackTrace(System.out);
return true;
}
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = CustomException.class)
public boolean handle2(CustomException exc) {
System.out.println("###custom exception######" + exc);
exc.printStackTrace(System.out);
return true;
}
}
***************Controller class************
#RestController("test")
#RequestMapping("/test1")
public class TestController {
#RequestMapping("/t1")
public boolean test() {
if (true) {
throw new CustomException();
}
return true;
}
}
In above example exception habdler is handle2 because 1st of all it will search for matching exception if not found then go for parrent handler
If we throw new NullPointerException() then it will search for matching handler but not found in this case then go for parrent that is handle1
for more you can refer here
I hope it will help you. Thanks
Use an annotation #Order or implement an interface Ordered for #ControllerAdvice.
See implementations of:
ExceptionHandlerMethodResolver class
ExceptionHandlerExceptionResolver class (method initExceptionHandlerAdviceCache)
AnnotationAwareOrderComparator class
There is no order/priority as long as you have a single controlleradvice class in your project. But if you have multiple controlleradvice classes, you can set the Order. But here, in your case, the order is not applicable as the two exceptions are handled differently (i.e., UnauthorizedException and Exception).
The good thing is, Spring will automatically find the respective custom Exception class (if any,
otherwise generic Exception) and invoke the corresponding method.
Please refer for more information on Spring Controller Advice and Exception handling:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
I am trying to create a custom http param binding for my restful service. Please see the example below.
#POST
#Path("/user/{userId}/orders")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(#PathParam("userId") String someString, #UserAuthHeaderParam String authString){
}
You can see that there is a UserAuthHeaderParam annotation in the function signature. What I want to do is have a custom http param binding other than the standard javax.ws.rs.*Param .
I have try to implement org.glassfish.hk2.api.InjectionResolver which basically extract the value from http header:
public class ProtoInjectionResolver implements InjectionResolver<UserAuthHeaderParam>{
...
#Override
public Object resolve(Injectee injectee, ServiceHandle< ? > root)
{
return "Hello World";
}
...
}
When I call the restful service, the server get below exceptions. It indicates that the framework fails to resolve the param in the function signature:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=String,parent=MyResource,qualifiers={}),position=0,optional=false,self=false,unqualified=null,2136594195),
java.lang.IllegalArgumentException: While attempting to resolve the dependencies of rs.server.MyResource errors were found
Please help. Any advise is appreciated. I do make a lot of search on google but fails to make it work. Jersey 2.*. How to replace InjectableProvider and AbstractHttpContextInjectable of Jersey 1.* might be the similar question.
-- UPDATES:
I use AbstractBinder to bind my resolver to UserAuthHeaderParam:
public class MyApplication extends ResourceConfig
{
public MyApplication()
{
register(new AbstractBinder()
{
#Override
protected void configure()
{
// bindFactory(UrlStringFactory.class).to(String.class);
bind(UrlStringInjectResolver.class).to(new TypeLiteral<InjectionResolver<UrlInject>>()
{
}).in(Singleton.class);
}
});
packages("rs");
}
}
Thank you!
If all you want is to pass value directly from the header to the method you don't need to create custom annotations. Let's say you have a header Authorization, then you can easily access it by declaring your method like this:
#GET
public String authFromHeader(#HeaderParam("Authorization") String authorization) {
return "Header Value: " + authorization + "\n";
}
You can test it by calling curl, e.g.
$ curl --header "Authorization: 1234" http://localhost:8080/rest/resource
Header Value: 1234
Given that the answer to your question, how to create custom binding is as follows.
First you have to declare your annotation like this:
#java.lang.annotation.Target(PARAMETER)
#java.lang.annotation.Retention(RUNTIME)
#java.lang.annotation.Documented
public #interface UserAuthHeaderParam {
}
Having your annotation declared you have to define how it will be resolved. Declare the Value Factory Provider (this is where you'll have access to the header parameters - see my comment):
#Singleton
public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) {
return null;
}
return new AbstractHttpContextValueFactory<String>() {
#Override
protected String get(HttpContext httpContext) {
// you can get the header value here
return "testString";
}
};
}
}
Now declare an injection resolver
public class UserAuthHeaderParamResolver extends ParamInjectionResolver<UserAuthHeaderParam> {
public UserAuthHeaderParamResolver() {
super(UserAuthHeaderParamValueFactoryProvider.class);
}
}
and a Binder for your configuration
public class HeaderParamResolverBinder extends AbstractBinder {
#Override
protected void configure() {
bind(UserAuthHeaderParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(UserAuthHeaderParamResolver.class)
.to(new TypeLiteral<InjectionResolver<UserAuthHeaderParam>>() {})
.in(Singleton.class);
}
}
now the last thing, in your ResourceConfig add register(new HeaderParamResolverBinder()), like this
#ApplicationPath("rest")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new HeaderParamResolverBinder());
packages("your.packages");
}
}
Given that, you should be now able to use the value as you wanted:
#GET
public String getResult(#UserAuthHeaderParam String param) {
return "RESULT: " + param;
}
I hope this helps.
I don't know how to resolve your exception. However, may I propose you a different way to do the same thing. I hope it helps.
I've faced exactly the same problem: I need extra parameters in the http header (btw, also related to authentication). Besides, I need to send them in every call, since I want to do a "typical" rest implementation, without maintaining a session.
I'm using Jersey 2.7 - but I'd say it should work in 2.0. I've followed their documentation
https://jersey.java.net/documentation/2.0/filters-and-interceptors.html
It's quite clear there, but anyway I copy-paste my implementation below.
It works fine. True there are some other ways to secure a rest service, for example this is a good one:
http://www.objecthunter.net/tinybo/blog/articles/89
But they depend on the application server implementation and the database you use. The filter, in my opinion, is more flexible and easier to implement.
The copy-paste: I've defined a filter for authentication, which applies to every call and it is executed before the service (thanks to #PreMatching).
#PreMatching
public class AuthenticationRequestFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
final MultivaluedMap<String, String> headers = requestContext.getHeaders();
if (headers == null) {
throw new...
}
// here I get parameters from the header, via headers.get("parameter_name")
// In particular, I get the profile, which I plan to use as a Jersey role
// then I authenticate
// finally, I inform the Principal and the role in the SecurityContext object, so that I can use #RolesAllowed later
requestContext.setSecurityContext(new SecurityContext() {
#Override
public boolean isUserInRole(final String arg0) {
//...
}
#Override
public boolean isSecure() {
//...
}
#Override
public Principal getUserPrincipal() {
//...
}
#Override
public String getAuthenticationScheme() {
//...
}
});
}
}
You have to include this filter class in your implementation of ResourceConfig,
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
// my init
// my packages
register(AuthenticationRequestFilter.class); // filtro de autenticación
// other register
}
}
Hope it helps!
If your need is to retrieve all the http headers binding into one object, a solution could be to use the #Context annotation to get javax.ws.rs.core.HttpHeaders; which contains the list of all request headers.
#POST
#Path("/user/{userId}/orders")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(#PathParam("userId") String someString, #Context HttpHeaders headers){
// You can list all available HTTP request headers via following code :
for(String header : headers.getRequestHeaders().keySet()){
System.out.println(header);
}
}
here is my actual implementatipn of UserAuthHeaderParamValueFactoryProvider class
import javax.inject.Inject;
import javax.inject.Singleton;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.model.Parameter;
#Singleton
public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) {
return null;
}
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
//you can use get any header value.
return getContainerRequest().getHeaderString("Authorization");
}
};
}