I want to do my own implementation of Bearer-only authentication in Wildfly. In essence, I will do the following steps:
When I receive a request, I will check if it has an Authorization header.
I obtain the token and check against a database (in this case I will be using Redis) for the validity of it.
I obtain the role for that user from the database.
I want to be able to use the #RolesAllowed annotation on my rest services.
How do I go about it? How do I need to modify the Wildfly configuration files? What interfaces do I need to implement? How can I pass the role of the user to the security context so that Wildfly does the #RolesAllowed check for me?
If answering, consider that I am an experienced Java Programmer, but new to Wildfly, so you can skip details on programming logic but not on Wildfly configuration. Also in your answer don't worry on how the token got to Redis in the first place, or how the client obtained it.
EDIT
This is what I have done, but with no luck yet. I have implemented an AuthenticationFilter that implements ContainerRequestFilter. (I am including below only the main filter function that I have implemented. Note that there are some helper functions that get the roles from the database that are not included). Even when, at the end of the function I set the security context of the request context with the user profile (which contains the role), I cannot get to work the #RolesAllowed annotations on my JAX-RS rest services. Any pointers on what should I do?
Note: I have not modified any Wildfly configuration files or the web.xml file. I know that the filter is being called for every request because I am able to LOG messages from it on every request.
/**
* (non-Javadoc)
* #see javax.ws.rs.container.ContainerRequestFilter#filter(javax.ws.rs.container.ContainerRequestContext)
*/
#Override
public void filter(ContainerRequestContext requestContext) {
//1. Read the JSON web token from the header
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
return;
}
String token = authorizationHeader.substring("Bearer".length()).trim();
try{
//Note that if the token is not in the database,
//an exception will be thrown and we abort.
UserProfile userProfile = this.getUserProfile(token);
if (null == userProfile){
userProfile = this.decodeToken(token);
}
if (null == userProfile){
throw new Exception();
}
String role = userProfile.getUserRole();
if (null == role){
role = this.getRoleFromMod(userProfile);
if (null == role){
role = RoleType.READ_ONLY;
}
userProfile.setUserRole(role);
this.updateUserProfileForToken(token, userProfile);
}
userProfile.setUserRole(role);
//5. Create a security context class that implements the crazy interface
//and set it here.
requestContext.setSecurityContext(new ModSecurityContext(userProfile));
}
catch(Exception e){
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
Yeah I am not sure how it would work in an EE environment, even making the resource class an stateless bean. The #RolesAllowed annotation is meant to be used for ejbs. In which case the principal is retrieved from the servlet request (I believe). What I would do is just implements your own authorization filter that looks up the annotation and checks against the principal in the security context.
You can see how Jersey implements it. Nothing is really Jersey specific about it except the AnnotatedMethod class. For that you can just do some reflection with java.lang.reflect.Method (resourceInfo.getResourceMethod()) instead. Other than that, you can pretty much copy the code as is. Once you're done, just register the RolesAllowedDynamicFeature with the application. Or just annotate it with #Provider to be scanned for.
You will also need to make sure your authentication filter is annotated with #Priority(Priorities.AUTHENTICATION) so that it is called before the authorization filter, which is annotated with #Priority(Priorities.AUTHORIZATION).
UPDATE
Here is a refactor of the the code I linked to, so It doesn't use an Jersey specific classes. The AnnotatedMethod is just changed to Method.
#Provider
public class RolesAllowedFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
Method resourceMethod = resourceInfo.getResourceMethod();
if (resourceMethod.isAnnotationPresent(DenyAll.class)) {
configuration.register(new RolesAllowedRequestFilter());
return;
}
RolesAllowed ra = resourceMethod.getAnnotation(RolesAllowed.class);
if (ra != null) {
configuration.register(new RolesAllowedRequestFilter(ra.value()));
return;
}
if (resourceMethod.isAnnotationPresent(PermitAll.class)) {
return;
}
ra = resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
if (ra != null) {
configuration.register(new RolesAllowedRequestFilter(ra.value()));
}
}
#Priority(Priorities.AUTHORIZATION) // authorization filter - should go after any authentication filters
private static class RolesAllowedRequestFilter implements ContainerRequestFilter {
private final boolean denyAll;
private final String[] rolesAllowed;
RolesAllowedRequestFilter() {
this.denyAll = true;
this.rolesAllowed = null;
}
RolesAllowedRequestFilter(final String[] rolesAllowed) {
this.denyAll = false;
this.rolesAllowed = (rolesAllowed != null) ? rolesAllowed : new String[]{};
}
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
if (!denyAll) {
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
throw new ForbiddenException("Not Authorized");
}
for (final String role : rolesAllowed) {
if (requestContext.getSecurityContext().isUserInRole(role)) {
return;
}
}
}
throw new ForbiddenException("Not Authorized");
}
private static boolean isAuthenticated(final ContainerRequestContext requestContext) {
return requestContext.getSecurityContext().getUserPrincipal() != null;
}
}
}
First let me explain a bit about how the DynamicFeature works. For that let's first change the context of discussion to your current implementation of your AuthenticationFilter.
Right now it is a filter that is processed for every request. But let's say we introduced a custom #Authenticated annotation
#Target({METHOD, TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Authenticated{}
We could use this annotation to annotate different methods and classes. To make it so that only the methods and classes annotated are filtered by the filter, we can introduce a DynamicFeature that checks for the annotation, then only register the filter when the annotation is found. For example
#Provider
public class AuthenticationDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
if (resourceInfo.getResourceMethod().isAnnotationPresent(Authenticated.class)) {
configuration.register(new AuthenticationFilter());
return;
}
if (resourceInfo.getResourceClass().isAnnotationPresent(Authenticated.class)) {
configuration.register(new AuthenticationFilter());
}
}
}
Once we register this AuthenticationDynamicFeature class, it will make it so that only methods and classes annotated with #Authenticated will be filtered.
Alternatively, this can even be done within the filter. We can get a reference to the ResourceInfo from within the AuthenticationFilter. For example check for the annotation, if is not there, then move on.
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext context) throws IOException {
boolean hasAnnotation = false;
if (resourceInfo.getResourceMethod().isAnnotationPresent(Authenticated.class)
|| resourceInfo.getResourceClass().isAnnotationPresent(Authenticated.class)) {
hasAnnotation = true;
}
if (!hasAnnotation) return;
// process authentication is annotation is present
This way we could completely forget about the DynamicFeature. It's better to just use the DynamicFeature, I was just giving an example for demonstration purposes.
But that being said, if we look at the first block of code with the RolesAllowedDynamicFeature, you can better understand what is going on. It only registers the filter for methods and classes annotated with #RolesAllowed and #DenyAll. You could even refactor it to have all the annotation logic in the filter instead of the feature. You only have the filter. Just like I did with the AuthenticationFilter example above. Again this would be just for example purposes.
Now as far as the registration of the DynamicFeature, it works the same way as registering any other resource class or provider class (e.g. your authentication filter). So however you register those, just register the RolesAllowedDynamicFeature the same way. There is scanning, where #Path and #Provider annotations are scanned for. If this is what you are current using, then just annotating the feature class with #Provider should register it. For example just having an empty Application subclass will cause scanning to happen
#ApplicationPath("/api")
public class RestApplication extends Application {}
Then there is explicit registration in your Application subclass. For example
#ApplicationPath("/api")
public class RestApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(AuthenticationFilter.class);
classes.add(RolesAllowedFeature.class);
classes.add(SomeResource.class);
return classes;
}
}
Note that when doing this, you disable any scanning that goes on.
So a couple other things to make sure after all the above is clear it still isn't working.
Make sure your current AuthenticationFilter is annotated with #Priority(Priorities.AUTHENTICATION). This is to ensure that your authentication filter is called before the authorization filter. This needs to happen because the authentication filter is what sets the security context, and the authorization filter checks it.
Make sure you are creating the security context correctly. The authorization filter will call the SecurityContext.isUserInRole(role) passing in roles from the #RolesAllowed annotation. So you need to make sure to implements the isUserInRole correctly.
Related
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
How can I map custom/dunamic requests toa given controller, based on a repository lookup?
The use-case is a CMS-like feature in a web-platform, where certain URL patterns ("pages") stored in the DB should be handled by a separate controller PageController.java. These patterns are not necessarily known at compile-time, and they can also be added and modified while the app is deployed (thus, it cannot be annotation-driven).
I did try to map a controller to "**" (see below), but that did not work for 2 reasons: firstly all other requests resolved to that same controller method (I had hoped that it would use "**" as a fallback and try the others first), and it also ended up resolving all requests to my static/asset files to this controller (resulting in unwanted 404-responses).
#Controller
public class PageController {
#Inject
private PageService pageService;
#RequestMapping(value = "**", method = RequestMethod.GET)
public String getPage(Model model, HttpServletRequest request, #CurrentUser User user) {
String path = request.getRequestURI();
Page page = this.pageService.getByPath(path, user);
if (page == null) {
throw new NotFoundException();
}
model.addAttribute("page", page);
return "web/page";
}
}
The temporary work-around/modification to the above method has so far been to map a pre-defined URL-prefixes to this controller (eg. /page/**, /info/**, /news/** etc), but this is an inelegant solution that adds arbitrary limitations to the system which I now seek to eliminate.
I am currently using Spring Boot 2.0. In addition to the naive mapping to ** in a regular #Controller class (using the #RequestMapping -annotation), I have also tried configuring SimpleUrlHandlerMapping the following way:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Inject
private PageDao pageDao;
#Bean
public PageController pageController() {
return new PageController();
}
#Bean
public SimpleUrlHandlerMapping pageUrlHandlerMapping() {
SimpleUrlHandlerMapping pageUrlHandlerMapping = new SimpleUrlHandlerMapping();
PageController pageController = this.pageController();
Map<String, Object> urlMap = this.pageDao.findAll().stream()
.map(Page::getNormalizedSlug)
.collect(Collectors.toMap(Function.identity(),
slug -> pageController, (existing, duplicate) -> existing));
pageUrlHandlerMapping.setUrlMap(urlMap);
pageUrlHandlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE); // <- Cannot be LOWEST_PRECEDENCE for some reason...
return pageUrlHandlerMapping;
}
}
public class PageController implements Controller {
#Inject
private PageService pageService;
#Inject
private DmaWebControllerAdvice controllerAdvice;
#Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof User) {
user = (User) principal;
}
String path = request.getRequestURI();
Page page = this.pageService.getByPath(path, user);
if (page == null) {
throw new NotFoundException();
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("web/page");
modelAndView.addObject("page", page);
controllerAdvice.globalModelAttributes(modelAndView.getModel(), null);
return modelAndView;
}
}
This approach does technically work, but the list of pages will somehow have to be reloaded into the SimpleUrlHandlerMapping whenever one of the pages is changed (i am not quite sure how to do that). This also possibly overwrites some default Spring Boot-configuration, that I would ideally like to keep. It also has some drawbacks compared to resolving controllers using #Controller and #RequesMapping because I currently am injecting certain data into all views resolved that way (mainly model-data used in the overall design of the website, like menu, quicklinks etc). In the above attempt, I have had to set those via a separate call to controllerAdvice-globalModelAttributes().
What I am seeking is a solution where my repository is queried for potential page-matches in runtime, and if it is valid then the request will be handled by the proper page-controller. Is a custom HandlerMapping -implementation the way to do this? And if not, how should I solve this? And if making a separate HandlerMapping for pages, how do I add/register this in my configuration without overwriting the default provided by Spring?
Why don't you just implement a catch-all controller which parses your patterns as a parameter, does a db look-up and then use a forward to specific controllers (info, page, news etc.)? Seems like for a CMS, this look-up logic belongs into your code (e.g. service layer).
Easiest(but not the best) way to achieve what you need is creating custom HandlerMapping implementation:
public class PageMapper implements HandlerMapping, Ordered {
private HandlerMethod handlerMethod;
public CustomMapper(Object controller, Method method) {
this.handlerMethod = new HandlerMethod(controller, method);
}
#Override
public HandlerExecutionChain getHandler(HttpServletRequest httpServletRequest) throws Exception {
return new HandlerExecutionChain(handlerMethod);
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; //you have to add the handler to the end
}
}
Now remove #Controller annotation from PageController because you don't need it to be detected automatically anymore. After that register controller and mapping to config:
#Configuration
public class AppWebConfig implements WebMvcConfigurer {
#Bean
public PageController pageController() {
return new PageController();
}
#Bean
public HandlerMapping pageMapping(PageController pageController) {
Method method = BeanUtils.resolveSignature("getPage", PageController.class);
return new PageMapping(pageController, method);
}
}
Now every request unrecognized by other HandlerMapping instances will be sent to your mapping hence to your controller. But this approach has obvious disadvantage. Since your mapping is the last in the chain of mappings you never get 404 error. Therefor you never know about something wrong with you resources (e.g. if some of them are missing).
I would prefer let application to distinguish paths by prefix (just like you do it already), where prefix is operation application is going to do with a page. For example if you need to show or edit the page:
#Controller
public class PageController {
private final static String SHOW = "/show";
private final static String EDIT = "/edit";
#Inject
private PageService pageService;
GetMapping(value = SHOW + "/**")
public String getPage(Model model, HttpServletRequest request, #CurrentUser User user) {
String path = request.getRequestURI().substring(SHOW.length());
Page page = this.pageService.getByPath(path, user);
...
model.addAttribute("page", page);
return "web/page";
}
//the same for EDIT operation
}
I've used jersey to create webservices. I've created request filter using ContainerRequestFilter. I've gone through Jersey Request Filter only on certain URI question but I want to exclude filter for some urls only.
#Provider
public class AuthFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// business logic
}
}
Name binding filters
Instead of excluding URIs from a global filter, you could consider using a name binding filter to select the endpoints your filter will be bound to.
Also check this answer for some examples with name binding filters.
Global filters
If you are still happy with the global filter approach, you could consider using the UriInfo interface to get details about the requested URI. Use one of the following approaches to get an instance of UriInfo:
Using the #Context annotation:
#Provider
public class AuthFilter implements ContainerRequestFilter {
#Context
private UriInfo info;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
...
}
}
Getting it from the ContainerRequestContext:
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
UriInfo info = requestContext.getUriInfo();
...
}
Once you have the UriInfo instance, you'll have access to a bunch of methods that may be useful:
getAbsolutePath(): Get the absolute path of the request.
getBaseUri(): Get the base URI of the application.
getMatchedResources(): Get a read-only list of the currently matched resource class instances.
getMatchedURIs(): Get a read-only list of URIs for matched resources.
getPath(): Get the path of the current request relative to the base URI as a string.
getPathSegments(): Get the path of the current request relative to the base URI as a list of PathSegment.
getRequestUri(): Get the absolute request URI including any query parameters.
relativize(URI): Relativize a URI with respect to the current request URI.
resolve(URI): Resolve a relative URI with respect to the base URI of the application.
For more details, check the UriInfo documentation.
If the requested URI does not match the URIs you want to apply the filter to, simply use a return instruction:
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
UriInfo info = requestContext.getUriInfo();
if (!info.getPath().contains("secured")) {
return;
}
}
Dynamic binding
Another approach is dynamic binding. It allows you to assign filters and interceptors to the resource methods in a dynamic manner. Name binding, mentioned above, uses a static approach and changes to binding require source code change and recompilation. With dynamic binding you can implement code which defines bindings during the application initialization time.
The following example extracted from the Jersey documentation shows how to implement dynamic binding:
#Path("helloworld")
public class HelloWorldResource {
#GET
#Produces("text/plain")
public String getHello() {
return "Hello World!";
}
#GET
#Path("too-much-data")
public String getVeryLongString() {
String str = ... // very long string
return str;
}
}
// This dynamic binding provider registers GZIPWriterInterceptor
// only for HelloWorldResource and methods that contain
// "VeryLongString" in their name. It will be executed during
// application initialization phase.
public class CompressionDynamicBinding implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (HelloWorldResource.class.equals(resourceInfo.getResourceClass())
&& resourceInfo.getResourceMethod().getName().contains("VeryLongString")) {
context.register(GZIPWriterInterceptor.class);
}
}
}
The binding is done using the provider which implements the DynamicFeature interface. The interface defines one configure method with two arguments, ResourceInfo and FeatureContext.
ResourceInfo contains information about the resource and method to which the binding can be done. The configure method will be executed once for each resource method that is defined in the application. In the example above the provider will be executed twice, once for the getHello() method and once for getVeryLongString() (once the resourceInfo will contain information about getHello() method and once it will point to getVeryLongString()).
If a dynamic binding provider wants to register any provider for the actual resource method it will do that using provided FeatureContext which extends JAX-RS Configurable API. All methods for registration of filter or interceptor classes or instances can be used. Such dynamically registered filters or interceptors will be bound only to the actual resource method. In the example above the GZIPWriterInterceptor will be bound only to the method getVeryLongString() which will cause that data will be compressed only for this method and not for the method getHello().
Note that filters and interceptors registered using dynamic binding are only additional filters run for the resource method. If there are any name bound providers or global providers they will still be executed.
For more details, check the Jersey documentation about filters and interceptors.
Using #NameBinding may be the most elegant approach, but if you just want to exclude a single resource and apply the filter on all others you have to remember putting the binding annotation on all resources. In this case you can use ContainerRequestContext.getUriInfo().getMatchedResources() to check whether the target resource has been matched. This is better than hard-coding a path that might change.
The example below will apply the filter logic on all resources but StatusResource:
public class CorsContainerRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext req) {
if (!matchesStatusResource(req)) {
// filter logic
}
}
private boolean matchesStatusResource(ContainerRequestContext req) {
List<Object> matchedResources = req.getUriInfo().getMatchedResources();
for (Object matchedResource : matchedResources) {
if (matchedResource instanceof StatusResource) {
return true;
}
}
return false;
}
}
As mentioned by others Dynamic bindings can be used instead but it is quite ugly as it is not obvious that the filter wouldn't be applied to all resources.
Probably you can check the URL pattern and abort the request, using getUri
Something like the following
public class AuthFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String path = requestContext.getUriInfo().getPath();
if(path.contains("admin")){
requestContext.abortWith(new Response());
}
}
}
https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/filters-and-interceptors.html#d0e9339
I'm using Dropwizard 0.9.2 and I want to create a resource that requires no authentication for GET and requires basic authentication for POST.
I have tried
#Path("/protectedPing")
#Produces(MediaType.TEXT_PLAIN)
public class ProtectedPing {
#GET
public String everybody() {
return "pingpong";
}
#PermitAll
#POST
public String authenticated(){
return "secret pingpong";
}
with
CachingAuthenticator<BasicCredentials, User> ca = new CachingAuthenticator<>(environment.metrics(), ldapAuthenticator, cbSpec);
AdminAuthorizer authorizer = new AdminAuthorizer();
BasicCredentialAuthFilter<User> bcaf = new BasicCredentialAuthFilter.Builder<User>().setAuthenticator(ca).setRealm("test-oauth").setAuthorizer(authorizer).buildAuthFilter();
environment.jersey().register(bcaf);
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
environment.jersey().register(new ProtectedPing());
This seems to result in all requests to "/protectedPing" requiring basic auth.
In Dropwizard 0.9.2 the documentation says to create a custom filter if I have a resource that is optionally protected. I'm assuming I need to do that, but I don't know where to start, or if that I what I actually need to do.
this is more of a jersey problem than a dropwizard problem. You can have a look here: https://jersey.java.net/documentation/latest/filters-and-interceptors.html
Essentially what you want is:
Create an annotation that indicates that you want to test for authentication (e.g. #AuthenticatePost)
Create the resource and annotate the correct method with #AuthenticatePost
Create your authentication filter (probably kind of like what you did above).
In the dynamic feature, test for the annotation to be present on the passed in resource. This will hold true for post, false for get. Then register the AuthenticationFilter directly on the resource method instead of globally on the resource.
This would be a semi-complete example of how I would solve this:
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if(resourceInfo.getResourceMethod().getAnnotation(AuthenticateMe.class) != null ) {
context.register(MyAuthFilter.class);
}
}
public class MyAuthFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// do authentication here
}
}
public #interface AuthenticateMe {
}
#Path("myPath")
public class MyResource {
#GET
public String get() {
return "get-method";
}
#POST
#AuthenticateMe
public String post() {
return "post-method";
}
}
}
Note, the DynamicFeature checks that the Authenticate Annotation is present, before registering the authentication with the feature context.
I hope that helps,
let me know if you have any questions.
I've created some JAX-RS 2.0 resources (using Jeresey 2.4 running in a Servlet container) and a filter that handles authentication and authorisation that can be selectively applied via a #NameBinding annotation. This all works great.
I would like to be able to define some parameters on this annotation (specifically, security permissions that are required to access each method/resource) that can be available to the filter at runtime to alter this behaviour.
I notice that interceptors can do this via javax.ws.rs.ext.InterceptorContext.getAnnotations() but there is no equivalent in javax.ws.rs.container.ContainerRequestContext for filters. Any ideas how this may be achieved? I would like to be able to do something like the following:
#Target({TYPE, METHOD})
#Retention(value = RetentionPolicy.RUNTIME)
#NameBinding
public #interface Secured {
String[] requiredPermissions() default {};
}
#Secured
#Priority(Priorities.AUTHENTICATION)
public class SecurityRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
// extract credentials, verify them and check that the user has required permissions, aborting if not
}
}
#Path("/userConfiguration/")
public class UserConfigurationResource {
#GET
#Produces(MediaType.APPLICATION_XML)
#Secured(requiredPermissions = {"configuration-permission"})
public Response getConfig(#Context HttpServletRequest httpServletRequest) {
// produce a response
}
}
For a non-vendor specific solution, since JAX-RS 2.0 you can use ResourceInfo:
#Secured
#Priority(Priorities.AUTHENTICATION)
public class SecurityRequestFilter implements ContainerRequestFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method method = resourceInfo.getResourceMethod();
if (method != null) {
Secured secured = method.getAnnotation(Secured.class);
...
}
}
}
You can get this information from UriInfo, particularly it's (Jersey specific) ExtendedUriInfo subinterface. To obtain an instance either invoke ContainerRequestContext#getUriInfo() and cast it
final ExtendedUriInfo extendendUriInfo = (ExtendedUriInfo) containerRequestContext.getUriInfo();
or inject it into your filter:
#Inject
private ExtendedUriInfo extendendUriInfo;
then
extendedUriInfo
.getMatchedResourceMethod()
.getInvocable()
.getHandlingMethod().getAnnotation(Secured.class);
In the second approach you can implement DynamicFeature and assign your filter only to a particular resource methods (i.e. in case the configuration of the filter is more complex, filter applies only to a couple of methods and you want to reduce the overhead, ...). Take a look at the implementation of RolesAllowedDynamicFeature which adds support for security annotations over resource methods in Jersey.