I have a Quarkus application in which I implemented the ContainerRequestFilter interface to save a header from incoming requests:
#PreMatching
public class SecurityFilter implements ContainerRequestFilter {
private static final String HEADER_EMAIL = "HD-Email";
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String email = requestContext.getHeaders().getFirst(HEADER_EMAIL);
if (email == null) {
throw new AuthenticationFailedException("Email header is required");
}
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return () -> email;
}
#Override
public boolean isUserInRole(String role) {
return false;
}
#Override
public boolean isSecure() {
return false;
}
#Override
public String getAuthenticationScheme() {
return null;
}
});
}
}
In a class annotated with ApplicationScoped I injected the context as follows:
#ApplicationScoped
public class ProjectService {
#Context
SecurityContext context;
...
}
The problem is that the context attribute is actually never injected, as it is always null.
What am I doing wrong? What should I do to be able to retrieve the SecurityContext throughout the application's code?
I like to abstract this problem, so that the business logic does not depend on JAX-RS-specific constructs. So, I create a class to describe my user, say User, and another interface, the AuthenticationContext, that holds the current user and any other authentication-related information I need, e.g.:
public interface AuthenticationContext {
User getCurrentUser();
}
I create a RequestScoped implementation of this class, that also has the relevant setter(s):
#RequestScoped
public class AuthenticationContextImpl implements AuthenticationContext {
private User user;
#Override
public User getCurrentUser() {
return user;
}
public void setCurrentUser(User user) {
this.user = user;
}
}
Now, I inject this bean and the JAX-RS SecurityContext in a filter, that knows how to create the User and set it into my application-specific AuthenticationContext:
#PreMatching
public class SecurityFilter implements ContainerRequestFilter {
#Inject AuthenticationContextImpl authCtx; // Injecting the implementation,
// not the interface!!!
#Context SecurityContext securityCtx;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
User user = ...// translate the securityCtx into a User
authCtx.setCurrentUser(user);
}
}
And then, any business bean that needs the user data, injects the environment-neutral, application-specific AuthenticationContext.
#Context can only be used in JAX-RS classes - i.e. classes annotated with #Path.
In your case, ProjectService is a CDI bean, not a JAX-RS class.
The canonical way to do what you want is to inject the SecurityContext into a JAX-RS resource and then pass that as a method parameter to your ProjectService
Related
I implemented a validation using the chain of responsibility pattern. The request payload to validate can have different parameters. The logic is: if the payload has some parameters, validate it and continue to validate other, else throw an exception. In a level of the validation chain I need to call other services, and here comes into play the Dependency Injection.
The validation structure is like a tree, starting from top to bottom.
So, the class where I need to start the Validation
#Service
public class ServiceImpl implements Service {
private final .....;
private final Validator validator;
public ServiceImpl(
#Qualifier("lastLevelValidator") Validator validator, .....) {
this.validator = validator;
this...........=............;
}
/...../
private void validateContext(RequestContex rc) {
Validator validation = new FirstLevelValidator(validator);
validation.validate(rc);
}
}
So the Validator Interface
public interface Validator<T> {
void validate(T object);
}
The validation classes that implements Validator
#Component
public class FirstLevelValidator implements Validator<RequestContext>{
private final Validator<RequestContext> validator;
#Autowired
public FirstLevelValidator(#Qualifier("lastLevelValidator") Validator<RequestContext> validator) {
this.validator = validator;
}
#Override
public void validate(RequestContext requestContext) {
if ( requestContext.getData() == null ) {
LOGGER.error(REQUEST_ERROR_MSG);
throw new BadRequestException(REQUEST_ERROR_MSG, INVALID_CODE);
}
if (requestContex.getData() == "Some Data") {
Validator validator = new SecondLevelValidator(this.validator);
validator.validate(requestContext);
} else {/* other */ }
}
#Component
public class SecondLevelValidator implements Validator<RequestContext>{
private final Validator<RequestContext> validator;
#Autowired
public SecondLevelValidator(#Qualifier("lastLevelValidator") Validator<RequestContext> validator) {
this.validator = validator;
}
#Override
public void validate(RequestContext requestContext) {
if ( requestContext.getOption() == null ) {
LOGGER.error(REQUEST_ERROR_MSG);
throw new BadRequestException(REQUEST_ERROR_MSG, INVALID_CODE);
}
if ( requestContext.getOption() == " SOME " ) {
validator.validate(requestContext); //HERE WHERE I CALL THE Qualifier
}
}
#Component
public class LastLevelValidator implements Validator<RequestContext>{
private final ClientService1 client1;
private final ClientService2 client2;
public LastLevelValidator(ClientService1 client1, ClientService2 client2) {
this.client1 = client1;
this.client2 = client2;
}
#Override
public void validate(RequestContext requestContext) {
Integer userId = client2.getId()
List<ClientService1Response> list = client1.call(requestContext.id(), userId);
boolean isIdListValid = list
.stream()
.map(clientService1Response -> clientService1Response.getId())
.collect(Collectors.toSet()).containsAll(requestContext.getListId());
if (!isIdListValid) {
LOGGER.error(NOT_FOUND);
throw new BadRequestException(NOT_FOUND, INVALID_CODE);
} else { LOGGER.info("Context List validated"); }
}
}
In the LastLevelValidator I need to call other services to make the validation, for that I inject into each validator class (First.., Second..) the #Qualifier("lastLevelValidator") object, so when I need to instantiate the LastLevelValidation class I can call it like validator.validate(requestContext); instance of validator.validate(ClientService1, ClientService2 ) that it would force me to propagate the ClientServices objects through all the chain from the ServiceImpl class.
Is it this a good solution ?
Is there any concern I didn't evaluate?
I tried also declaring the services I need to call for the validation as static in the LastLevelValidation, in the way that I can call it like LastLevelValidation.methodvalidar(), but look like not a good practice declares static objects.
I tried to pass the objects I need propagating it for each Validation class, but seems to me that if I need another object for the validation I have to pass it through all the validation chain.
I have a quite simple quarkus extension which defines a ContainerRequestFilter to filter authentication and add data to a custom AuthenticationContext.
Here is my code:
runtime/AuthenticationContext.java
public interface AuthenticationContext {
User getCurrentUser();
}
runtime/AuthenticationContextImpl.java
#RequestScoped
public class AuthenticationContextImpl implements AuthenticationContext {
private User user;
#Override
public User getCurrentUser() {
return user;
}
public void setCurrentUser(User user) {
this.user = user;
}
}
runtime/MyFilter.java
#ApplicationScoped
public class MyFilter implements ContainerRequestFilter {
#Inject
AuthenticationContextImpl authCtx;
#Override
public void filter(ContainerRequestContext requestContext){
// doing some stuff like retrieving the user from the request Context
// ...
authCtx.setCurrentUser(retrievedUser)
}
}
deployment/MyProcessor.java:
class MyProcessor {
//... Some stuff
#BuildStep
AdditionalBeanBuildItem createContext() {
return new AdditionalBeanBuildItem(AuthenticationContextImpl.class);
}
}
I have a Null Pointer Exception in authCtx.setCurrentUser(retrievedUser) call (authCtx is never injected)
What am I missing here ?
Thanks
Indexing the runtime module of the extension fixes the problem.
There are multiple ways to do that as mentioned in https://stackoverflow.com/a/55513723/2504224
i'm trying to observes a #SessionScoped component after change any property. HttpSessionAttributeListener not fire changes in cdi managed components.
#SuppressWarnings("serial")
#SessionScoped
public class TestSession implements Serializable {
private User user;
public TestSession() {
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
An example Servlet:
#SuppressWarnings("serial")
#WebServlet(name = "demo", urlPatterns = "/demo")
public class DemoServlet extends HttpServlet {
private static final Logger logger = LoggerFactory.getLogger(DemoServlet.class);
#Inject
private TestSession testSession;
#Override
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws ServletException, IOException {
User user = new User(1L,new Role(1L));
user.setId(RandomUtils.nextLong());
testSession.setUser(user); //listen that component change something
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsp/demo.jsp");
dispatcher.forward(httpServletRequest, httpServletResponse);
}
}
Is the a way to listen when component change any attribute ? Anyone has some approach to do that ?
Important : I dont have access to rewrite or add code on TestSession java class or servlet .
You can place an interceptor in the setUser() method that creates an event then catch it.
I'm new here even though I've found many answers to my problems in here before.
Now I'm looking for help with this: I have this little example resource on my little REST API:
#Path("/greeting")
#PermitAll
public class HelloResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
#Path("all")
public String sayHelloToAll() {
return "Hello, everybody!";
}
#GET
#Produces(MediaType.TEXT_PLAIN)
#RolesAllowed("admin")
#Path("admin")
public String sayHelloToAdmin() {
return "Hello, admin!";
}
}
In order to filter roles, I have this implementation of SecurityContext:
public class Authorizer implements SecurityContext {
#Override
public String getAuthenticationScheme() {
return null;
}
#Override
public Principal getUserPrincipal() {
return null;
}
#Override
public boolean isSecure() {
return false;
}
#Override
public boolean isUserInRole(String role) {
return true;
}
}
And this implementation of ContainerRequestFilter:
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthorizationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestContext.setSecurityContext(new Authorizer());
}
}
This is my application class:
#ApplicationPath("/")
public class Application extends ResourceConfig {
public Application() {
super(HelloResource.class);
register(AuthorizationFilter.class);
register(RolesAllowedDynamicFeature.class);
}
}
With all this, when I request the URI greeting/all, everything is ok, the string "Hello, everybody!" is shown. But when I request the URI greeting/admin, which should be called when an user in admin role requests it, is never invoked, even when my isUserInRole method always returns true. In fact, my filter method is always called, but my isUserInRole method is never called.
I have followed many advices:
SecurityContext doesn't work with #RolesAllowed
Authorization with RolesAllowedDynamicFeature and Jersey
How to access Jersey resource secured by #RolesAllowed
Best practice for REST token-based authentication with JAX-RS and Jersey
But it doesn't seem to work with anything.
Can anyone please help me? I don't know is there is something I am missing
Thank you all in advance.
EDIT: When I request the URI greeting/admin I get 403 Forbiden by the way (I forgot to say that)
Take a look at the source code for the RoleAllowedRequestFilter. When a user is authenticated, it is expected that there be an associated Principal. The filter checks it here
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
throw new ForbiddenException(LocalizationMessages.USER_NOT_AUTHORIZED());
}
...
private static boolean isAuthenticated(final ContainerRequestContext requestContext) {
return requestContext.getSecurityContext().getUserPrincipal() != null;
}
So you need to return a Principal in the getUserPrincipal of the SecurityContext
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return "Some Name";
}
};
}
I am developing a REST API using Jersey 2 and at the moment I am trying to incorporate basic authentication by use of an annotation similar to the #Auth found in Dropwizard. With
#Path("hello")
public class HelloResource {
#GET
#Produces("application/json")
public String hello(#Auth final Principal principal) {
return principal.getUsername();
}
}
the hello resource invocation should be intercepted by some code performing basic authentication using the credentials passed in the Authorization HTTP request header and on success injecting the principal into the method principal parameter.
I have started creating an #Auth resolver, see below, but I do not see how I can access the Authorization HTTP request header from within that?
#Singleton
public class AuthResolver {
public static class AuthInjectionResolver extends ParamInjectionResolver<Auth> {
public AuthInjectionResolver() {
super(AuthValueFactoryProvider.class);
}
}
#Singleton
public static class AuthValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public AuthValueFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider, final ServiceLocator injector) {
super(extractorProvider, injector, UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
final Class<?> classType = parameter.getRawType();
return classType == null || !classType.equals(Principal.class) ? null :
new AbstractContainerRequestValueFactory<Principal>() {
#Override
public Principal provide() {
// Authentication?
}
};
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(AuthValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(AuthInjectionResolver.class).to(
new TypeLiteral<InjectionResolver<Auth>>() {
}
).in(Singleton.class);
}
}
}
How to approach this? :)
Ah, in AbstractContainerRequestValueFactory<Principal> I can add
#Context private ResourceContext context;
and then extract the HTTP request and it's headers from there inside the provide method.