I have an endpoint secured with token given in the header. I would like to create my custom annotation so when I annotate the controller method the validation token goes first and then if the token was accurate do the rest of the method. I tried do it with interceptor and it worked but I want to do it using reflection to have custom annotation. I can share some of my code snippet but there is not a lot of code because I couldn't find some that tells me how to get token from header and validate it. And I'm a noob in reflection.
Custom Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface TokenValidation {
String token() default "";
}
Controller Class
#TokenValidation
#PostMapping("/endpoint")
public ResponseEntity createComplianceDirectory(#RequestBody ComplianceDirRequest request) {
return ResponseEntity.status(HttpStatus.OK).build();
}
Reflection class
#Value("${token}")
private String token;
private Reflections reflections;
public Reflection() {
reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("com.sampleapp.controller"))
.setScanners(new FieldAnnotationsScanner()));
setReflections();
}
public void setReflections() {
Set<Method> methods = reflections.getMethodsAnnotatedWith(TokenValidation.class);
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
String requestToken = request.getHeader("Authorization");
}
}
Could someone tell me:
How to register that reflection class in spring boot to be invoked when the controller method is called.
Is it a good way in my reflection class to retrieve the header ?
Related
Say I have an annotation (#RequiresAccount) introduced in another library and I'm using it in my project, is there a way to conditionally apply it to a method, e.g. apply it when the customer is from website A and not apply when customer is from website B?
I've taken a look and the only possibility I've found was, creating a wrapper-Annotation:
#Aspect
#Component
public class RequiresAccountWrapperAspect {
#Autowired
private HttpServletRequest request;
private RequiresAccountAspect requiresAccountAspect = new RequiresAccountAspect();
#Around("#annotation(com.example.demo.components.RequiresAccountWrapper)")
public Object checkIfRequiresAccount(ProceedingJoinPoint joinPoint) throws Throwable {
String requestURL = request.getRequestURL().toString();
if (requestURL.startsWith("http://localhost")) {
requiresAccountAspect.checkAccount(joinPoint);
}
return joinPoint.proceed();
}
}
So everywhere you've used your RequiresAccount annotation, you can use this wrapper instead. For example:
#GetMapping("/test")
#RequiresAccountWrapper
public String h() {
return "test";
}
As you can see I'm creating a new instance of the aspect. I don't know if you have access to the Aspect-class itself but if you have you can then call the method in it and pass the joinPoint. To find the URL from the request you can inject the HttpServletRequest.
I have the following Spring MVC Controller:
#RestController
#RequestMapping(value = "my-rest-endpoint")
public class MyController {
#GetMapping
public List<MyStuff> defaultGet() {
...
}
#GetMapping(params = {"param1=value1", "param2=value2"})
public MySpecificStuff getSpecific() {
...
}
#GetMapping(params = {"param1=value1", "param2=value3"})
public MySpecificStuff getSpecific2() {
return uiSchemas.getRelatedPartyUi();
}
}
What I need is to make it more generic using custom annotations:
#RestController
#RequestMapping(value = "my-rest-endpoint")
public class MyController {
#GetMapping
public List<MyStuff> defaultGet() {
...
}
#MySpecificMapping(param2 = "value2")
public MySpecificStuff getSpecific() {
...
}
#MySpecificMapping(param2 = "value3")
public MySpecificStuff getSpecific2() {
return uiSchemas.getRelatedPartyUi();
}
}
I know that Spring meta annotations could help me with that.
So I define the annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping(method = RequestMethod.GET, params = {"param1=value1"})
public #interface MySpecificMapping {
String param2() default "";
}
That alone won't do the trick.
So I add an interceptor to deal with that "param2":
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// get annotation of the method
MySpecificMapping mySpecificMapping = handlerMethod.getMethodAnnotation(MySpecificMapping.class);
if (mySpecificMapping != null) {
// get the param2 value from the annotation
String param2 = mySpecificMapping.param2();
if (StringUtils.isNotEmpty(param2)) {
// match the query string with annotation
String actualParam2 = request.getParameter("param2");
return param2 .equals(actualParam2);
}
}
}
return true;
}
}
And include it into the Spring configuration of course.
That works fine but only if I have one such custom mapping per controller.
If I add two methods annotated with #MySpecificMapping even having different values of "param2" then I get an "ambiguous mapping" error of the application start:
java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'myController' method
public com.nailgun.MySpecificStuff com.nailgun.MyController.getSpecific2()
to {[/my-rest-endpoint],methods=[GET],params=[param1=value1]}: There is already 'myController' bean method
public com.nailgun.MySpecificStuff com.nailgun.MyController.getSpecific() mapped.
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.assertUniqueMethodMapping(AbstractHandlerMethodMapping.java:576)
- Application startup failed
I understand why it happens.
But can you help me to give Spring a hint that those are two different mappings?
I am using Spring Boot 1.4.3 with Spring Web 4.3.5
#AliasFor is annotation for do things like this.
Here is an example of custom annotation with #RequestMapping
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #interface JsonGetMapping {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String value() default "";
}
and also example of use
#JsonGetMapping("/category/{categoryName}/books")
public List<Book> getBooksByCategory(#PathVariable("categoryName") String categoryName){
return bookRepository.getBooksByCategory(categoryName);
}
You can not bind annotations in the stack with their params and Spring will consider these two methods as methods with equal #RequestMapping.
But you could try make a trick: embed somehow your custom annotation enhancer before mapping builder and perform there annotations replacing:
Get all methods with annotation #MySpecificMapping:
MySpecificMapping myMapping = ...;
Read #RequestMapping annotation for each such method, let say it will be
RequestMapping oldMapping = ...;
Create new instance of the #RequestMapping class:
RequestMapping newMapping = new RequestMapping() {
// ... rest methods
#Override
public String[] params() {
// here merge params from old and MySpecificMapping:
String[] params = new String[oldMapping.params().length + 1];
// todo: copy old one
// ...
params[params.length-1] = myMapping.param2();
return params;
}
}
Forcly assign this new newMapping to each method correspondingly instead of oldMapping.
This is quite tricky and complex, but this is only one way to achieve what you want, I believe.
I think the best way around this is to move your #RequestMapping annotation to the method level instead of the class level.
The error Spring is giving you is because Spring is binding multiple handlers on one path which is invalid. Maybe give us an example of the URL's you'd like to expose so we have a better overview of what you're trying to build.
I am using jersey 1.9.1. I have rest method like following where
Authorization header contained encoded credentials such as username
and password and it is parsed in a method and mapped local values.
#PUT
#Path(SystemConstants.REST_MESSAGE_SENDSMS)
#Consumes(MediaType.APPLICATION_JSON)
#Produces({MediaType.APPLICATION_JSON})
public Response sendSms(#HeaderParam("Authorization") String authorization, String param) {
String[] credentials = ImosUtils.getUserCredentials(authorization);
String username = credentials[0];
String password = credentials[1];
}
I am trying to design a way to make this process automatically, without writing same parsing code in each method. I mean I would like to know if writing a special annotation such as HeaderParamExtended to this is used to parse this credentials.
I am using jersey 1.9.1 version as rest api. Where I have to edit a class in that life cycle?
#PUT
#Path(SystemConstants.REST_MESSAGE_SENDSMS)
#Consumes(MediaType.APPLICATION_JSON)
#Produces({MediaType.APPLICATION_JSON})
public Response sendSms(#HeaderParamExtended("Authorization","username") String username, #HeaderParamExtended("Authorization","password") String password, , String param) {
}
Normally you need an InjectableProvider to support the custom injection, and also an Injectable to provide the value.
Here's an example
#BasicAuth
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface BasicAuth {
}
InjectableProvider
#Provider
public class BasicAuthInjectionProvider
implements InjectableProvider<BasicAuth, Parameter> {
#Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(ComponentContext cc, BasicAuth a, Parameter c) {
return new BasicAuthInjectable();
}
}
Injectable
public class BasicAuthInjectable extends AbstractHttpContextInjectable<User>{
#Override
public User getValue(HttpContext hc) {
String authHeaderValue = hc.getRequest()
.getHeaderValue(HttpHeaders.AUTHORIZATION);
String[] credentials = ImosUtils.getUserCredentials(authHeaderValue);
return new User(credentials[0], credentials[1]);
}
}
One thing you'll notice is that I have a User class. This is to wrap the username and password, and just have one injection point. i.e.
public Response getSomething(#BasicAuth User user) {
}
I actually tried to do it your way, with
public Response getSomething(#BasicAuth("username") String username,
#BasicAuth("password") String password) {
And in the InjectableProvider get the annotation value from the annotation passed to the getInjectable, then pass that value onto the BasicAuthInjectable. From there check to see if the value is "username" or "password" and return the corresponding value. But for some reason the injection providers were not even called. You can play around with it to see if you can get it to work. But to me the User looks cleaner anyway, and with the two strings, the injection providers are called twice and you need to parse the headers twice. Seems unnecessary.
While trying to get request object in Aspect I found two solutions. I would like to know performance wise which one is better. Here are the details.
I wanted to execute myAspectMethod for all methods annotated by '#myAnnotation'. So where ever spring finds #myAnnotation at method level myAspectMethod will be executed where I am using request object to perform business logic. To get request I found two solutions
Inject request object in Aspect class like
below
#Aspect
public class MyAspect {
#Autowired(required = true)
**private HttpServletRequest request;**
#Around("#annotation(myAnnotation)")
public Object myAspectMethod(ProceedingJoinPoint pjp,
MyAnnotation myAnnotation) throws Throwable {
//....do something with request object
}
}
By sending request object as argument in annotated method and access it thru the argument list received
Access request in Aspect
#RequestMapping(method = { RequestMethod.GET }, value = "/something")
#MyAnnotation
public Object myAnnotatedMethod(**HttpServletRequest request**)
{
//....some business logic
}
#Aspect
public class MyAspect {
#Around("#annotation(myAnnotation)")
public Object myAspectMethod(ProceedingJoinPoint pjp,
MyAnnotation myAnnotation) throws Throwable {
HttpServletRequest request = getRequestArgument(pjp);
....do something with request object
}
private HttpServletRequest getRequestArgument(ProceedingJoinPoint pjp) {
for (Object object : pjp.getArgs()) {
if (object instanceof HttpServletRequest) {
return (HttpServletRequest) object;
}
}
return null;
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
}
Between above two different ways of request object usage which one is better from performance perspective? This is important question for which I would like to know the answer.
What are the other pros and cons of each approach.
I'm not sure that the first method works. Even if you can autowire HttpServletRequest this way, you'll have to make your aspect request-scoped.
I think the best option would be to use RequestContextHolder:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
This method uses a thread-local storage already populated by Spring and doesn't need any changes in your method signature.
The first method did not work.
#Autowired(required = true)
private HttpServletRequest request;
did have the request specific data.
I am currently using the following to extract custom headers from my request
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes()).getRequest();
request.getHeader("mycustom");
What's the Jersey equivalent of this Spring MVC code? I need the response to return 201 along with the resource URL, following successful POST:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
Widget create(#RequestBody #Valid Widget wid) {
return service.create(wid);
}
This is the shortest example I found in Jersey. Is it required to build the response manually for successful POST/201?
#POST #Path("widget")
Response create(#RequestBody #Valid Widget wid) {
return Response
.status(Response.Status.CREATED)
.entity("new widget created")
.header("Location","http://localhost:7001/widget"+wid)
.build();
}
Example of comment, per request of OP:
I don't think there is an equivalent, but personally, I like creating my own response. I have more control. Also there is a Response.created(...), this will automatically set the status. It accepts the URI or String as an argument, and sets the location header with that argument. Also You can use UriInfo to getAbsolutePathBuilder() then just append the created id. That's generally the way I go about it.
#Path("/widgets")
public class WidgetResource {
#Inject
WidgetService widgetService;
#POST
#Consumes(...)
public Response createWidget(#Context UriInfo uriInfo, Widget widget) {
Widget created = widgetService.createWidget(widget);
UriBuilder builder = uriInfo.getAbsolutePathBuilder();
URI uri = builder.path(created.getId()).build();
return Response.created(uri).build();
}
}
This is the general pattern I use for my create methods. The collection path will be the absolute path obtained from uriInfo.getAbsolutePath(Builder), then you just append the created id to the path. So if the collection path is http://blah.com/widgets, and the id is someId, then the location header will be Location: http://blah.com/widgets/someId (which is the location of the new resource), and the status will get set to 201 Created
Response.created(..) returns Response.ResponseBuilder, just like Response.status, so you can do the usual method chaining. There are a number of static method on Response that have default settings, like ok, noContent. Just do through the API. Their names pretty much match up with the status name.
I don't think there is an annotation like that in Jersey. You could create one using Name Binding.
Basically, you create an annotation and add the #NameBinding meta-annotation:
#NameBinding
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ResponseStatusCreated {}
Next you create an filter which will override the status.
#ResponseStatusCreated
#Provider
class StatusCreatedFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
responseContext.setStatusInfo(Response.Status.CREATED)
String location = "..."; // set based on responseContext.getEntity()
// or any other properties
responseContext.getHeaders().putSingle("Location", location);
}
}
Then use the same annotation on your resource methods.
#POST
#Path("widget")
#ResponseStatusCreated
Object create(#RequestBody #Valid Widget wid) {
return ... // return whatever you need to build the
// correct header fields in the filter
}
You could also make it more generic by creating an annotation that will accept the status as an argument, i.e. #ResponseStatus(Status.CREATED) and get the status in the filter using responseContext.getAnnotations().