This seems to be a hot topic based on the amount of questions asked but I have not found the answer I am looking for just yet. I want to implement a simple authorization service in my Quarkus app, but I seem to be repeating code over and over again.
Basically, I take in the JWT from the Authorization Http header and check if the role supplied in it is sufficient to access my endpoint:
public void someApiCall(#Context HttpHeaders headers) {
authService.validate(ApiToken.SOME_API_CALL, headers); // Throws an exception when unauthorized
//…
}
Now, I think this looks really clunky and I do not like the additional parameter that I need for every single Http endpoint. I have done some research into AOP and know how to add an interceptor which could validate the Http headers through an annotation which would be applied to my method:
#Authorize
public void someApiCall(/*…*/) { /*…*/ }
The issue is, I do not know how to pass in arguments into this annotation to specify the required role. I want something like this:
#Authorize(UserRole.SYSADMIN)
This seems pretty simple but I cannot figure it out. Below you will find the interceptor and annotation classes (Missing the required role of course):
Authorize.java
#Retention(value=RUNTIME)
#Target(value=METHOD)
public #interface Authorize {}
AuthorizeInterceptor.java
#Interceptor
#Priority(3000)
#Authorize
public class AuthorizeInterceptor {
#Inject
AuthorizationService authService;
#AroundInvoke
public void validateRole(InvokationContext ctx) {
authService.validate(ApiToken.ALL, ((RestEndpoint)ctx.getTarget()).getHttpHeaders());
}
}
RestEndpoint.java
public class RestEndpoint {
#Context
HttpHeaders headers;
public HttpHeaders getHttpHeaders() { return headers; }
}
SomeResource.java
public class SomeResource extends RestEndpoint {
#GET
#Authorize
public Object someApiCall() {
/* do code directly */
}
}
So, in conclusion, where I write #Authorize, I want to have #Authorize(UserRole.SOME_ROLE).
Thanks in advance!
So, I managed to figure it out. It turns out that it isn't that hard, I just didn't know where to look.
Here are the modified classes:
Authorize.java
#InterceptorBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Authorize {
// Nonbinding is very important. It makes the interceptor fire regardless of the value
#Nonbinding ApiToken value();
}
AuthorizeInterceptor.java
#Interceptor
#Priority(3000)
#Authorize(ApiToken.NULL)
public class AuthorizeInterceptor {
/* fields */
public Object validate(InvokationContext ctx) throws Exception {
authService.validate(/* stays the same */);
return ctx.proceed();
}
}
SomeResource.java
public class SomeResource {
#GET
#Authorize(ApiToken.SOME_API_CALL)
public Object someApiCall() { /* implementation */ }
}
As Turing85 pointed out, a similar API already exists in JavaEE which implements the authorization functionality in the same way.
Related
Is it possible to define multiple authenticationProviders in Micronaut?
Let's say I have an entity A which can be logged using authenticationProviderA: which given a user and pass checks the DB table of A.
Is it possible to add an entity B and its authenticationProviderB which given a user and pass will check the DB table of B?
If so, how do you define in your controller which authenticationProvider you want to use?
After taking a look at io.micronaut.security.authentication.Authenticator I've seen it's possible to have multiple authenticationProviders in Micronaut.
The documentation says:
An Authenticator operates on several {#link AuthenticationProvider} instances returning the first authenticated {#link AuthenticationResponse}.
From what I've seen you just have to implement AuthenticationProvider and the Authenticator will include the implementations (even if it isn't annotated!) in an internal list of AuthenticationProviders.
IMHO this isn't a good way to provide multiple ways to authenticate. In the example provided in the question, the authentication for A and B both require calls to DB which means depending on the order of the execution of the AuthenticationProviders unneeded BD calls will be executed.
I think would be better to provide a way to indicate which AuthenticationProviders has to be used by controller or endpoint.
Maybe there is a way to do that and I just don't know, so feel free to comment if so.
There is no built-in solution regarding the problem, but there are multiple ways to achieve what you want with small amount of code.
Solution №1:
Creating custom AuthenticationRequest.class and LoginController.class if you need multiple login end points:
public class AuthenticationRequestForEntityA extends UsernamePasswordCredentials { ... }
public class AuthenticationRequestForEntityB extends UsernamePasswordCredentials { ... }
in your custom LoginController replace the default UsernamePasswordCredentials with your specific AuthenticationRequestForEntityA or AuthenticationRequestForEntityB and copy paste the rest of the code from the original LoginController.class:
#Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
#Post
public Single<MutableHttpResponse<?>> login(#Valid #Body AuthenticationRequestForEntityA usernamePasswordCredentials, HttpRequest<?> request) {
Flowable<AuthenticationResponse> authenticationResponseFlowable = Flowable.fromPublisher(authenticator.authenticate(request, usernamePasswordCredentials));
...
and then in your authentication providers:
public class AuthenticationProviderA implements AuthenticationProvider {
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
if (authenticationRequest instanceof AuthenticationRequestForEntityA) {
return authenticate(authenticationRequest);
} else {
// return empty
}
}
}
public class AuthenticationProviderB implements AuthenticationProvider {
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
if (authenticationRequest instanceof AuthenticationRequestForEntityB) {
return authenticate(authenticationRequest);
} else {
// return empty
}
}
}
Solution №2: Creating your custom route-based AuthenticationProvider
Since HttpRequest is available in the AuthenticationProvider as an input parameter you can simply authenticate based on the httpRequest path or query parameter properties.
To make the code cleaner you can create your own RouteBasedAuthenticationProvider interface:
public interface RequestBasedAuthenticationProvider extends AuthenticationProvider {
/**
You can check the request path or request parameter or whatever
*/
boolean supports(HttpRequest<?> request);
}
then in Micronaut AuthenticationProvider:
#Context
public class AppAuthenticationProvider implements AuthenticationProvider {
private final Collection<RequestBasedAuthenticationProvider> providers;
constructor(...) {...}
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
return providers.stream()
.filter(provider -> provider.supports(httpRequest))
.findFirst()
.orElseThrow(//Throw provider not found error)
.authenticate(httpRequest, authenticationRequest);
}
}
EDIT: I just realized, is it even possible to perform a custom action with a custom attribute in Java? Or is it just informational?
I want to include an authentication token in my Jax-RS service header, but I don't want to add a parameter to every request to get the header and check it like so:
public Response getUser(#Context HttpHeaders headers) {
if(authorize(headers.getRequestHeader("token").get(0)) {
// Do something
}
}
I would much rather add an attribute to each request (or even the class if that is possible:
#Authorize
public Response getUser() {
// Do something
}
This way I can also add the attribute to only the requests I want to.
And if the request isn't authorized, I can override it and return a 401.
A custom attribute is easy to write, but how can I get the header information in the attribute without passing it in every time?
NOTE: I would rather not use a web.xml. I don't have one right now and I don't like using them. I want to keep my code clean without xml and I think if I used a filter/web.xml it would apply to all calls. If that is the only way, I will, but I much prefer the approach with custom attributes.
"I think if I used a filter/web.xml it would apply to all calls"
Actually there are #NameBinding annotations we can use. For example
#NameBinding
#Rentention(RetentionPoilicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface Authorize {}
Then just annotate the filter and the methods/classes you want filtered.
#Authorize
public Response getUser() {
// Do something
}
#Provider
#Authorize
#Priority(Priorities.AUTHORIZATION)
public class AuthorizationRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
MultivauledMap<String, String> headers - requestContext.getHeaders();
...
if (!authorized) {
throw new NotAuthorizedException();
}
}
}
Notice the use of #Priority. This is important. Say you want the authenticate also, so you create a filter for authentication. If you don't set the priority, either filter may occur first. It's unpredictable. If we provide the authentication filter with #Priority(Priorities.AUTHENTICATION), then that filter will always occur before the #Priority(Priorities.AUTHORIZATION) filter.
You will also need to register this filter with the Application subclass (See some other Deployment Options (Jersey, but the Application subclass is portable with implementations))
#ApplicationPath("/api")
public class YourApplication extends Application {
private Set<Class<?>> classes = new HashSet<>();
private Set<Object> singletons = new HashSet<>();
public YourApplication() {
classes.add(AuthorizationRequestFilter.class);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
#Override
public Set<Object> singletons() {
return singletons;
}
}
See more on Filters and Interceptors
See the WebAppplicationException Hierarchy for more exceptions like NotAuthorizedException
See the Priorities class and Priories guide
The best way to solve your use case would be to use name binding and filters. In this way, you can use a filter to do your authorization logic as well as return a 401 in the case of unauthorized requests.
You can find more information here.
Name binding via annotations is only supported as part of the Server API. In name binding, a name-binding annotation is first defined using the #NameBinding meta-annotation:
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(value = RetentionPolicy.RUNTIME)
#NameBinding
public #interface Logged { }
The defined name-binding annotation is then used to decorate a filter or interceptor class (more than one filter or interceptor may be decorated with the same name-binding annotation):
#Logged
public class LoggingFilter
implements ContainerRequestFilter, ContainerResponseFilter {
...
}
At last, the name-binding annotation is applied to the resource method(s) to which the name-bound JAX-RS provider(s) should be bound to:
#Path("/")
public class MyResourceClass {
#GET
#Produces("text/plain")
#Path("{name}")
#Logged
public String hello(#PathParam("name") String name) {
return "Hello " + name;
}
}
A name-binding annotation may also be attached to a custom JAX-RS Application subclass. In such case a name-bound JAX-RS provider bound by the annotation will be applied to all resource and sub-resource methods in the JAX-RS application:
#Logged
#ApplicationPath("myApp")
public class MyApplication extends javax.ws.rs.core.Application {
...
}
Based on peeskillet's answer, which the concept is right but the code is slightly wrong, this is the final code for the answer.
Using #NameBinding, this works:
Authorize.java
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface Authorize {
}
AuthorizeFilter.java
note: still needs to do the actual token authorization. This is just checking if the token exists right now.
#Provider
#Authorize
#Priority(Priorities.AUTHORIZATION)
public class AuthorizeFilter implements ContainerRequestFilter
{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
MultivaluedMap<String, String> headers = requestContext.getHeaders();
String token = headers.getFirst("token");
if (token == null || token.isEmpty()) {
Response.ResponseBuilder responseBuilder = Response
.status(Response.Status.UNAUTHORIZED)
.type(MediaType.APPLICATION_JSON)
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Credentials", "true")
.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD")
.header("Access-Control-Max-Age", "1209600");
requestContext.abortWith(responseBuilder.build());
}
}
}
ApplicationConfig.java
note: add the filter here so it doesn't have to be included in the web.xml
#ApplicationScoped
#ApplicationPath("/api")
public class ApplicationConfig extends Application
{
#Override
public Set<Class<?>> getClasses()
{
return getRestResourceClasses();
}
private Set<Class<?>> getRestResourceClasses()
{
Set<Class<?>> resources = new java.util.HashSet<Class<?>>();
resources.add(com.example.AuthorizeFilter.class);
resources.add(com.example.UserService.class);
return resources;
}
}
I have routes like:
GET /job$id<[0-9]+>/ controllers.Jobs.index(id)
POST /job$id<[0-9]+>/done controllers.Jobs.done(id)
POST /job$id<[0-9]+>/update controllers.Jobs.update(id)
DELETE /job$id<[0-9]+>/remove controllers.Jobs.remove(id)
and I' like to secure it. Each job has an owner. So I made
public class Secured extends Security.Authenticator {
...
}
Then I tryed to secure all my Actions by "#With()" annotatian, but I need to pass "Id" param to my secure method, so I wrote smth like:
#With(IsOwner.class)
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Owner {
long value() default 0;
}
public static class IsOwner extends Action<Owner>{
#Override
public Promise<SimpleResult> call(Context ctx) throws Throwable {
if(!isOwner(configuration.value())){
return Promise.pure(redirect(..));
}
return delegate.call(ctx);
}
}
But I can't pass my action parametr to annotation.
#Security.Authenticated(Secured.class)
#Owner(id) //**this part I want to work**
public class Jobs extends Controller {
//#Owner(id) - or at list there
public static Result index(Long Id){
//I have too many Actions and don't want to do every time this
/*if(!Secured.isOwnerMethod(Id)){
return forbidden();
}*/
return ok();
}
Other way I see is get params from Context in IsOwner.call() method...
Please, give me a recommendation or good practise for such situations.
Thanks.
I ran into this problem and came up with a Play extension that exposes all url parameters as common request parameters... You can check it out here => https://github.com/asouza/reverse-route-plugin
Basically you have to extend from my Global class.
Cheers,
Alberto
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");
}
};
}
Using Jersey 1.7, JAX-WS 2.2.3, Tomcat 6.0.30 and the following method declaration prevents Jersey servlet to start:
#POST
#Produces("text/plain")
public void postIt(#WebParam(name = "paramOne") final String paramOne,
final String paramTwo) {
// ...
}
The generated exception is:
SEVERE: Missing dependency for method public
java.lang.String com.sun.jersey.issue.MyResource.postIt(
java.lang.String,java.lang.String) at parameter at index 0
SEVERE: Method, public void
com.sun.jersey.issue.MyResource.postIt(
java.lang.String,java.lang.String),
annotated with POST of resource,
class com.sun.jersey.issue.MyResource,
is not recognized as valid resource method.
If the #WebParam annotation is removed, it all works fine.
Now, please have in mind that I am not trying to work with mere strings, rather, I am migrating complicated Objects that got marshalled/unmarshalled using SOAP to RESTful services, but I must provide both interfaces for a while, without breaking the previous WASDs. The method is just a minimalistic scenario.
Has any of you any idea of the status of this? Has it been fixed? Suggestions?
The specification is clear on this. Section 3.3.2.1 tells us that:
Resource methods MUST NOT have more
than one parameter that is not
annotated with one of the above listed
annotations.
The above listed annotations are the JAX-RS parameter annotations: #QueryParam, #MatrixParam, etc.
There is, however, a Jersey specific way to solve this problem. Using InjectableProvider. So, a method that defines two non-JAX-RS parameters:
#POST
public void postIt(#CustomInjectable final Customer customer,
final Transaction transaction) {
// ...
}
Of course, we have to code the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface CustomInjectable {
}
An implementation of InjectableProvider that knows how to provide Customers:
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import com.sun.jersey.api.model.Parameter;
#Provider
public class CustomerInjectableProvider implements
InjectableProvider<CustomInjectable, Parameter> {
// you can use #Context variables, as in any Provider/Resource
#Context
private Request request;
public ComponentScope getScope() {
// ComponentScope.Singleton, Request or Undefined
}
public Injectable getInjectable(ComponentContext i,
CustomInjectable annotation,
Parameter param) {
Injectable injectable = null;
if (Customer.getClass().isAssignableFrom(param.getParameterClass()) {
injectable = getInjectable();
}
return injectable;
}
private Injectable getInjectable() {
return new Injectable<Customer>() {
public Customer getValue() {
// parse the customer from request... or session... or whatever...
}
};
}
}
But, Jersey considers only the last annotation (see JERSEY-ISSUE-731), so be careful.
And, a more portable way (if you do care about that, anyway):
// simple bean
public class CustomerWithTransaction {
private Customer customer;
private Transaction transaction;
// getters and setters
}
Then change the method to:
#POST
public void postIt(CustomerWithTransaction customerWithTransaction) {
// ...
}
Then create your own MessageBodyReader for CustomerWithTransaction, where you can also access any context variables (request, headers, etc.).