I followed the Jersey tutorials to implement a completely custom SecurityContext for my application. I created a custom ContainerRequestFilter to set the SecurityContext as follows:
package com.my.security;
#Provider
#Priority(Priorities.AUTHORIZATION)
public class SecurityRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) {
requestContext.setSecurityContext(new MySecurityContext(requestContext));
}
public static final class MySecurityContext implements SecurityContext {
private String token;
public MySecurityContext(ContainerRequestContext requestContext) {
token = requestContext.getHeaderString("token");
}
#Override
public boolean isUserInRole(String role) {
return role.equals("admin") && token.equals("token-for-admin");
}
// other methods omitted
}
}
The logic in the isUserInRole method is irrelevant, it's just a mock to make the point.
My endpoint looks something like:
package com.my.rest;
#PermitAll
#Path("/people")
public class PeopleRestService {
#RolesAllowed({"admin"})
#Path("/{email}")
#DELETE
public Response deletePerson(#PathParam("email") final String email) {
peopleService.removePerson(email);
return Response.ok().build();
}
}
Now I created a test (using JerseyTest) configured with the packages where the two classes are:
#Override
protected Application configure() {
return new ResourceConfig().packages("com.my.rest", "com.my.security");
}
If I execute the following in my test:
Response response = target("people/my#email.com")
.request().header("token", "token-for-admin").delete();
Assert.assertEquals(200, response.getStatus());
everything works fine.
However, if I execute the following:
Response response = target("people/my#email.com").request().delete();
Assert.assertEquals(403, response.getStatus());
I would expect a 403 error code because I didn't set the authentication token. However, I get a 500 error code and a Grizzly (the container used for the test) HTML response with the string "Request failed.".
If I comment out the #Provider annotation on the SecurityRequestFilter class or remove the package com.my.security from the test configuration, Jersey uses the container provided SecurityContext and correctly returns a 403 instead.
Why is this happening? Shouldn't Jersey return a 403 with a custom SecurityContext too? What am I missing?
I apologize for my dumbness. the logic in the isUserInRole method is completely relevant! I had a NPE in there that I didn't see and was causing the 500.
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 have two seperate projects, a projectApi (spring backend) and a projectUi (angular frontent).
I'm using maven-resource-plugin to combine them into one jar for production.
When I start the spring server, the connection between those two modules works fine.
Now I would like to customize the backend url path, so that a request like 'http://localhost:8088/login' looks like 'http://localhost:8088 /api/v1/login'.
I was able to do so, by adding the following entry to application.properties: spring.mvc.servlet-path=/api/v1 and modifying the base url, for calls from the ui to the api.
Since that change I'm getting a whitelabel error calling the ui (localhost:8088).
After some search, I tried to implement WebMvcConfigurer but it did not work for me. This is the reference stackoverflow link.
// Application.java
#SpringBootApplication
#EnableJpaRepositories
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// UserRestService.java
#RestController
#RequestMapping("/user")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class UserRestService extends AbstractRestService {
...
#PostMapping("/login")
public String login(#RequestBody User user) throws CustomException {
return Response.ok(userService.loginUser(user, response));
}
}
// application.properties
server.port=8088
// without that entry the post request works fine -> localhost:8088/user/login
// adding that entry and trying to call: localhost:8088/api/v1/user/login i get whitelabel error
spring.mvc.servlet.path=/api/v1
Try to add "/api/v1/" to your controller otherwise all your controllers will be prefixed by that path and you will not be able to provide other versions withing the same application.
I would prefer to program to an interface. This helps in making better use of IOC. Add the URI prefix (/api/v1/) to the interface like below. It will append this prefix to all methods offered by the interface.
// Interface for UserRestService with URI prefix mentioned using Request mapping annotation
#RequestMapping(value = "/api/certs/", produces = { "application/json" },consumes = { "application/json" })
public interface IUserRestService {
String login(User user) throws CustomException;
}
//UserRestService Class
#RestController
#RequestMapping("/user", method = RequestMethod.POST)
public class UserRestService extends AbstractRestService implements IUserRestService{
...
#PostMapping("/login")
public String login(#RequestBody User user) throws CustomException {
return Response.ok(userService.loginUser(user, response));
}
}
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.
I have a RESTful web service with an #POST annotated method that throws an exception when the request content type cannot be consumed. I would like to see what exactly went wrong. For this I would like to access all the details of the failed POST call, such as the body content. For this I created an #Provider catching NotSupportedException. I still have problems finding a way to get the desired details though. How would I do that?
The #POST annotated method:
#Path("/language")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class LanguageResource {
#POST
public Response postLanguages(Session session) {
return Response.status(Response.Status.OK)
.entity(Mock.getLanguages())
.build();
}
}
The #Provider:
#Provider
public class NotSupportedMapper implements ExceptionMapper<NotSupportedException> {
#Override
public Response toResponse(NotSupportedException exception) {
System.out.println(exception.toString());
return Response.status(Response.Status.NOT_IMPLEMENTED)
.build();
}
}
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");
}
};
}