Spring boot how to get resource method in request filter - rest

I am building a REST API with Spring Boot, and now trying to create a custom filter class where I need to access the resource method that would be invoked by the request. I need that in order to check if the method is annotated with a certain annotation, e.g.
#Component
#ApplicationScope
public class MyFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Method method = // get the target method somehow
if (method.isAnnotationPresent(MyAnnotation.class)) {
// business logic here
}
filterChain.doFilter(request, response);
}
}
With RESTEasy I would do something like this
#Context
private ResourceInfo resourceInfo;
where ResourceInfo has methods to get the resource class and method that is the target of the request. Is there a similar class in Spring Boot that would do the same job?

Related

Same QueryParams In All JAX-RS Endpoints

I have a requirement that a few QueryParams should be present in absolutely all JAX-RS endpoints of my application.
Is there a way to specify somewhere, only once, these parameters? Or do I have to repeat myself in all method endpoints?
Thank you!
I would implement a ContainerRequestFilter and handle the parameters there. You can add the result to the ContainerRequestContext:
#Provider
public class MyFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Object result = // handle the parameter
requestContext.setProperty("myParam", result);
}
}
Your implementation will of course depend on your needs.
You can inject the context into your resource classes like:
#Context
private ContainerRequestContext containerRequestContext;
See also:
Jersey 2 filter uses Container Request Context in Client Request Filter

Java EE Servlet and REST path clashing

I am trying to write a Java web application that provides both HTML and REST interface. I would like to create a servlet that would provide the HTML interface using JSP, but data should also be accessible via REST.
What I already have is something like this for the REST:
#javax.ws.rs.Path("/api/")
public class RestAPI {
... // Some methods
}
and
#WebServlet("/servlet")
public class MyServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Howdy at ");
}
}
Now, when I change the #WebServlet("/servlet") annotation to #WebServlet("/"), the servlet stops working probably due to path clash with the REST.
How can I provide REST on specific path and HTML in the root?
Thank you,
Lukas Jendele
This seems to work OK for me. What I did:
In my pom.xml, I have a dependency on org.wildfly.swarm:undertow (for Servlet API) and org.wildfly.swarm:jaxrs (for JAX-RS). And of course the Swarm Maven plugin.
For servlet, I have just this one class:
#WebServlet("/")
public class HelloServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("Hello from servlet");
}
}
For JAX-RS, I have these 2 classes:
#ApplicationPath("/api")
public class RestApplication extends Application {
}
#Path("/")
public class HelloResource {
#GET
public Response get() {
return Response.ok().entity("Hello from API").build();
}
}
To test, I run curl http://localhost:8080/ and curl http://localhost:8080/api. Results are as expected. (Maybe my example is too simple?)

Spring REST Security with Custom Filters and Multiple Authentication Providers - Target Method Not Called On Successful Authentication

I'm implementing a Spring security module for REST services. It involves 2 custom filters and 2 custom authentication providers. The application is using Spring 4.1.5.RELEASE with Spring security 4.0.0.RELEASE. No XML.
After successful authentication my custom AuthenticationSuccessHandler is invoked alright. Inside the onAuthenticationSuccess method, I simply reconstruct the original URL and forward the request. But the request never gets to the REST service. I don't get any errors, just an empty response with status 200. I checked by removing the custom filters and the service is called alright. I just cannot figure out even after calling the success handler. why the request never makes it to the target? Please help.
WebSecurityConfigurerAdapter implementation:
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(usernamePasswordAuthProvider)
.authenticationProvider(tokenAuthProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(serviceRegistrationValidatingFilter,
CustomUsernamePasswordAuthenticationFilter.class)
.addFilter(authFilter);
}
AuthenticationEntryPoint implementation:
#Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
authException.getMessage());
}
}
AuthenticationSuccessHandler implementation:
#Component
public class RestAuthenticationSuccessHandler extends
SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String path = Joiner.on("/")
.join(request.getServletPath(), request.getPathInfo())
.replaceAll("//", "/");
request.getRequestDispatcher(path).forward(request, response);
clearAuthenticationAttributes(request);
}
}
Log showing the filter chain that gets created (package names and hash codes omitted for brevity). My filters are where I want them to be.
INFO: Creating filter chain: AnyRequestMatcher, [WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, ServiceRegistrationValidatingFilter, CustomUsernamePasswordAuthenticationFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor]
So the issue turned out to be using Spring MockMvc. I needed to use forwardedUrl("forwardToUrl") for verification. The actual forward wasn't happening, as I posted above. Also content().string("expectedString") didn't work, content was coming as blank.
It appears that MockMvc is designed to test behavior, not data.
mockMvc.perform(get("/form"))
.andExpect(status().isOk())
.andExpect(content().string("expectedString"))
.andExpect(forwardedUrl("forwardToUrl"));

JEE6 REST Service #AroundInvoke Interceptor is injecting a null HttpServletRequest object

I have an #AroundInvoke REST Web Service interceptor that I would like to use for logging common data such as the class and method, the remote IP address and the response time.
Getting the class and method name is simple using the InvocationContext, and the remote IP is available via the HttpServletRequest, as long as the Rest Service being intercepted includes a #Context HttpServletRequest in its parameter list.
However some REST methods do not have a HttpServletRequest in their parameters, and I can not figure out how to get a HttpServletRequest object in these cases.
For example, the following REST web service does not have the #Context HttpServletRequest parameter
#Inject
#Default
private MemberManager memberManager;
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Member add(NewMember member) throws MemberInvalidException {
return memberManager.add(member);
}
I have tried injecting it directly into my Interceptor, but (on JBoss 6.1) it is always null...
public class RestLoggedInterceptorImpl implements Serializable {
#Context
HttpServletRequest req;
#AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
logger.info(req.getRemoteAddr()); // <- this throws NPE as req is always null
...
return ic.proceed();
I would like advice of a reliable way to access the HttpServletRequest object - or even just the Http Headers ... regardless of whether a REST service includes the parameter.
After researching the Interceptor Lifecycle in the Javadoc http://docs.oracle.com/javaee/6/api/javax/interceptor/package-summary.html I don't think its possible to access any servlet context information other than that in InvocationContext (which is defined by the parameters in the underlying REST definition.) This is because the Interceptor instance has the same lifecycle as the underlying bean, and the Servlet Request #Context must be injected into a method rather than the instance. However the Interceptor containing #AroundInvoke will not deploy if there is anything other than InvocationContext in the method signature; it does not accept additional #Context parameters.
So the only answer I can come up with to allow an Interceptor to obtain the HttpServletRequest is to modify the underlying REST method definitons to include a #Context HttpServletRequest parameter (and HttpServletResponse if required).
#Inject
#Default
private MemberManager memberManager;
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Member add(NewMember member, #Context HttpServletRequest request, #Context HttpServletResponse response) throws MemberInvalidException {
...
}
The interceptor can then iterate through the parameters in the InvocationContext to obtain the HttpServletRequest
#AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
HttpServletRequest req = getHttpServletRequest(ic);
...
return ic.proceed();
}
private HttpServletRequest getHttpServletRequest(InvocationContext ic) {
for (Object parameter : ic.getParameters()) {
if (parameter instanceof HttpServletRequest) {
return (HttpServletRequest) parameter;
}
}
// ... handle no HttpRequest object.. e.g. log an error, throw an Exception or whatever
Another work around to avoid creating additional parameters in every REST method is creating a super class for all REST services that use that kind of interceptors:
public abstract class RestService {
#Context
private HttpServletRequest httpRequest;
// Add here any other #Context fields & associated getters
public HttpServletRequest getHttpRequest() {
return httpRequest;
}
}
So the original REST service can extend it without alter any method signature:
public class AddService extends RestService{
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Member add(NewMember member) throws MemberInvalidException {
return memberManager.add(member);
}
...
}
And finally in the interceptor to recover the httpRequest:
public class RestLoggedInterceptorImpl implements Serializable {
#AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
// Recover the context field(s) from superclass:
HttpServletRequest req = ((RestService) ctx.getTarget()).getHttpRequest();
logger.info(req.getRemoteAddr()); // <- this will work now
...
return ic.proceed();
}
...
}
I'm using Glassfish 3.1.2.2 Jersey
For http header this works for me:
#Inject
#HeaderParam("Accept")
private String acceptHeader;
To get UriInfo you can do this:
#Inject
#Context
private UriInfo uriInfo;

Using GWT RPC from a GWTTestCase using Guice

I've configured my GWT app with Guice as documented here. With this setup the app works fine.
However what I'd like to do now is get a GWTTestCase to call a service using GWT RPC. To this end I've done this,
Updated my <app>JUnit.gwt.rpc so that the service URL maps to GuiceRemoteServiceServlet
Added an init() method to GuiceRemoteServiceServlet to initialise the Injector as per this comment
Unfortunately I'm still getting an error,
com.google.inject.ProvisionException: Guice provision errors:
Caused by: com.google.inject.OutOfScopeException: Cannot access scoped object. Either we are not currently inside an HTTP Servlet request, or you may have forgotten to apply com.google.inject.servlet.GuiceFilter as a servlet filter for this request.
at com.google.inject.servlet.GuiceFilter.getContext(GuiceFilter.java:132)
at com.google.inject.servlet.GuiceFilter.getRequest(GuiceFilter.java:118)
at com.google.inject.servlet.InternalServletModule$1.get(InternalServletModule.java:35)
.....
The object it's trying to provision is ServletContext. The cause of the error is due to the fact the GuiceFilter hasn't been called so the ServletContext hasn't been bound to ThreadLocal.
Is there any way of getting past this?
In the Junit environment you aren't getting two things that you normally get from the servlet container: the setup/destroy help from the GuiceServletContextListener and the filtering of the GuiceFilter, so you need to do these bits yourself.
You basically need to create another servlet that wraps your servlet and does all the setup/filtering that you'd normally see done by the servlet container; what I recommend is something like this:
Suppose your servlet is called AdriansGuicedGwtServiceServlet. Then create this in your testing directory:
public class TestAdriansGuicedGwtServiceServlet extends AdriansGuicedGwtServiceServlet {
private GuiceFilter filter;
#Override
public void init() {
super.init();
// move your injector-creating code here if you want to
// (I think it's cleaner if you do move it here, instead of leaving
// it in your main servlet)
filter = new GuiceFilter();
filter.init(new FilterConfig() {
public String getFilterName() {
return "GuiceFilter";
}
public ServletContext getServletContext() {
return TestAdriansGuicedGwtServiceServlet.this.getServletContext();
}
public String getInitParameter(String s) {
return null;
}
public Enumeration getInitParameterNames() {
return new Vector(0).elements();
}
});
}
#Override
public void destroy() {
super.destroy();
filter.destroy();
}
private void superService(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
super.service(req, res);
}
#Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
filter.doFilter(new FilterChain() {
public void doFilter (ServletRequest request, ServletResponse response)
throws IOException, ServletException {
superService(request, response);
}
});
}
}
And then in your <app>Junit.gwt.rpc have it map in TestAdriansGuicedGwtServiceServlet instead of your real servlet.