Map Shiro's AuthenticationException with Jersey's ExceptionMapper - jersey

Preface
First of all, my sincerest apologies for this question being extremely long, but I honestly have no idea on how to shorten it, since each part is kind of a special case. Admittedly, I may be blind on this since I am banging my head against the wall for a couple of days now and I am starting to get desperate.
My utmost respect and thankfulness to all of you who read through it.
The aim
I would like to be able to map Shiro's AuthenticationException and it's subclasses to JAX-RS Responses by using Jersey ExceptionMappers, set up using a Guice 3.0 Injector which creates an embedded Jetty.
The environment
Guice 3.0
Jetty 9.2.12.v20150709
Jersey 1.19.1
Shiro 1.2.4
The setup
The embedded Jetty is created using a Guice Injector
// imports omitted for brevity
public class Bootstrap {
public static void main(String[] args) throws Exception {
/*
* The ShiroWebModule is passed as a class
* since it needs a ServletContext to be initialized
*/
Injector injector = Guice.createInjector(new ServerModule(MyShiroWebModule.class));
Server server = injector.getInstance(Server.class);
server.start();
server.join();
}
}
The ServerModule binds a Provider for the Jetty Server:
public class ServerModule extends AbstractModule {
Class<? extends ShiroWebModule> clazz;
public ServerModule(Class <?extends ShiroWebModule> clazz) {
this.clazz = clazz;
}
#Override
protected void configure() {
bind(Server.class)
.toProvider(JettyProvider.withShiroWebModule(clazz))
.in(Singleton.class);
}
}
The JettyProvider sets up a Jetty WebApplicationContext, registers the ServletContextListener necessary for Guice and a few things more, which I left in to make sure no "side effects" may be hidden:
public class JettyProvider implements Provider<Server>{
#Inject
Injector injector;
#Inject
#Named("server.Port")
Integer port;
#Inject
#Named("server.Host")
String host;
private Class<? extends ShiroWebModule> clazz;
private static Server server;
private JettyProvider(Class<? extends ShiroWebModule> clazz){
this.clazz = clazz;
}
public static JettyProvider withShiroWebModule(Class<? extends ShiroWebModule> clazz){
return new JettyProvider(clazz);
}
public Server get() {
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
// Set during testing only
webAppContext.setResourceBase("src/main/webapp/");
webAppContext.setParentLoaderPriority(true);
webAppContext.addEventListener(
new MyServletContextListener(injector,clazz)
);
webAppContext.addFilter(
GuiceFilter.class, "/*",
EnumSet.allOf(DispatcherType.class)
);
webAppContext.setThrowUnavailableOnStartupException(true);
QueuedThreadPool threadPool = new QueuedThreadPool(500, 10);
server = new Server(threadPool);
ServerConnector connector = new ServerConnector(server);
connector.setHost(this.host);
connector.setPort(this.port);
RequestLogHandler requestLogHandler = new RequestLogHandler();
requestLogHandler.setRequestLog(new NCSARequestLog());
HandlerCollection handlers = new HandlerCollection(true);
handlers.addHandler(webAppContext);
handlers.addHandler(requestLogHandler);
server.addConnector(connector);
server.setStopAtShutdown(true);
server.setHandler(handlers);
return server;
}
}
In MyServletContextListener, I created a child injector, which gets initialized with the JerseyServletModule:
public class MyServletContextListener extends GuiceServletContextListener {
private ServletContext servletContext;
private Injector injector;
private Class<? extends ShiroWebModule> shiroModuleClass;
private ShiroWebModule module;
public ServletContextListener(Injector injector,
Class<? extends ShiroWebModule> clazz) {
this.injector = injector;
this.shiroModuleClass = clazz;
}
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
this.servletContext = servletContextEvent.getServletContext();
super.contextInitialized(servletContextEvent);
}
#Override
protected Injector getInjector() {
/*
* Since we finally have our ServletContext
* we can now instantiate our ShiroWebModule
*/
try {
module = shiroModuleClass.getConstructor(ServletContext.class)
.newInstance(this.servletContext);
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
/*
* Now, we create a child injector with the JerseyModule
*/
Injector child = injector.createChildInjector(module,
new JerseyModule());
SecurityManager securityManager = child
.getInstance(SecurityManager.class);
SecurityUtils.setSecurityManager(securityManager);
return child;
}
}
The JerseyModule, a subclass of JerseyServletModule now put everything together:
public class JerseyModule extends JerseyServletModule {
#Override
protected void configureServlets() {
bindings();
filters();
}
private void bindings() {
bind(DefaultServlet.class).asEagerSingleton();
bind(GuiceContainer.class).asEagerSingleton();
serve("/*").with(DefaultServlet.class);
}
private void filters() {
Map<String, String> params = new HashMap<String, String>();
// Make sure Jersey scans the package
params.put("com.sun.jersey.config.property.packages",
"com.example.webapp");
params.put("com.sun.jersey.config.feature.Trace", "true");
filter("/*").through(GuiceShiroFilter.class,params);
filter("/*").through(GuiceContainer.class, params);
/*
* Although the ExceptionHandler is already found by Jersey
* I bound it manually to be sure
*/
bind(ExceptionHandler.class);
bind(MyService.class);
}
}
The ExceptionHandler is extremely straightforward and looks like this:
#Provider
#Singleton
public class ExceptionHandler implements
ExceptionMapper<AuthenticationException> {
public Response toResponse(AuthenticationException exception) {
return Response
.status(Status.UNAUTHORIZED)
.entity("auth exception handled")
.build();
}
}
The problem
Now everything works fine when I want to access a restricted resource and enter correct principal/credential combinations. But as soon as enter a non-existing user or a wrong password, I want an AuthenticationException to be thrown by Shiro and I want it to be handled by the above ExceptionHandler.
Utilizing the default AUTHC filter provided by Shiro in the beginning, I noticed that AuthenticationExceptions are silently swallowed and the user is redirected to the login page again.
So I subclassed Shiro's FormAuthenticationFilter to throw an AuthenticationException if there is one:
public class MyFormAutheticationFilter extends FormAuthenticationFilter {
#Override
protected boolean onLoginFailure(AuthenticationToken token,
AuthenticationException e, ServletRequest request,
ServletResponse response) {
if(e != null){
throw e;
}
return super.onLoginFailure(token, e, request, response);
}
}
And I also tried it with throwing the exception e wrapped in a MappableContainerException.
Both approaches cause the same problem: Instead of the exception being handled by the defined ExceptionHandler, a javax.servlet.ServletException is thrown:
javax.servlet.ServletException: org.apache.shiro.authc.AuthenticationException: Unknown Account!
at org.apache.shiro.web.servlet.AdviceFilter.cleanup(AdviceFilter.java:196)
at org.apache.shiro.web.filter.authc.AuthenticatingFilter.cleanup(AuthenticatingFilter.java:155)
at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:148)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.shiro.guice.web.SimpleFilterChain.doFilter(SimpleFilterChain.java:41)
at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:163)
at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:118)
at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:113)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:744)
Caused by: org.apache.shiro.authc.AuthenticationException: Unknown Account!
at com.example.webapp.security.MyAuthorizingRealm.doGetAuthenticationInfo(MyAuthorizingRealm.java:27)
at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:568)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)
at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:270)
at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256)
at org.apache.shiro.web.filter.authc.AuthenticatingFilter.executeLogin(AuthenticatingFilter.java:53)
at org.apache.shiro.web.filter.authc.FormAuthenticationFilter.onAccessDenied(FormAuthenticationFilter.java:154)
at org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(AccessControlFilter.java:133)
at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)
at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:203)
at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:178)
at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131)
... 32 more
The question, after all
Given that the environment can't be changed, how can I achieve that a server instance still can be requested via Guice, while Shiro's exceptions are handled with Jersey's auto discovered ExceptionMappers?

This question is much too complicated for me to reproduce on my side, but I saw a problem that I think is the answer and I'll delete this answer if I turn out to be wrong.
You do this:
#Provider
#Singleton
public class ExceptionHandler implements
ExceptionMapper<AuthenticationException> {
Which is correct, you are supposed to bind with both of those annotations as in this question. However, what you do differently is this:
/*
* Although the ExceptionHandler is already found by Jersey
* I bound it manually to be sure
*/
bind(ExceptionHandler.class);
The annotations in a class definition have lower priority than that in a module's configure() method, meaning you are erasing the annotations when you bind "it manually just to be sure". Try erasing that line of code and see if that fixes your problem. If it doesn't fix the problem, leave it deleted anyway, because I am certain that it is at least part of the problem - that statement erases those essential annotations.

I've not found a way to do this either. It looks like the Jersey filters/handlers aren't active on the Shiro servlet stack during authentication. As a work-around specifically for the AuthenticationException I opted to override the AdviceFilter::cleanup(...) method on my AuthenticatingFilter and return a custom message directly.
public class MyTokenAuthenticatingFilter extends AuthenticatingFilter {
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
// regular auth/token creation
}
#Override
protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {
HttpServletResponse httpResponse = (HttpServletResponse)response;
if ( null != existing ) {
httpResponse.setContentType(MediaType.APPLICATION_JSON);
httpResponse.getOutputStream().write(String.format("{\"error\":\"%s\"}", existing.getMessage()).getBytes());
httpResponse.setStatus(Response.Status.FORBIDDEN.getStatusCode());
existing = null; // prevent Shiro from tossing a ServletException
}
super.cleanup(request, httpResponse, existing);
}
}
When authentication is successful the ExceptionMappers work fine for exceptions thrown within the context of the Jersey controllers.

Related

How to force persisting data during execution of camel processors rather than the end of the route?

When i toogle a breakpoint beside the repository.saveAndFlush and during de debug mode i see that it returne a new client objet with new Id but when i check in the data base i do not find that client. However, if i do a resume (F8 with eclipse) then i re-check the DB i find the client.
So How to force persisting data during execution of camel processors rather than the end of the route?
#Component
public class myRoute extends RouteBuilder {
#Autowired Processor validationDatasProcessor;
#Autowired Processor clientProcessor;
#Autowired Processor endCientProcessor;
#Override
public void configure() throws Exception {
from("queueIn")
.id("route_processing").messageHistory().transacted()
.log(LoggingLevel.DEBUG, log, "reception").pipeline()
.process(validationDatasProcessor)
.id(validationDatasProcessor.getClass().getSimpleName().toLowerCase())
.process(clientProcessor)
.id(clientProcessor.getClass().getSimpleName().toLowerCase())
.process(endCientProcessor).id(endCientProcessor.getClass().getSimpleName().toLowerCase())
.to("outputQueue")
.end();
}
}
Processors:
#Component
public class ValidationDatasProcessor implements Processor {
#Autowired ObjectMapper objectMapper;
#Autowired ClientRepository clientRepository;
#Override
public void process(Exchange exchange) throws Exception {
String clientString = exchange.getIn().getBody(String.class);
Client client = objectMapper.readValue(clientString, Client.class);
clientRepository.saveAndFlush(client)
exchange.setOut(generateOutMessage(client, exchange.getContext()));
}
Message generateOutMessage(Client client, CamelContext camelContext) throws JsonProcessingException {
DefaultMessage outMessage = new DefaultMessage(camelContext);
outMessage.setBody(objectMapper.writeValueAsString(client), String.class);
return outMessage;
}
}
#Component
public class ClientProcessor implements Processor {
#Autowired ObjectMapper objectMapper;
#Autowired ClientRepository clientRepository;
....
#Override
public void process(Exchange exchange) throws Exception {
String clientString = exchange.getIn().getBody(String.class);
Client client = objectMapper.readValue(clientString, Client.class);
client.setAccessDate(LocalDateTime.now);
clientRepository.saveAndFlush(client)
exchange.setOut(generateOutMessage(client, exchange.getContext()));
}
Message generateOutMessage(Client client, CamelContext camelContext) throws JsonProcessingException {
DefaultMessage outMessage = new DefaultMessage(camelContext);
outMessage.setBody(objectMapper.writeValueAsString(client), String.class);
return outMessage;
}
}
Your entire route is a transacted; which means the whole route is under a transaction scope. A commit will be done only after the whole route is executed.
if you want to execute a processor outside the transaction boundary, split the route and use a seda endpoint. seda are asynchronous and start new threads. They won't participate in the active transaction boundary.
Commiting parts in the middle of a transaction scope doesn't sound like a great idea. Perhaps your rounte needs to be split into multiple fragments.
This documentation might help you understand them better.

Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'request' is not active for the current thread for feign client

I am calling another microservice once my current microservice is up and ready using feign client in my current microservice built using Jhipster.
So my Feign Interface is
package com.persistent.integration.client;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.persistent.integration.service.dto.DataPipelineDTO;
#AuthorizedFeignClient(name = "Integrationconfiguration")
public interface DataPipelinesResourceFeign {
#RequestMapping(value = "/api/data-pipelines", method = RequestMethod.GET)
List<DataPipelineDTO> getAllDataPipelines(#RequestParam(value = "pageable") Pageable pageable );
}
}
And I have implemented ApplicationRunner where I have called feign client method.
#Component
public class ApplicationInitializer implements ApplicationRunner {
#Autowired
private DataPipelinesResourceFeign dataPipelinesResourceFeign;
#Autowired
private ActiveMQListener activeMqListener;
#Override
public void run(ApplicationArguments args) throws Exception {
// TODO Auto-generated method stub
Pageable pageable = PageRequest.of(0, 20);
try {
List <DataPipelineDTO> allStartedDataPipeLines = dataPipelinesResourceFeign.getAllDataPipelines(pageable); //.stream().filter(p->p.getState().equals(State.STARTED)).collect(Collectors.toList());
allStartedDataPipeLines.forEach(datapipe ->
{
try {
activeMqListener.consume(datapipe);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
But after running this, it gives below exception at dataPipelinesResourceFeign.getAllDataPipelines :
com.netflix.hystrix.exception.HystrixRuntimeException: DataPipelinesResourceFeign#getAllDataPipelines(Pageable) failed and no fallback available.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'request' is not active for the current thread; consider
defining a scoped proxy for this bean if you intend to refer to it
from a singleton; nested exception is java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:362)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractB>eanFactory.java:199)
at
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTarge>tSource.java:35)
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.>java:193)
at com.sun.proxy.$Proxy147.getAccessToken(Unknown Source) at
com.persistent.integration.security.oauth2.AuthorizationHeaderUtil.getAuthoriza>tionHeaderFromOAuth2Context(AuthorizationHeaderUtil.java:28)
at
com.persistent.integration.client.TokenRelayRequestInterceptor.apply(TokenRelay>RequestInterceptor.java:23)
at
feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)
at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at
feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
at
rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 68 more Caused by: java.lang.IllegalStateException: No
thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.web.context.request.RequestContextHolder.currentRequestAttr>ibutes(RequestContextHolder.java:131)
at
org.springframework.web.context.request.AbstractRequestAttributesScope.get(Abst>ractRequestAttributesScope.java:42)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:350)
many suggestions on internet were to add listerner RequestContextListener. But problem persisted even if I added listener in webConfigurer.java in onStartup method.
{
servletContext.addListener(RequestContextListener.class);
}
But of no use.
Any leads would be appreciated.
I found a workaround for this. I don't know why TokenRelayRequestIntercepton isn't working but you can use your own RequestInterceptor based on Spring's SecurityContext.
First, define a RequestInterceptor :
public class MyRequestInterceptor implements RequestInterceptor {
public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer";
public MyRequestInterceptor() {
super();
}
#Override
public void apply(RequestTemplate template) {
// demander un token à keycloak et le joindre à la request
Optional<String> header = getAuthorizationHeader();
if (header.isPresent()) {
template.header(AUTHORIZATION, header.get());
}
}
public static Optional<String> getAuthorizationHeader() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getDetails() != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oAuth2AuthenticationDetails =
(OAuth2AuthenticationDetails) authentication.getDetails();
return Optional.of(String.format("%s %s", oAuth2AuthenticationDetails.getTokenType(),
oAuth2AuthenticationDetails.getTokenValue()));
} else {
return Optional.empty();
}
}
}
and then, declare a config class for your feign client using your RequestInterceptor, it should contains something like this :
#Bean(name = "myRequestInterceptor")
public RequestInterceptor getMyRequestInterceptor() throws IOException {
return new MyRequestInterceptor();
}
Your Feign client shoud look like this:
#FeignClient(name = "SERVICE_NAME", configuration = MyFeignConfiguration.class)
public interface MyRestClient {
I had the same issue with Feign Client running on startup using ApplicationRunner and I came up with following solution.
I defined my FeignClientsConfiguration with OAuth2FeignRequestInterceptor, which accepts predefined bean DefaultOAuth2ClientContext and OAuth2 configuration OAuth2ProtectedResourceDetails:
#Configuration
public class MyConfig extends FeignClientsConfiguration {
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor( DefaultOAuth2ClientContext oAuth2ClientContext, MyOauth2Properties properties) {
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resourceDetails(properties));
}
#Bean
public DefaultOAuth2ClientContext oAuth2ClientContext() {
return new DefaultOAuth2ClientContext();
}
private OAuth2ProtectedResourceDetails resourceDetails(MyOauth2Properties oauth2Properties) {
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setAccessTokenUri(oauth2Properties.getAccessTokenUri());
resourceDetails.setUsername(oauth2Properties.getUsername());
resourceDetails.setPassword(oauth2Properties.getPassword());
resourceDetails.setClientId(oauth2Properties.getClientId());
return resourceDetails;
}
}
Your feign client will look something like this:
#FeignClient(url = "http://localhost:8080/api/v1")
public interface FeignClient {
}
After all this, calling FeignClient from ApplicationRunner.run() works fine.
Spring Boot 2.2.6

Autowiring a spring managed bean in the resteasy providers

I have a developing a webservice where in i need to validate a particular httpheader sent in the request against the database.
I want to use RestEasy provider for doing the same as the same functionality needs to be applied to all the service.
#Provider
#ServerInterceptor
public class TestValidationInterceptor implements PreProcessInterceptor {
#Autowired
DetailsDelegate detailsDelegate;
#Override
public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException {
//code to get data using detailsDelegate.
return null;
}
}
public interface DetailsDelegate {
String BEAN_ID = "DetailsDelegate";
/**
* #param appName
* #return
* #throws BaseException
*/
ServiceInfo getServiceDetails(String appName) throws BaseException;
}
#Service("detailsDelegate")
public class DetailsDelegateImpl implements DetailsDelegate {
#Override
public ServiceInfo getServiceDetails(String appName) throws BaseException {
return null;
}
}
The detailsDelegate instance is not getting autowired and is null.
Can someone explain why I am facing this issue.
It's best to let spring chose it's bean names so change
#Service("detailsDelegate")
to
#Service
The autowire the interface :
#Auowired
DetailsDelegate
Finally make sure the package in which DetailsDelegate is defined is scanned in your config:
#ComponentScan("com.mypackage")
See http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch06s02.html for examples

Multiple servlet mappings in Spring Boot

Is there any way to set via property 'context-path' many mappings for a same Spring Boot MVC application? My goal is to avoid creating many 'Dispatcherservlet' for the uri mapping.
For example:
servlet.context-path =/, /context1, context2
You can create #Bean annotated method which returns ServletRegistrationBean , and add multiple mappings there. This is more preferable way, as Spring Boot encourage Java configuration rather than config files:
#Bean
public ServletRegistrationBean myServletRegistration()
{
String urlMapping1 = "/mySuperApp/service1/*";
String urlMapping2 = "/mySuperApp/service2/*";
ServletRegistrationBean registration = new ServletRegistrationBean(new MyBeautifulServlet(), urlMapping1, urlMapping2);
//registration.set... other properties may be here
return registration;
}
On application startup you'll be able to see in logs:
INFO | localhost | org.springframework.boot.web.servlet.ServletRegistrationBean | Mapping servlet: 'MyBeautifulServlet' to [/mySuperApp/service1/*, /mySuperApp/service2/*]
You only need a single Dispatcherservlet with a root context path set to what you want (could be / or mySuperApp).
By declaring multiple #RequestMaping, you will be able to serve different URI with the same DispatcherServlet.
Here is an example. Setting the DispatcherServlet to /mySuperApp with #RequestMapping("/service1") and #RequestMapping("/service2") would exposed the following endpoints :
/mySuperApp/service1
/mySuperApp/service2
Having multiple context for a single servlet is not part of the Servlet specification. A single servlet cannot serve from multiple context.
What you can do is map multiple values to your requesting mappings.
#RequestMapping({"/context1/service1}", {"/context2/service1}")
I don't see any other way around it.
You can use 'server.contextPath' property placeholder to set context path for the entire spring boot application. (e.g. server.contextPath=/live/path1)
Also, you can set class level context path that will be applied to all the methods e.g.:
#RestController
#RequestMapping(value = "/testResource", produces = MediaType.APPLICATION_JSON_VALUE)
public class TestResource{
#RequestMapping(method = RequestMethod.POST, value="/test", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<TestDto> save(#RequestBody TestDto testDto) {
...
With this structure, you can use /live/path1/testResource/test to execute save method.
None of the answers to this sort of question seem to mention that you'd normally solve this problem by configuring a reverse proxy in front of the application (eg nginx/apache httpd) to rewrite the request.
However if you must do it in the application then this method works (with Spring Boot 2.6.2 at least) : https://www.broadleafcommerce.com/blog/configuring-a-dynamic-context-path-in-spring-boot.
It describes creating a filter, putting it early in the filter chain and basically re-writing the URL (like a reverse proxy might) so that requests all go to the same place (ie the actual servlet.context-path).
I've found an alternative to using a filter described in https://www.broadleafcommerce.com/blog/configuring-a-dynamic-context-path-in-spring-boot that requires less code.
This uses RewriteValve (https://tomcat.apache.org/tomcat-9.0-doc/rewrite.html) to rewrite urls outside of the context path e.g. if the real context path is "context1" then it will map /context2/* to /context1/*
#Component
public class LegacyUrlWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private static final List<String> LEGACY_PATHS = List.of("context2", "context3");
#Override
public void customize(TomcatServletWebServerFactory factory) {
RewriteValve rewrite = new RewriteValve() {
#Override
protected void initInternal() throws LifecycleException {
super.initInternal();
try {
String config = LEGACY_PATHS.stream() //
.map(p -> String.format("RewriteRule ^/%s(/.*)$ %s$1", p, factory.getContextPath())) //
.collect(Collectors.joining("\n"));
setConfiguration(config);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
factory.addEngineValves(rewrite);
}
}
If you need to use HTTP redirects instead then there is a little bit more required (to avoid a NullPointerException in sendRedirect):
#Component
public class LegacyUrlWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private static final List<String> LEGACY_PATHS = List.of("context2", "context3");
#Override
public void customize(TomcatServletWebServerFactory factory) {
RewriteValve rewrite = new RewriteValve() {
#Override
protected void initInternal() throws LifecycleException {
super.initInternal();
try {
String config = LEGACY_PATHS.stream() //
.map(p -> String.format("RewriteRule ^/%s(/.*)$ %s$1 R=permanent", p, factory.getContextPath())) //
.collect(Collectors.joining("\n"));
setConfiguration(config);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#Override
public void invoke(Request request, Response response) throws IOException, ServletException {
if (request.getContext() == null) {
String[] s = request.getRequestURI().split("/");
if (s.length > 1 && LEGACY_PATHS.contains(s[1])) {
request.getMappingData().context = new FailedContext();
}
}
super.invoke(request, response);
}
};
factory.addEngineValves(rewrite);
}
}
I use this approach:
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
#Configuration
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
rootContext.setServletContext(servletContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/mapping1/*");
dispatcher.addMapping("/mapping2/*");
servletContext.addListener(new ContextLoaderListener(rootContext));
}
}

Why would Spring autowire fail?

#Service
public class LogProcessorServiceImpl {
#Autowired
private static ApplicationConfigurationService applicationConfigurationService;
public static void processPageRequestsLogs() {
if(applicationConfigurationService==null) {
System.out.println("autowire failed");
}
I have the ApplicationConfigurationService service autowired like this all over the place and it works fine. The package of this class is being scanned so that's not the problem. It might be related to the way this particular method is called. I have a servlet that is loaded after all other servlets and it fires of a timer that executes the method above with 60 second delay. I assume all autowiring should be completed.
public class ProcessSchedulerServlet implements javax.servlet.Servlet {
Timer timer=new Timer();
#Override
public void init(ServletConfig arg0) throws ServletException {
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
LogProcessorServiceImpl.processPageRequestsLogs();
}
}, 60*1000, 120*1000);
}
Here's what happens as soon as I true to use ApplicationConfigurationService:
autowire failed
Exception in thread "Timer-1" java.lang.NullPointerException
at com.siteadmin.services.impl.LogProcessorServiceImpl.processPageRequestsLogs(LogProcessorServiceImpl.java:39)
at com.siteadmin.servlets.ProcessSchedulerServlet$1.run(ProcessSchedulerServlet.java:20)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
=== 2012-11-18 ============================================================
See also: How to go about Spring autowiring?
You can't autowire static fields in Spring, this is discussed here
As alternative, if your LogProcessorServiceresides in the root web application context, you can
autowire it with Spring WebApplicationContextUtils utility class.
public class ProcessSchedulerServlet implements javax.servlet.Servlet {
Timer timer=new Timer();
#Autowired
LogProcessorService logProcessorService;
#Override
public void init(ServletConfig arg0) throws ServletException {
WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext())
.getAutowireCapableBeanFactory().autowireBean(this);
final LogProcessorService svc = this.logProcessorService;
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
svc.processPageRequestsLogs();
}
}, 60*1000, 120*1000);
In general, you should avoid using Java singletons, where using Spring singletons is enough.
Also, if you declared LogProcessorServiceImpl with a #Service annotation, that implies it to be a Spring singleton, so you should not use static fields there at all.
P.S. this answer is about autowiring, it assumes that the idea with TimerTask is correct, in the real apps consider using the Spring Scheduling API

Resources