I wanted to use a interceptor(custom annotation) inside a jax-rs service.
1.First,I wrote an annotation class:
BasicAuthentication.java:
#NameBinding
#Target( {ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface BasicAuthentication {
}
2.Then I added the BasicAuthenticationInterceptor implements javax.ws.rs.ext.ReaderInterceptor
BasicAuthenticationInterceptor.java:
#Provider
#Priority(Priorities.AUTHENTICATION)
#BasicAuthentication
public class BasicAuthenticationInterceptor extends Dumpable implements ReaderInterceptor {
#Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
//log.info("authentication here")
String authHeader = context.getHeaders().getFirst(AUTHORIZATION);
if (authHeader == null) {
error("\"authorization\" is not found from the request header.");
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
return context.proceed();
}
}
3.At last,I add a test service with annotation #BasicAuthentication.
TestRestfulService.java
#Stateless
#Path("/api")
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
#BasicAuthentication
public class TestRestfulService extends Dumpable{
#EJB
LocalService localService;
#Path("/test/{id}")
#GET
public Response test(#PathParam("id")String id) {
try {
localService.findUser(id);
} catch (Exception e) {
error(e);
return Response.serverError().build();
}
return Response.ok().build();
}
}
But every time I request /api/test/1 with empty header,I can get the correct response,the interceptor seems not work at all.
I'm using Wildfly 10.
Thanks in advance.
Finally I worked out.Change the Interceptor to filter:
public class BasicAuthenticationInterceptor implements javax.ws.rs.container.ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext context){
...
}
}
Then it works as expected.
Form JAX-WS API:
Interface for message body reader interceptors that wrap around calls to MessageBodyReader.readFrom(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.InputStream).
javax.ws.rs.ReaderInterceptor
This could be the reason.
Related
I'm not sure whether this is possible or not but I'm trying to setup a EJB + JAX-RS (Jersey) test project and use the #RolesAllowed annotation.
I'm currently getting the following error logs:
Warning: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception
Severe: ejb.stateless_ejbcreate_exception
Warning: A system exception occurred during an invocation on EJB TestSB, method: public java.util.List rest.sb.TestSB.findAll()
Warning: javax.ejb.EJBException: javax.ejb.EJBException: javax.ejb.CreateException: Could not create stateless EJB
Relevant classes:
ApplicationConfig.java
#ApplicationPath("rest")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("rest");
register(RolesAllowedDynamicFeature.class);
}
}
TestSBFacade.java
#Local
public interface TestSBFacade {
public List<Test> findAll();
}
TestSB.java
#Stateless
#Path("secured/test")
public class TestSB implements TestSBFacade {
#DAO #Inject
private TestDAOFacade dao;
#Context
SecurityContext securityContext;
#Secured
#RolesAllowed({"READ"})
#Path("all")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Override
public List<Test> findAll() {
//this works without the #RolesAllowed so it is a possible workaroud for now.
System.out.println(securityContext.isUserInRole("READ")); //output: true
return dao.findAll();
}
}
AuthFilter.java
#Provider
#Secured //NameBinding
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String token = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
try {
verifyToken();
createSecurityContext();
} catch (Exception e) {
Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, "Invalid or Expired JWT");
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
}
My SecurityContext is set and working, the #RolesAllowed seems to be to problem since I get no errors if I remove it and a JSON is properly returned to the front-end. Keeping the #RolesAllowed results in the errors mentioned at the start.
However I'd like to use the handy annotation instead of embedding every method inside isUserInRole IF Statements. Any help and insights are much appreciated.
So apparently due to both EJB and JAX-RS implementations using#RolesAllowed they don't do well together. So I decided to create my own Annotation instead and register my own DynamicFeature in the ApplicationConfig.java.
Authorized.java
#Documented
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Authorized {
public String[] value() default "";
}
AuthorizationDynamicFeature.java
public class AuthorizationDynamicFeature implements DynamicFeature {
#Override
public void configure(final ResourceInfo resourceInfo, final FeatureContext featureContext) {
Authorized auth = new AnnotatedMethod(resourceInfo.getResourceMethod()).getAnnotation(Authorized.class);
if (auth != null) {
featureContext.register(new AuthorizationRequestFilter(auth.value()));
}
}
#Priority(Priorities.AUTHORIZATION)
private static class AuthorizationRequestFilter implements ContainerRequestFilter {
private final String[] roles;
AuthorizationRequestFilter() {
this.roles = null;
}
AuthorizationRequestFilter(final String[] roles) {
this.roles = roles;
}
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
if (!this.roles[0].isEmpty()) {
for (final String role : this.roles) {
if (requestContext.getSecurityContext().isUserInRole(role)) {
return;
}
}
throw new ForbiddenException(LocalizationMessages.USER_NOT_AUTHORIZED());
}
}
}
}
Huge thanks to #PaulSamsotha for leading me to a more suitable solution.
I am trying to create a filter for a REST API I have developed following these question Best practice for REST token-based authentication with JAX-RS and Jersey.
The problem is whatever of the methods I am invoking the filter doesnt appear to work.
These are my classes:
Secured.java
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface Secured {
}
AuthenticationFilter.java
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the HTTP Authorization header from the request
String authorizationHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Check if the HTTP Authorization header is present and formatted correctly
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
throw new NotAuthorizedException("Authorization header must be provided");
}
// Extract the token from the HTTP Authorization header
String token = authorizationHeader.substring("Bearer".length()).trim();
try {
// Validate the token
validateToken(token);
} catch (Exception e) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private void validateToken(String token) throws Exception {
// Check if it was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
}
RestService.java
#Path("/test")
public class RestService {
TestDAO testDAO;
#GET
#Secured
#Path("/myservice")
#Produces("application/json")
public List<Test> getEverisTests() {
testDAO=(TestDAO) SpringApplicationContext.getBean("testDAO");
long start = System.currentTimeMillis();
List<Test> ret = testDAO.getTests();
long end = System.currentTimeMillis();
System.out.println("TIEMPO TOTAL: " + (end -start));
return ret;
}
}
RestApplication.java
public class RestApplication extends Application{
private Set<Object> singletons = new HashSet<Object>();
public RestApplication() {
singletons.add(new RestService());
singletons.add(new AuthenticationFilter());
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
I am missing something? Thanks in advance.
Your AuthenticationFilter may not be registered.
It's very likely you have an Application subclass somewhere in your application. Use it to register the filter:
#ApplicationPath("api")
public class ApiConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> classes = new HashSet<>();
classes.add(AuthenticationFilter.class);
...
return classes;
}
}
The solution was to update Jboss modules of resteasy following this page resteasy and selecting the version of resteasy that I was using.
Thanks for the answers by the way!
I can't yet comment so this goes into an answer:
I don't understand how the #Secured mechanism works. Did you try to remove all #Secured annotations? The filter should then be active for all endpoints.
If it still does not work most probably you will have to register it manually in your application.
If it does work afterwards you have at least a hint on where to look for the problem ...
Using Dropwizard 0.9.1 I have created a custom AuthFilter to check session cookie as below:
Priority(Priorities.AUTHENTICATION)
public class SessionAuthFilter extends AuthFilter<String /*session key*/, SessionUser /*principal*/> {
private SessionAuthFilter() {
}
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Cookie sessionKey = requestContext.getCookies().get("sessionKey");
if (sessionKey != null) {
try {
Optional<SessionUser> principal = new SessionAuthenticator().authenticate(sessionKey.getValue());
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return principal.get();
}
#Override
public boolean isUserInRole(String role) {
return false;
}
#Override
public boolean isSecure() {
return requestContext.getSecurityContext().isSecure();
}
#Override
public String getAuthenticationScheme() {
return SecurityContext.FORM_AUTH;
}
});
return;
} catch (AuthenticationException e) {
throw new InternalServerErrorException(e.getMessage(), e);
}
}
throw new NotAuthorizedException("Please log in!", "realm="+realm);
}
And registered it as below:
environment.jersey().register(new AuthDynamicFeature(new SessionAuthFilter.Builder().setAuthenticator(new
SessionAuthenticator()).setRealm("Login").buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
The problem is I can not use #Permitall annotation on class level in Resource classes. It works fine If I use on method, but not filtering on class.
Resource class:
#Path("/")
#PermitAll //Doesn't work here
#Produces(MediaType.APPLICATION_JSON)
public class HomeResource {
#GET
#PermitAll //Works fine if here
#Path("/about")
public Response get() {
}
}
Any idea anyone?
Authz annotations at the class level is not supported in DW 9.x. You can see in the source code of AuthDynamicFeature, only method level annotations are checked, ultimately only registering the auth filter to methods with the Authz annotations.
This limitiation has been fixed in this pull request (to 1.0.0), where #RolesAllowed and #PermitAll at the class level will be supported.
I am trying to use CDI-like interceptors with EJB on Wildfly 8.2, but they are not invoked in any EJB. However, they are working just fine with CDI objects.
#InterceptorBinding
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface DataTransferObject {
}
Interceptor to clone entity to DTO as return;
#Interceptor
#DataTransferObject
public class DataTransferObjectInterceptor {
#AroundInvoke
public Object clone(InvocationContext invocationContext) throws Exception {
Object actual = invocationContext.proceed();
Object clone = actual.getClass().newInstance();
BeanUtil.clone(actual, clone);
return clone;
}
}
The interceptor doesn't catch this;
#Stateless
#DataTransferObject
public class BaseCompanyService implements CompanyService {
#EJB
private CompanyDAO companyDAO;
#Override
public void create(Company entity) throws EntityException {
companyDAO.create(entity);
}
.
.
}
But it is working OK here;
#Path("/company")
#Produces(MediaType.APPLICATION_JSON)
#DataTransferObject
#Slf4j
public class CompanyResource implements Resource {
#EJB
private CompanyService companyService;
#GET
#Path("/check")
#Override
public Success check() {
return new Success("The company service is running.");
}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public Success create(#Valid Company entity) throws EntityException {
companyService.create(entity);
log.info("The company with id \"{}\" is successfully created.", entity.getId());
return new Success("The company is successfully created.");
}
.
.
}
beans.xml
<interceptors>
<class>io.rraa.interceptors.DataTransferObjectInterceptor</class>
</interceptors>
Trying to build a RESTful web service using Spring MVC.
The controller should return specific Java types, but the response body must be a generic envelope. How can this be done?
The following sections of code are what I have so far:
Controller method:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyController {
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Response envelope:
public class Response<T> {
private String message;
private T responseBody;
}
ServiceDetails code:
public class ServiceDetails {
private String serviceName;
public ServiceDetails(String serviceName) {
this.serviceName = serviceName;
}
}
Intended final response to clients should appear as:
{
"message" : "Operation OK"
"responseBody" : {
"serviceName" : "MyService"
}
}
What you can do is having a MyRestController just wrapping the result in a Response like this:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyRestController {
#Autowired
private MyController myController;
#RequestMapping(value = "/details")
public #ResponseBody Response<ServiceDetails> getServiceDetails() {
return new Response(myController.getServiceDetails(),"Operation OK");
}
}
This solution keep your original MyController independant from your REST code. It seems you need to include Jackson in your classpath so that Spring will auto-magically serialize to JSON (see this for details)
EDIT
It seems you need something more generic... so here is a suggestion.
#Controller
#RequestMapping(value = "/mycontroller")
public class MyGenericRestController {
#Autowired
private MyController myController;
//this will match all "/myController/*"
#RequestMapping(value = "/{operation}")
public #ResponseBody Response getGenericOperation(String #PathVariable operation) {
Method operationToInvoke = findMethodWithRequestMapping(operation);
Object responseBody = null;
try{
responseBody = operationToInvoke.invoke(myController);
}catch(Exception e){
e.printStackTrace();
return new Response(null,"operation failed");
}
return new Response(responseBody ,"Operation OK");
}
private Method findMethodWithRequestMapping(String operation){
//TODO
//This method will use reflection to find a method annotated
//#RequestMapping(value=<operation>)
//in myController
return ...
}
}
And keep your original "myController" almost as it was:
#Controller
public class MyController {
//this method is not expected to be called directly by spring MVC
#RequestMapping(value = "/details")
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Major issue with this : the #RequestMapping in MyController need probably to be replaced by some custom annotation (and adapt findMethodWithRequestMapping to perform introspection on this custom annotation).
By default, Spring MVC uses org.springframework.http.converter.json.MappingJacksonHttpMessageConverter to serialize/deserialize JSON through Jackson.
I'm not sure if it's a great idea, but one way of solving your problem is to extend this class, and override the writeInternal method:
public class CustomMappingJacksonHttpMessageConverter extends MappingJacksonHttpMessageConverter {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
super.writeInternal(new Response(object, "Operation OK"), outputMessage);
}
}
If you're using XML configuration, you could enable the custom converter like this:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="path.to.CustomMappingJacksonHttpMessageConverter">
</mvc:message-converters>
</mvc:annotation-driven>
Try the below solution.
Create a separate class such ResponseEnvelop. It must implement ResponseBodyAdvice interface.
Annotate the above class with #ControllerAdvice
Autowire HttpServletRequest
Override methods according to your requirement. Take reference from below.
#Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (httpServletRequest.getRequestURI().startsWith("/api")) {
return true;
}
return false;
}
#Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.OK.value()
|| ((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.CREATED.value()) {
return new EntityResponse(Constants.SUCCESS, body);
}
return body;
}