Equivalent of javax.ws.rs NameBinding in Micronaut? - java

I am working on porting an old HTTP server to Micronaut and I am stuck trying to port an authorization filter that used the javax.ws.rs NameBinding annotation to a Micronaut HTTP server filter. 90% of my endpoints/controllers use the NameBinding annotation I have so using the standard Micronaut HTTP server filter would be difficult.
One code smelly thought was to create a filter accepting all api endpoints (ie. #Filter("/**")) and then maybe storing a list of all the paths that don't require authorization and comparing that against the requested path.
Another hack I attempted to was to try and derive the target method with reflections through the request/chain but it seems that target method is held in an #Internal class which leads me to believe I should not be reflecting on the method from a filter. If I was able to reflect on the target method from a filter I could look for my old annotation and filter on that.
In general are there any guiding principles for providing filters to a large subset of controllers/methods excluding a handful, for example an inverse filter pattern (although this would also not be ideal)?
Is there any way in micronaut to manually control the injection of filters?

If you need a fine grained control over your endpoints, I'll go for micronaut AOP
#Documented
#Retention(RUNTIME)
#Target(ElementType.METHOD)
#Around
#Type(AuthenticatedInterceptor.class)
public #interface Authenticated {
}
and the interceptor coresponding
#Singleton
public class AuthenticatedInterceptor implements MethodInterceptor<Object, Object> {
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
final var authHeader = ServerRequestContext.currentRequest()
.map(HttpMessage::getHeaders)
.flatMap(HttpHeaders::getAuthorization)
.orElseThrow(() -> new RuntimeException("no header"));
validate(authHeader);
return context.proceed();
}
}
then you'll have to add #Authenticated on each methods that need to be authenticated.
UPDATE
Micronaut security provides it's own #Secured annotation.

Related

Proxying SOAP services with REST - Sharing generated SEI's

I have a few SOAP webservices I need to proxy with a REST frontend. REST API operations would map 1-1 to their SOAP equivalents.
For instance for invoking operation operation1 for the SOAP webservice at http://soapservices/ServiceA its REST proxy would be POST http://restservices/ServiceA/operation1, with the same exact data binding objects for arguments and return value.
I'll use Camel to dynamically route REST invocations to their corresponding SOAP endpoints, and possibly perform some common pre or post processing.
Ideally I'd like to have a project where I just add the proxied WSDL's, have the JAX-WS Service Endpoint Interfaces generated with Maven's cxf-codegen-plugin, and dynamically instance CxfEndpoint beans for the services using a properties file that will enumerate them.
For the JAX-RS part, I've found reusing the generated SEI's to be quite convenient, and have used a SpringJAXRSServerFactoryBean that gets its resourceClasses set programmatically at application startup, reading enumerated services from the same properties file.
I've got a working draft project, but having to manually change the generated SEI's to add JAX-RS annotations (#Path, #Consumes, #Produces, #Post and so on) looks just bad.
For instance:
For this, and N in general generated SEI's like this one:
#WebService(...)
#XmlSeeAlso(...)
#SoapBinding(...)
public interface ServiceAPortType {
#WebMethod(operationName="operation1")
#WebResult(...)
public ResponseObject operation1(#WebParam(...) ParamObject param);
}
I have a yml file:
services:
- service: ServiceA
config:
address: http://soapservices/ServiceA
serviceClass: ServiceAPortType.class
- service: ServiceB
config:
address: http://soapservices/ServiceB
serviceClass: ServiceBPortType.class
Then register CxfEndpoints for the clients:
#PostConstruct
public void registerSOAPClients(){
Map<String, Object> values = (Map<String, Object>) ws.getConfig(); // "ws" injected with #ConfigurationProperties
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(CxfEndpoint.class);
for (Map.Entry<String, Object> val : values.entrySet()) {
bdb.addPropertyValue(val.getKey(), val.getValue());
}
beanFactory.registerBeanDefinition(ws.getService(), bdb.getBeanDefinition());
}
The JAX-RS part is the one that needs manual tweaking of the generated SEI's to add #Path, #Consumes and various annotations to let SpringJAXRSServerFactoryBean use them:
#Bean
public SpringJAXRSServerFactoryBean jaxRSfactoryBean() throws ClassNotFoundException {
SpringJAXRSServerFactoryBean bean = new SpringJAXRSServerFactoryBean();
bean.setAddress("/restservices");
bean.setResourceClasses(jaxRSAnnotatedSEIs); // jaxRSAnnotatedSEIs injected from yml serviceClass
return bean;
}
The Camel route is pretty simple and along the lines of:
from("cxfrs:bean:jaxRSfactoryBean?providers=#jsonProvider")
.setHeader("serviceName", getServiceFromURI())
.setHeader("operationName", getOperationFromURI())
.toD("cxf:bean:${header.serviceName}Service?")
.transform().simple("${body.get(0)}")
.marshal().json(JsonLibrary.Jackson)
.end();
Is there any way I could automate adding these annotations so generated source stays unedited, or generating JAX-RS SEI's from a SOAP WSDL or the existing JAX-WS SEIs? Or maybe there is a different cleaner approach? I'm open to alternative approaches as long as they don't involve manual modifications of generated sources and rely on properties files only for enumerating and configuring services.

JAX-RS #Path in Composed Annotation

This one seems relatively straightforward. I'm messing around with composed annotations, and I'm trying to do the following:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Path("")
public #interface TestAnnotation {
#AliasFor(annotation = Path.class, attribute = "value")
String path();
}
This does not work. When I use it like so:
#Path("")
public class MyResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#TestAnnotation(path = "/things")
public void postIt(Thing myThing) {
// Do various things and then return a Response
}
}
...I receive a 405 in return. If I do this:
// Remove class-level #Path
// #Path("")
public class MyResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#TestAnnotation(path = "/things")
public void postIt(Thing myThing) {
// Do various things and then return a Response
}
}
...I receive a 404 in return.
There is just something about #Path or the fact that #Path has a required value attribute that results in this just not functioning, and I have no idea how to remedy it.
After further experimentation and research, it would appear that what I am trying to do is literally not possible.
A follow-up attempt was made to utilize Jackson's #JsonView and expose it through my composed annotation via Spring's #AliasFor, and this also failed to function.
I spent some time thinking about how annotations work and considering peeskillet's comments about compilation vs. processing, and I have come to the conclusion that both Jersey and Jackson must use annotation processors that basically just call "Method.isAnnotationPresent()" for detection of relevant annotations. Spring's #AliasFor most likely does not compile nor weave the aliased meta-annotations into the byte-code of the target methods, and thus they are not found by the processors.
My personal solution to this problem was to drop JAX-RS entirely and use Spring's #RequestMapping, which can be aliased in a composed annotation, and to just accept that Jackson's #JsonView simply can't be integrated into a composed annotation.
Obviously, this is not an ideal solution for most people, especially those with large, already established systems. In such situations, it is more likely that the idea of composed annotations will be abandoned long before JAX-RS.
So if anyone has some more insight into the problem, or someone directly from the Spring team wants to chime in, feel free.

How to use PATCH method in CXF

I am trying to use PATCH method in my client using CXF implementation of JAX-RS.
At first I defined the PATCH annotation as
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#HttpMethod("PATCH")
public #interface PATCH {
}
Referencing what was written here :
How to have a #PATCH annotation for JAX-RS?
Then I found out #PATCH was added into CXF 3.1.2, so I changed version in my maven's pom.xml and indeed there is public #interface PATCH inside of package org.apache.cxf.jaxrs.ext; and the code actually looks exactly as what I posted above.
However, when I try to use this annotation on my service definition as
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public interface AbcService {
#PATCH
#Path("/abc/efg")
public SomeDTO patchSomething(RequestObject request);
}
I end up with the java.net.ProtocolException: Invalid HTTP method: PATCH as was said in the queston link I posted above. They discuss some solution for this with Jersey, however what I can I do in CXF, so that I can use :
AbcService abcService = JAXRSClientFactory.create(myURI, AbcService.class, myProviders, true);
abcService.patchSomething(new RequestObject('something'));
So I have couple of questions:
How can I make this work ? No I need to write custom CXF interceptor ?
Why did they add the PATCH annotation into CXF if it doesn't work ?
Some guys in the other topic said that the mentioned PATCH annotation definition works for them. How come ? Does it only make trouble on the client side, and if so why is it ?
Why I can't find this annotation in CXF documentation ? I looked into org.apache.cxf.jaxrs.ext package at http://cxf.apache.org/javadoc/latest/ and I don't see any PATCH. Yet in the latest cxf 3.1.2 I really can find it in this package.
It turns out it's cause because in JAVA7, HttpURLConnection doesn't support PATCH, the supported methods in that class are defined statically as
private static final String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
However it is possible to send PATCH request in CXF, but the Conduit object must be of type AsyncHTTPConduit.
To make CXF use AsyncHTTPConduit, you can programatically achieve it like this
AbcService service = JAXRSClientFactory.create(myURI, AbcService.class, myProviders, true);
WebClient.getConfig(service).getRequestContext().put("use.async.http.conduit", true);
service.patchEnvironmentParameters(patchRequest);
Or
WebClient client = WebClient.create("http://localhost:53261/v1-0/api/environment/parameters");
WebClient.getConfig(client).getRequestContext().put("use.async.http.conduit", true);
client.invoke("PATCH", "{}");
But beware !! In order to make this work, you have put this dependency into your project
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-hc</artifactId>
<version>${cxf.version}</version>
</dependency>
Also make sure that you use the same version of cxf-rt-transports-http-hc and cxf.
But as you can see what I described doesn't solve the original issue, this way I just made 1 specific PATCH request. However in my project there are many PATCH services defined using interfaces like I showed originally
public interface AbcService {
#PATCH
#Path("/abc/efg")
public SomeDTO patchSomething(RequestObject request);
}
So in order to use the AsyncHTTPConduit only on PATCH methods, I had to write custom CXF interceptor, about which you can learn more here http://cxf.apache.org/docs/interceptors.html
The interceptor I wrote runs in PRE_LOGIC phase and it checks what kind of method is used and in case it PATCH, it defined the conduit property. Then in latter phases of service invocation, CXF uses this property to choose which Conduit implementation should be used, and so after
if ( message.get(Message.HTTP_REQUEST_METHOD).equals("PATCH") {
message.put("use.async.http.conduit", true);
}
the AsyncHTTPConduit instance will be used with which the PATCH will work.
Could you try to replace the use of #PATCH with #POST in your code to see if it works ? Your AbcService interface misses an #Path annotation at the type level (unless it is a subresource ?), so it might be worth trying with a standard HTTP verb first to make sure everything else is properly configured.

Authenticating with an API key in JAX-RS

We'd like to secure our rest api using an api key. Here are the requirements:
Public-facing services require an api key.
"Private" services can only accept a call from within the cluster,
not the outside world.
Each api identifies a user, and the User object must be available to
the rest service.
Is there some standard way to do this in a JAX-RS app? (We're using Resteasy.)
I've read all about filters, interceptors and basic auth, but it isn't clear to me what's the best approach.
In an earlier version of the app we had a roll-your-own solution in which public services ran on a public port and private ones on a private port. There was a custom api key lookup that set the User object as a variable into the rest service object.
I can't figure out how to do either of these things using standard JAX-RS.
Using a filter to intercept the request
This kind of authentication could be achieved with a ContainerRequestFilter, intercepting the requests to your resource methods.
The filter will be used to extract the API key from the request and validate it. If the API key is not valid, the request will be refused. Otherwise, the request will proceed to the resource methods.
Have a look at the following piece of code. The ContainerRequestContext API can be used to extract information from the HTTP request:
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Extract and validate the API key from the request
String apiKey = requestContext.getHeaderString("API-Key");
...
}
}
Also have a look at this answer I wrote a while ago about authentication with tokens in JAX-RS. There you will find plenty of details that can be useful to address the situation you described in your question.
Identifying the user
During the authentication process, you must be able to identify the user who is performing the request. To propagate this information to your resource classes/methods you could:
Override the SecurityContext and inject it into your resource classes/methods.
Use a CDI Event and a producer method to create an object that contains the user identifier that can be injected in your resource classes/methods.
For more details on the these approaches, refer to the answer I mentioned above.
Binding the filter to some resource classes/methods
By default, the filters are global (it means they are executed for all the resource methods of your application). To bind the filter to a subset of resource methods or classes, you could use name binding annotations.
Not giving a detailed answer, but just a suggestion. Check for CustomInvokers and register the invoker for the services. Validate the api-key and throw an error if it's not valid. If there is an error then your client gets an error. The Service code won't be called.
For the actual security framework, please check netflix zuul.

Cookie based authorization before calling controllers method in Spring

Recently I came accross this issue. Let's say that I want to give different authorizations to different methods within my controller. I would like to do this using the cookie that was send with the request and to do that I would like to use annotations. I tried to figure out how force #PreAuthorize annotation to work but I am missing something here.
Does anyone know if it's possible scenario (pseudo-code)
#Controller
class myController
{
#SomeAuthorizationAnnotation
#RequestMapping("/")
void doSth()
{
//something is done after authorization
}
}
And now i'd like to write my custom method which grants me access to http request sent to this controller where i could let's say write something like
HttpServletRequest ht = ....
Optional<Cookie> cookie = getCookie(ht.getCookies(), AUTH_TOKEN);
if (cookie.isPresent()) {
//grant authorization for the method call
}
I hope that this is somewhat understandable.
P.S I don't want to bind filters or sth to url's, i want to bind it to methods.
In way to secure your methods you can use #Secured or #PreAuthorize annotations but also you should declare it <global-method-security secured-annotations="enabled" />
See also method security
Well, my solution to this problem was by simply using Authentication Providers within Spring Security and implement interface of UserDetailsService to match my demands according to this project. So then my sessions are automatically handled without necessity for handling Cookies myself. And of course, annotations work.

Categories