Initialising OAuth WebClient Bean in Spring MVC - spring-mvc

I have a WebApp JSP project deployed on Weblogic 12 as a WAR.
My gradle build includes mvc and webflux:
implementation 'org.springframework.boot:spring-boot-starter-web:2.3.2.RELEASE'
implementation ("org.springframework.boot:spring-boot-starter-security:2.3.2.RELEASE")
implementation ("org.springframework.boot:spring-boot-starter-oauth2-client:2.3.2.RELEASE")
implementation ("org.springframework.boot:spring-boot-starter-webflux:2.3.2.RELEASE")
I am trying to configure OAuth2 to use client_credentials flow from my client JSP application.
I need the #Controller class to use WebClient and propagate the access token to a Resource Server.
My Bean to create the WebClient is seen below.
#Bean
public ReactiveClientRegistrationRepository getRegistration() {
ClientRegistration registration = ClientRegistration
.withRegistrationId("ei-gateway")
.tokenUri("https://xxxxx.xxxxxxx.net/auth/oauth/v2/token")
.clientId("xxx-xxxx-43e9-a407-xxxxx")
.clientSecret("xxxxxx-3d21-4905-b6e5-xxxxxxxxxx")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
#Bean
public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations, ServerOAuth2AuthorizedClientRepository authorizedClients) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
oauth.setDefaultOAuth2AuthorizedClient(true);
return WebClient.builder()
.filter(oauth)
.defaultHeader("accept", "application/json")
.defaultHeader("content-type", "application/json")
.defaultHeader("environment", environment)
.filter(logRequest())
.filter(logResponse())
.build();
}
However I get the following error during compile:
Could not autowire. There is more than one bean of 'ReactiveClientRegistrationRepository' type.
Beans:
clientRegistrationRepository   (ReactiveOAuth2ClientConfigurations.class)
getRegistration   (WebSecurityConfiguration.java)
However when I uncomment out the getRegistration Bean method and configure the oauth client registration via the web.xml, then when deploying the application I get this error:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}:org.springframework.beans.factory.NoSuchBeanDefinitionException:No qualifying bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I see from the ReactiveOAuth2ClientAutoConfiguration source code that the Reactive OAuth2 Auto Configuration is not run when ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition is set.
#Configuration(proxyBeanMethods = false)
#AutoConfigureBefore(ReactiveSecurityAutoConfiguration.class)
#EnableConfigurationProperties(OAuth2ClientProperties.class)
#Conditional(ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.class)
#ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ClientRegistration.class })
#Import({ ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration.class,
ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration.class })
public class ReactiveOAuth2ClientAutoConfiguration {
}
Can anyone suggest a course of action? Is is possible to manually configure the ReactiveOAuth2ClientConfiguration?
Thanks

Form what I understand ReactiveClientRegistrationRepository is not available since you are not using a reactive stack, and here's how you can set up WebClient to be used in a Servlet environment.
Setup application properties so Spring autowires ClientRegistrationRepository and OAuth2AuthorizedClientRepository for you.
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://xxxxx.xxxxxxx.net/auth/oauth/v2/token
spring.security.oauth2.client.registration.ei-gateway.client-id=xxx-xxxx-43e9-a407-xxxxx
spring.security.oauth2.client.registration.ei-gateway.client-xxxxxx-3d21-4905-b6e5-xxxxxxxxxx
spring.security.oauth2.client.registration.ei-gateway.provider=my-oauth-provider
spring.security.oauth2.client.registration.ei-gateway.scope=read,write
spring.security.oauth2.client.registration.ei-gateway.authorization-grant-type=client_credentials
Setup configuration to indicate that your application needs to act as an oauth2 Client
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Client();
}
}
Expose WebClient bean configured to use client credentials
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
#Bean
WebClient webClient(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
oAuth2AuthorizedClientManager);
// default registrationId - Only if you are not using the webClient to talk to different external APIs
oauth2Client.setDefaultClientRegistrationId("ei-gateway");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
Now you can use WebClient in your code to access external protected resources.
references:
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2client
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2Client-webclient-servlet
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#defaulting-the-authorized-client
This set up worked for me when the application is not configured as a resource server, I had to use a different configuration when the application needs to use WebClient, but also configured to be a resource server.

Related

Provider test integration with pact broker for Spring Boot junit5 + configuration in application properties

The pact-jvm-provider-spring states that for junit5 provider test, it is not required to use the spring library.
However, #PactBroker annotation depends on the system properties. Is there a way to get this working for application properties via the Spring Property Resolver. I tried to create something similar to SpringEnvironmentResolver.kt and used it in the context setup. But that did not work.
#Provider("api-provider-app")
#PactBroker
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
public class PactVerificationTest {
#LocalServerPort
private int port;
#Autowired
private Environment environment;
#TestTemplate
#ExtendWith(PactVerificationInvocationContextProvider.class)
void testTemplate(Pact pact, Interaction interaction, HttpRequest request,
PactVerificationContext context) {
context.setTarget(new HttpTestTarget("localhost", port));
context.setValueResolver(new SpringResolver(environment));
context.verifyInteraction();
}
}
I get the following error
Invalid pact broker host specified ('${pactbroker.host:}'). Please provide a valid host or specify the system property 'pactbroker.host'.
Update
After some more searching found out that the setTarget was not working and that needs to be moved to #BeforeEach method.
#BeforeEach
void setContext(PactVerificationContext context) {
context.setValueResolver(new SpringResolver(environment));
context.setTarget(new HttpTestTarget("localhost", port));
}
The following snippet helped it work with #PactFolder annotation. But the #PactBroker with properties is still not working
There is a new module added to Pact-JVM that extends the JUnit5 support to allow values to be configured in the Spring Context. See https://github.com/DiUS/pact-jvm/tree/master/provider/pact-jvm-provider-junit5-spring. It will be released with the next version of Pact-JVM, which will be 4.0.7.

call Remote EJB from client JAVA

I have to implement two simples java projects ; the first is an EJB projectwho contains a simple service which implement a remote interface, and the second project is a java client which try to call this ejb project ,
so here is what I did until now :
Context context = new InitialContext(jndiProperties);
TestServiceRemote proxy = (TestServiceRemote) context
.lookup("java:global/testEJB/TestService!services.TestServiceRemote");
System.out.println(proxy.showHello());
and this my ejb service :
#Stateless
public class TestService implements TestServiceRemote {
public TestService() {
}
#Override
public String showHello() {
return "Hello";
}
}
finally this my Remote interface :
#Remote
public interface TestServiceRemote {
public String showHello();
}
I had deployed the EJB in WIldfly 9 , but when I launch the java client i get this error shown in console :
Exception in thread "main" javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:662)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:350)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at swing.testClient.main(testClient.java:22)
Can someone tell me what I had wrong in my code ?
The java: namespace is not available by default in Java. You can either package your main class as an application client JAR inside an EAR and run it using an application client container launcher, or you can configure the InitialContext to have access to the java: namespace as described in this blog post.

Spring Cloud Netflix : Passing host request parameter via RequestInterceptor to FeignClient

I am building a Spring Cloud project (Brixton.M4 with Spring Boot 1.3.1) with Eureka, Zuul and FeignClient where I am trying to add multi tenancy support (Tenants are identified by subdomain : tenant1.myservice.com). To do so, I would like to somehow pass the original subdomain along requests that are forwarded from a service to the other via Feign but I can't seem to be able to find the right way to do it.
What I have is a client that exposes a #RestController which calls a #FeignClient to communicate with my backend which exposes server operations to the client through its own #RestController.
The #FeignClient using same interface as my #RestController on the server :
#FeignClient(name = "product")
public interface ProductService extends IProductService {
}
What I am currently trying to do is set a header in a RequestInterceptor :
#Component
public class MultiTenancyRequestInterceptor implements RequestInterceptor {
private CurrentTenantProvider currentTenantProvider;
#Autowired
public MultiTenancyRequestInterceptor(CurrentTenantProvider currentTenantProvider) {
this.currentTenantProvider = currentTenantProvider;
}
#Override
public void apply(RequestTemplate template) {
try {
template.header("TENANT", currentTenantProvider.getTenant());
} catch (Exception e) {
// "oops"
}
}
}
My provider class is a simple component where I'm trying to inject a request / session scope bean :
#Component
public class CurrentTenantProvider {
#Autowired
private CurrentTenant currentTenant;
//...
}
The bean (I tried both session and request scope) :
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CurrentTenant currentTenant() {
return new CurrentTenant();
}
On the server, I use Hibernate multitenant provider that is supposed to catch the header value and use it to define which DB to connect to :
#Autowired
private HttpServletRequest httpRequest;
#Override
public String resolveCurrentTenantIdentifier() {
return httpRequest.getHeader("TENANT");
}
It seems the Feign call to the server is done in another thread and out of the incoming request scope, so i'm not sure how to pass that value along.
It all works fine when I hardcode the tenant value in the RequestInterceptor so I know the rest is working properly.
I have also looked at many other posts about Zuul "X-Forwaded-For" header and cannot find it in the request received on the server. I have also tried adding a ZuulFilter to pass host name to next request but what I see is that original request to the Client is picked up by the ZuulFilter and I can add but not when the Feign request is sent to the backend service even if I map it in zuul (i guess that is intended ?).
I am not really sure what's the next step and would appreciate some suggestions.
Hope that it's of any use for you but we're doing sth similar in Spring-Cloud-Sleuth but we're using a ThreadLocal to pass span between different libraries and approaches (including Feign + Hystrix).
Here is an example with the highlighted line where we retrieve the Span from the thread local: https://github.com/spring-cloud/spring-cloud-sleuth/blob/master/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceFeignClientAutoConfiguration.java#L123

How to pass IOptions to an ASP.NET 5 middle-ware service?

I have started playing around with ASP.NET 5 vNext and I am struggling passing the options from config.json into a middle-ware service that is used by my WebApi controller.
Here is a snippet with my middle-ware service:
public class MyService : IMyService
{
public MyService(IOptions<MyOptions> settings)
{
var o = settings.Options;
}
}
Here is my WebApi controller that is using the middle-ware service:
public class MyController : Controller
{
private IMyService _myService;
public TestController(IMyService service)
{
_myService = service;
}
}
In Startup.cs I am reading the options:
services.AddOptions();
services.Configure<MyOptions>(Configuration);
What I am struggling with is how to register an instance to IMyService so that it would be passed to the constructor of the controller (how can I get a hold of the IOptions)?
services.AddInstance<IMyService>(new MyService(XXXXX));
As suggested below I did try to use both
services.AddTransient<MyService>();
and
services.AddSingleton<MyService>();
But in both cases I am seeing the following error:
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type
'MyApp.Services.IMyService' while attempting to activate
'MyApp.Controllers.TestController'.
Microsoft.Framework.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider
sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
Thanks for your help!
Don't register it as an Instance. Instead just add it as Scoped/Transient/Singleton depending on your requirements and let Dependency Injection do its magic;
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddScoped<IMyService, MyService>();
For example, you can to add to Startup.cs that code:
public void ConfigureServices(IServiceCollection services)
{
var builder = new ConfigurationBuilder("[path to file with configuration]");
builder.AddJsonFile("config.json");
var config = builder.Build();
services.AddOptions();
services.Configure<MyOptions>(config);
//services.AddSingleton<IMyService, MyService>();
services.AddTransient<IMyService, MyService>();
}
I can assure you that you can use Singleton or Transient.
If you're interested, you can find more info here https://github.com/aspnet/Docs/issues/24.
And additionally, currently Autofac creates DI for ASP.NET 5 on
http://alexmg.com/autofac-4-0-alpha-1-for-asp-net-5-0-beta-3/

Spring beans injection into jax-ws services

So I already learnt that integration of spring and jax-ws is not an easy thing.
I want to inject a spring bean into jax-ws service, but for some reason I get an exception during the deployment:
Exception while loading the app : java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: java.lang.IllegalArgumentException: javax.servlet.ServletException: com.sun.enterprise.container.common.spi.util.InjectionException: Error creating managed object for class: class org.springframework.web.context.ContextLoaderListener|#]
this is my jax-ws configuration:
<wss:binding url="/ws/users">
<wss:service>
<ws:service bean="#usersWs"/>
</wss:service>
</wss:binding>
<bean id="usersWs" class="love.service.endpoint.implementations.UserServiceImpl" />
And this is my service:
#WebService
public class UserServiceImpl implements UserService{
#EJB
private DBManager dbmanager;
#Override
#WebMethod
public boolean addUser(String name, String password, String email) {
return false;
}
#Override
#WebMethod
public boolean isUsernameAvailable(String username) {
return dbmanager.isLoginAvailable(username);
}
#Override
#WebMethod
public boolean isEmailAvailable(String email) {
return dbmanager.isEmailAvailable(email);
}
}
and finally my bean configuration:
<bean id="dbmanager" class="love.commons.database.DBManager" scope="request">
<aop:scoped-proxy/>
</bean>
I also tried injecting the bean into some controllers and then it works perfectly well.
If I replace #EJB with #Autowired, the application starts, but the service still doesn't work. When I tried sending a message to it, my only response was the following:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
<faultcode>S:Server</faultcode>
<faultstring>Error creating bean with name 'scopedTarget.dbmanager': 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.</faultstring>
</S:Fault>
</S:Body>
</S:Envelope>

Resources