I have an endpoint secured with a token given in the header. I would like to create my custom annotation so when I annotate the controller method the validation for the token goes first and then if the token was accurate do the rest of the method. I tried to do it with an interceptor and it worked but I want to do it using reflection. 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 a token from the header and validate it.
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");
}
}
My questions are:
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
Before everything I tried this two solution but didn't work for me
Equivalent of javax.ws.rs NameBinding in Micronaut?
https://blogs.ashrithgn.com/custom-annotation-to-handle-authorisation-in-micronaut-aop-tutorial/
In my application I have to get a string in the Authorization header and then decode it from base64 and the json transform it into a POJO. Certainly the string is a jwt and I need to decode the public part of the json to get a data from a field.
Technically speaking a client will forward the header to me to take it, decode it and extract the data. (It's very bad practice but that's what I have to do).
For this I am using micronaut 2.4.1 and this is my code:
Interceptor:
public class HeadInterceptor implements MethodInterceptor<Object, Object> {
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
Request request = (Request) context.getParameterValueMap().get("request");
// Where do i get Authorization header?
// i.e String token = (String) context.getParameterValueMap().get("Authorization");
String token = "eyJhdWQiOiJ0ZXN0IiwiaXNzIjoidGVzdCIsInN1YiI6InRlc3QiLCJleHAiOjExMTExMTEsImlhdCI6MTExMTExMTEsImRhdGEiOiJ0ZXN0In0=";
ObjectMapper mapper = new ObjectMapper();
Info info = mapper.readValue(new String(Base64.getDecoder().decode(token)), Info.class);
request.setData(info.getSub().toUpperCase());
return context.proceed();
}
}
Controller:
#Controller("/main")
public class MainController {
#Post
#Head
public Single<Response> index(#Body #Valid Request request) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
}
Here's a sample app https://github.com/j1cs/micronaut-jacksonxml-error
(ignore the name is for other issue)
In your implementation, the header cannot be shown in the interceptor because your index method doesn't receive it as a parameter.
So, if you add it as a parameter as below:
...
#Post
#Head
public Single<Response> index(#Body #Valid Request request, #Header("Authorization") String authorizationHeader) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
...
Then, you can retrieve it in the intercept method via getParameterValues(). Basically, it will be the second argument.
...
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
...
String token = (String) context.getParameterValues()[1];
...
}
...
Update
Since you want your Request to contain both body and header, I edited the solution a bit. Basically, the header is added as a member variable to Request as below:
public class Request {
#NotNull
#NotBlank
private String info;
private String data;
#Header("Authorization")
String authorizationHeader;
}
Then, use #RequestBean rather than a #Body annotation on your Request parameter:
...
#Post
#Head
public Single<Response> index(#RequestBean #Valid Request request) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
...
Finally, you can access the header easily in your intercept() method as follows:
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
...
Request request = (Request) context.getParameterValueMap().get("request");
String token = request.authorizationHeader;
...
}
I created a pull request for this change here, so you can check how it works.
In order to address the problem, you may first break the problem into parts.
Part 1: How to get arbitrary header (or list all headers)?
Try to use request.getHeaders() doc.
Part 2: How to get the header named Authorization ?
Use the way in part 1. In addition, be careful about the case. For example, is Authorization the same as authorization?
Method 2:
In controller (https://github.com/j1cs/micronaut-jacksonxml-error/blob/master/src/main/java/me/jics/MainController.java):
public Single<Response> index(#Body Request request, #Header('Authorization') String authorization) {
...
}
p.s. the "Header" annotation's doc is here: https://docs.micronaut.io/2.0.1/api/io/micronaut/http/annotation/Header.html
In interceptor:
...
String token = context.getParameterValueMap().get("authorization");
...
Why the code looks like this:
Firstly get the auth header you want using parameter injection.
Secondly, recall the fundamental concepts of AOP / AspectJ (which your interceptor class uses). Inside your interceptor, you intercept a method (in your case, the index method in controller. Thus, you can happily get the parameters of that method. In the code above, just the authorization parameter.
Please tell me if you are stuck on somewhere (and paste the code and the outputs).
I'm looking for something like JSR-303 Validation Groups (bean validation, when you mark method argument with #Validated(GroupName.class) in controller and specify group in request class fields where needed), but it should decide how to validate at runtime depending on one of request fields.
For example, if we have controller class like this
#Controller
public class MyController {
#Autowired
private MyService myService;
#ResponseBody
#RequestMapping(value = "/path", method = RequestMethod.PUT)
public ResponseVo storeDetail(/*maybe some annotation here*/ DetailRequestVo requestVo) {
return myService.storeDetail(requestVo);
}
class DetailRequestVo {
String type;
Long weight;
Long radius;
}
}
And we want validation depending on type field value: if type = "wheel" then radius and weight fields should be presented, if type = "engine" then only weight field should be presented.
Does Spring (as of 3.2.17) provide API to implement these rules in more declarative approach? org.springframework.validation.Validator looks like not an option here, because its method boolean supports(Class<?> clazz) decides based on class info, not instance info.
Thanks in advance
Not sure about Spring but you can do that with plain JSR-303 using a custom Validator for the class itself... like
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE })
#Constraint(validatedBy = TypeValidator.class)
#Documented
public #interface ValidType {
...
}
public class TypeValidator implements ConstraintValidator<ValidType , Object> {
public boolean isValid(final Object target, final ConstraintValidatorContext context) {
DetailRequestVo request = (DetailRequestVo) target;
// do your checks here
}
and used like
#ValidType
class DetailRequestVo {
String type;
Long weight;
Long radius;
}
Since the custom Validator has access to the whole DetailRequestVo-Object you can do your check of field A depending on field B etc.
I am very new to Spring. I have a REST api written in Spring, but I don't know how to return a JSON response with a custom http response code.
I return a JSON response as follows:
public String getUser(String id){
...
return jsonObj;
}
But it always displays 200 http ok status code.
Here are my questions:
How can I synchronize the response JSON and HTTP code?
How is it possible to return JSON response and custom HTTP code in void function?
Use #ResponseStatus annotation:
#GetMapping
#ResponseStatus(HttpStatus.ACCEPTED)
public String getUser(String id) {...}
Alternative way: If you want to decide programmatically what status to return you can use ResponseEntity. Change return type of a method to ResponseEntity<String> and you'll be offered with a DSL like this:
ResponseEntity
.status(NOT_FOUND)
.contentType(TEXT_PLAIN)
.body("some body");
How I do it
Here is how I do JSON returns from a Spring Handler method.
My techniques are somewhat out-of-date,
but are still reasonable.
Configure Jackson
Add the following to the spring configuration xml file:
<bean name="jsonView"
class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
</bean>
With that,
Spring will convert return values to JSON and place them in the body of the response.
Create a utility method to build the ResponseEntity
Odds are good that you will have multiple handler methods.
Instead of boilerplate code,
create a method to do the standard work.
ResponseEntity is a Spring class.
protected ResponseEntity<ResponseJson> buildResponse(
final ResponseJson jsonResponseBody,
final HttpStatus httpStatus)
{
final ResponseEntity<ResponseJson> returnValue;
if ((jsonResponseBody != null) &&
(httpStatus != null))
{
returnValue = new ResponseEntity<>(
jsonResponseBody,
httpStatus);
}
return returnValue;
}
Annotate the handler method
#RequestMapping(value = "/webServiceUri", method = RequestMethod.POST)
you can also use the #PostMethod annotation
#PostMethod("/webServiceUri")
Return ResponseEntity from the handler method
Call the utility method to build the ResponseEntity
public ResponseEntity<ResponseJson> handlerMethod(
... params)
{
... stuff
return buildResponse(json, httpStatus);
}
Annotate the handler parameters
Jackson will convert from json to the parameter type when you use the #RequestBody annotation.
public ResponseEntity<ResponseJson> handlerMethod(
final WebRequest webRequest,
#RequestBody final InputJson inputJson)
{
... stuff
}
A different story
You can use the #JsonView annotation.
Check out the Spring Reference for details about this.
Browse to the ref page and search for #JsonView.
I have a use case where I need to limit the values that can be passed as the query param.
#Path("/foo")
public interface Foo {
#GET
#Path("/details/id/{id}")
void getFooDetails(#PathParam("id") String id, #QueryParam("sort") String sortDirection);
}
public class FooImpl {
public void getFooDetails(String id, String sortDir) {
//Implementation
}
}
In the above example, I want to restrict the value of query param sort that can be passed via the API to ASC, DESC.
Is there any existing CXF annotation which I can use to restrict the values on a parameter? I haven't found any and so I tried the following solution.
My Approach:
#Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface ValueSet {
String[] allowedValues();
}
The modified interface looks like this.
#Path("/foo")
public interface Foo {
#GET
#PathParam("/details/id/{id}")
void getFooDetails(#PathParam("id") String id, #QueryParam("sort") #ValueSet(allowedValues = {"ASC", "DESC"}) String sortDirection);
}
I wrote a CXF Interceptor which intercepts the API invocation. I used reflection to get a handle on FooImpl.getFooDetails params. But the problem I faced is that the interceptor looks at FooImpl.getFooDetails method and doesn't find the annotations #QueryParam on the method params since #QueryParam is on the base method and the annotation is not inherited.
Interceptor implementation:
#Provider
public class ParamValidationInterceptor extends AbstractPhaseInterceptor<Message> {
public ParamValidationInterceptor() {
super(Phase.PRE_INVOKE);
super.addBefore(someInterceptor);
}
#Override
public void handleMessage(Message message) throws Fault {
UriInfo uriInfo = new UriInfoImpl(message);
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
Method methodToInvoke = (Method) message.get("org.apache.cxf.resource.method");
Parameter[] parameters = methodToInvoke.getParameters();
for (Parameter parameter : parameters) {
if (parameter.isAnnotationPresent(ValueSet.class)) {
ValueSet valueSet = parameter.getAnnotation(ValueSet.class);
QueryParam queryParam = parameter.getAnnotation(QueryParam.class);
Object invokedVal = queryParams.get(queryParam.value());
String[] allowedValues = valueSet.allowedValues();
if (!Arrays.asList(allowedValues).contains(invokedVal)) {
throw new CustomException();
}
}
}
}
}
Can anyone suggest a way forward? It would be great if anyone can suggest an alternative approach.
P.S: I am using CXF as an implementation for JAX-RS and spring is used as a container.
Update:
Like #Cássio Mazzochi Molin and #Andy McCright suggested, I will go with #Pattern annotation. But I am curious to know why the JAX-RS annotations are not inherited from the interface although the spec says they will be inherited.
Annotation inheritance
According to the section §3.6 Annotation Inheritance of the JAX-RS specification, it is recommended to always repeat annotations instead of relying on annotation inheritance.
Refer to this answer for the complete quote.
#QueryParam can be applied to different targets
Bear in mind that the #QueryParam annotation can be applied to:
Resource method parameters
Resource class fields
Resource class bean properties
Hence a manual validation can be tricky.
Use Bean Validation
For validation purposes, you should consider Bean Validation. Consider a #Pattern annotation with the allowed values:
#Pattern(regexp = "ASC|DESC")
And just annotate your resource method parameter:
#GET
#Path("foo")
public Response getFoos(#QueryParam("sort")
#Pattern(regexp = "ASC|DESC") String sortDirection) {
...
}
If you prefer case insensitive values, use:
#Pattern(regexp = "ASC|DESC", flags = Pattern.Flag.CASE_INSENSITIVE)
If the given value is invalid, a ConstraintViolationException will be thrown. To handle such exception and return a customized response, you can use an ExceptionMapper:
#Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
...
}
}
Perhaps it is just a typo, but CXF may not be recognizing the getFooDetails method (on the interface) because it is annotated with #PathParam instead of #Path.
Instead of using your ValueSet approach, I used BeanValidation, but the following code works for me.
Foo.java
#Path("/foo")
public interface Foo {
#GET
#Path("/details/id/{id}")
Response getFooDetails(
#PathParam("id") #Pattern(regexp="[0-9]*") String id,
#QueryParam("sort") #Pattern(regexp = "ASC|DESC") String sortDirection);
}
FooImpl.java
public class FooImpl implements Foo {
#Override
public Response getFooDetails(String id, String sortDirection) {
Integer idInt = Integer.parseInt(id);
if ("ASC".equals(sortDirection) || sortDirection == null) {
...
} else if ("DESC".equals(sortDirection)) {
...
}
return ...;
}
I've got this working on WebSphere Liberty 17.0.0.2 which is based on CXF 3.1.11.
Hope this helps,
Andy
I have the following combined custom annotation with Springs #PreAuthorize annotation,
#RequestMapping(
produces = MimeTypeUtils.APPLICATION_JSON_VALUE + ";charset=UTF-8",
method = RequestMethod.GET)
#ResponseBody
#PreAuthorize(value = "hasRole('permitAll()')")
#ResponseStatus(HttpStatus.OK)
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Get {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
#AliasFor(annotation = PreAuthorize.class, attribute = "value")
String authorize() default "permitAll()";
}
And I've got the following client using it
#Get(value = "/users", authorize = "hasRole('ROLE_GET_USERS')")
public List<User> retrieveUsers() {
// body
}
As you can see the purpose is to allow clients of #Get annotation to override the #PreAuthorize so that they can provide the role they require for.
I din't have so far any problem using #AliasFor, even in this example #RequestMapping is working, but unfortunately it does not override the value of #PreAuthorize and everyone can still access the resources as the default value is permitAll().
I wonder first of all why this does not work, and second if it is possible to make it work?