Is there a solution to propagate request scoped cdi bean to a consumer with calling EventBus request? - events

I'm trying to use quarkus-rest-client-reactive to call a rest api, and the rest-client calling is in a #ConsumeEvent method.
But, now it doesn't work because the authorization header on the request message is not set, and I noticed that when I call the consume event with using a EventBus request, a request scoped cdi bean is not properly propagated.
I'm refering the example https://ja.quarkus.io/guides/reactive-event-bus.
It is said then 'You can inject an executor if you use the Context Propagation extension'.
Please tell me how to propagate request scoped bean when I'm using EventBus request.
#Priority(10)
#Decorator
abstract public class Repository_Event implements Repository {
#Inject
#Any
#Delegate
Repository _delegate;
#Inject
EventBus _bus;
#Inject
JsonWebToken _accessToken;
#Inject
Token _token;
#Override
public Uni<File> findById(FileId id) {
_token.setToken(_accessToken.getRawToken());
return _delegate.findById(id)
.onItem().transformToUni(file -> _bus.<File>request("find", file))
.onItem().transform(Message::body);
}
}
#ApplicationScoped
public class EventFileFileName {
#RestClient
ServiceFileFileName _service;
#Inject
Logger _log;
#Inject
Token _token;
#Inject
ManagedExecutor managedExecutor;
#ConsumeEvent("find")
public Uni<File> find(File file) {
_log.info(".find " + file.getId().getFileId() + ",token:" + _token.getToken()); // token: null
return Uni.createFrom().nullItem()
.emitOn(managedExecutor)
.onItem().transformToUni(item -> _service.name(file.getId()))
.invoke(name -> file.setName(name))
.onItem().transform(name -> file);
}
}
#RequestScoped
public class Token {
private String _token;
public String getToken() {
return _token;
}
public void setToken(String token) {
_token = token;
}
}
■ application.properties
org.eclipse.microprofile.rest.client.propagateHeaders=Authorization
■ pom.xml
<properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
<quarkus.platform.version>2.15.0.Final</quarkus.platform.version>
<surefire-plugin.version>2.22.1</surefire-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-authorization</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logmanager</groupId>
<artifactId>log4j2-jboss-logmanager</artifactId>
</dependency>
</dependencies>
It worked when I use event.fireAsync and #ObservesAsync, but I couldn't get a return value so I can't use this means.
#Priority(10)
#Decorator
abstract public class Repository_Event implements Repository {
#Inject
#Any
#Delegate
Repository _delegate;
#Inject
#Find
Event<File> _eventFind;
#Inject
Token _token;
#Inject
ManagedExecutor managedExecutor;
#Override
public Uni<File> findById(FileId id) {
_token.setToken(_accessToken.getRawToken());
return _delegate.findById(id)
.onItem().transformToUni(file ->
Uni.createFrom()
.completionStage(() -> _eventFind.fireAsync(file, NotificationOptions.ofExecutor(managedExecutor))));
}
}
#ApplicationScoped
public class EventFileFileName {
#Inject
Token _token;
#RestClient
ServiceFileFileName _service;
public void find2(#ObservesAsync #Find File file) {
_log.info(".find " + file.getId().getFileId() + ",token:" + _token.getToken()); // token is not null
_service.name(file.getId())
.onItem().invoke(name -> file.setName(name))
.onItem().transform(name -> file)
.subscribeAsCompletionStage();
}
}
And I tried to add an emitOn() with a ManagedExecutor before EventBus.request calling, but it doesn't work.
#Priority(10)
#Decorator
abstract public class Repository_Event implements Repository {
#Inject
#Any
#Delegate
Repository _delegate;
#Inject
EventBus _bus;
#Inject
JsonWebToken _accessToken;
#Inject
Token _token;
#Inject
ManagedExecutor managedExecutor;
#Override
public Uni<File> findById(FileId id) {
_token.setToken(_accessToken.getRawToken());
return _delegate.findById(id)
.emitOn(managedExecutor)
.onItem().transformToUni(file -> _bus.<File>request("find", file))
.onItem().transform(Message::body);
}
}
#ConsumeEvent("find")
public Uni<File> find(File file) {
_log.info(".find " + file.getId().getFileId() + ",token:" + _token.getToken()); // token: null
return Uni.createFrom().nullItem()
.emitOn(managedExecutor)
.onItem().transformToUni(item -> _service.name(file.getId()))
.invoke(name -> file.setName(name))
.onItem().transform(name -> file);
}

Related

Configure Spring Boot to use Custom Access Token converter

I'm trying to get user information from the access token provided by an Identity Provider. The Identity Provider that I'm using provides it's scope in the form of a string instead of a list because of which the DefaultAccessTokenConverter doesn't work for me. As a result I wish to extend it to a CustomAccessTokenConverter to override it's extractAuthentication() method. I'm using the following in my security config to make Spring use this custom class instead of the default one:
#Configuration
#EnableResourceServer
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Autowired
private CustomAccessTokenConverter customAccessTokenConverter;
// For validating the incoming access token and fetching user information from it
#Bean
public ResourceServerTokenServices createResourceServerTokenServices() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setCheckTokenEndpointUrl(*Introspection URL*);
tokenServices.setClientId(*Client ID*);
tokenServices.setClientSecret(*Client Secret*);
return tokenServices;
}
#Bean
public AccessTokenConverter accessTokenConverter() {
return customAccessTokenConverter;
}
}
But, Spring still uses the DefaultAccessTokenConverter. What am I doing wrong? Please help me out here.
Here is what my CustomAccessTokenConverter class looks like just for reference:
#Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
.
.
.
return new OAuth2Authentication(request, user);
}
}
I am using Spring Boot with the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
ResourceTokenServices allows us to use our own AccessTokenConverter.
Simply add the following to your security config:
#Bean
public ResourceServerTokenServices createResourceServerTokenServices() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setCheckTokenEndpointUrl(*Introspection URL*);
tokenServices.setClientId(*Client ID*);
tokenServices.setClientSecret(*Client Secret*);
// ADD THE NEXT LINE
tokenServices.setAccessTokenConverter(customAccessTokenConverter);
return tokenServices;
}

IllegalArgumentException with Spring AOP

This problem was first reported as Spring issue #24248.
I'm building an application with Spring AOP and I'm using aspects to modify some of my business objects. When I'm starting my application I get the following stacktrace (only the root is shown):
java.lang.IllegalArgumentException: methods with same signature get() but incompatible return types: [interface main.DomainObjectInterfaceA, interface main.DomainObjectInterfaceB]
at sun.misc.ProxyGenerator.checkReturnTypes(ProxyGenerator.java:712)
at sun.misc.ProxyGenerator.generateClassFile(ProxyGenerator.java:461)
at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:339)
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:639)
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557)
at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
at java.lang.reflect.WeakCache.get(WeakCache.java:127)
at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419)
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:123)
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:473)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:355)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:304)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:439)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1712)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:581)
... 10 more
I extracted this in a minimal code snippet from my application to reproduce this stacktrace.
Remark by kriegaex: I created an MCVE and pushed it to a GitHub repo.
My domain model consists of three classes:
package main;
public interface DomainObjectInterfaceA {
}
package main;
public interface DomainObjectInterfaceB{
}
package main;
public class DomainObjectImplementation implements DomainObjectInterfaceA, DomainObjectInterfaceB {
private int fieldToModifyThroughAspect = 0;
public int getFieldToModifyThroughAspect() {
return fieldToModifyThroughAspect;
}
public void setFieldToModifyThroughAspect( int aFieldToModify ) {
fieldToModifyThroughAspect = aFieldToModify;
}
}
Furthermore I have the following two Spring components:
package main;
#Component
public class ComponentImplementation implements ComponentInterfaceA<DomainObjectImplementation>, ComponentInterfaceB<DomainObjectImplementation> {
public DomainObjectImplementation get() {
return new DomainObjectImplementation();
}
}
package main;
#org.aspectj.lang.annotation.Aspect
#Component
public class Aspect {
#Pointcut( "execution(public DomainObjectImplementation main.ComponentImplementation.*(..))" )
public void anyPublicOperation() {
}
#AfterReturning( pointcut = "anyPublicOperation()", returning = "aObject" )
public Object get( Object aObject ) {
DomainObjectImplementation returningObject = ( DomainObjectImplementation ) aObject;
returningObject.setFieldToModifyThroughAspect( 5);
return returningObject;
}
}
The crucial part in reproducing the issue are the following two generic interfaces which are implemented by ComponentImplementation:
package main;
public interface ComponentInterfaceA<T extends DomainObjectInterfaceA> {
T get();
}
package main;
public interface ComponentInterfaceB<T extends DomainObjectInterfaceB> {
T get();
}
The above two interfaces share a method with the same signature, but a different generic type. ComponentImplementation implements those two interfaces with a class that satisfies both of those generic arguments.
The last two required classes are of course the Main class and the Spring Config class which are declared below:
package main;
#Configuration
#EnableAspectJAutoProxy
#ComponentScan( basePackageClasses = Config.class )
public class Config {
}
package main;
public class Main {
public static void main( String[] args ) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( Config.class );
ComponentImplementation bean = context.getBean( ComponentImplementation.class );
DomainObjectImplementation domainObject = bean.get();
System.err.println("Aspect modified get method = " + ( domainObject.getFieldToModifyThroughAspect() == 5));
}
}
I also tried to configure EnableAspectJAutoProxy with proxyTargetClass = true, but then the same exception is thrown with a slightly different stacktrace (namely the JDK proxy vs the Cglib)
The complete dependencies list:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aspectj/aspectjrt -->
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>
</dependencies>

How Can I Generate a POJO from an HTTP POST Request in a Spring Boot Application

I have an Angular 2 service that POSTs login data to a Spring Boot #RestContoller
The login request body:
{"_username": "test", "_password": "test"}
The Rest Controller:
#RestController
public class AuthControl {
#Autowired
AuthenticationService authenticationService;
#RequestMapping(value = "/someurl", method = RequestMethod.POST)
public LoginDto authenticate(HttpServletResponse response, LoginRequestDto requestDto) throws IOException, JSONException {
return authenticationService.authenticate(requestDto, response);
}
}
Jackson unmarshalls the request into this POJO
public class LoginRequestDto {
private String _username;
private String _password;
public LoginRequestDto() { }
public LoginRequestDto(String u, String p) {
this._username = u;
this._password = p;
}
// getters and setters
}
However, when Jackson unmarshalls the POJO the field values are set to null, rather than "test"/"test". As a result, the login attempt fails with a bad credentials message. I'm wondering if i'm missing a library or something that is resulting in the improper setting of these fields.
Dependencies from pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency><!-- implements Thymeleaf HTML5 parsing -->
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>
Tthe only exception logged is a UsernameNotFoundException thrown by my implmentation of Spring Security's UserDetailsService. Using the debugger, I can manually parse the values from the request and set the values into the POJO. After doing this, the request completes, so it seems clear that the issue is in the unmarshalling phase.
Any thoughts or suggestions would be greatly appreciated.
That is due to Jackson using the default constructor, by default, when deserializing. You need to use the #JsonCreator annotation to let jackson know which the constructor you want to use is and the the annotation #JsonProperty to let it know how to bind the params with the json properties.
public class LoginRequestDto {
private String _username;
private String _password;
public LoginRequestDto() { }
#JsonCreator
public LoginRequestDto(#JsonProperty("_username") String u,
#JsonProperty("_password") String p) {
this._username = u;
this._password = p;
}
// getters and setters
}
Have a look to the section Using constructors or factory methods at the jackson-annotations project.
Update your request mapping method specifying it consumes json and add the #RequestBody annotation to the method param
#RestController
public class AuthControl {
#Autowired
AuthenticationService authenticationService;
#RequestMapping(value = "/someurl", method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE })
public LoginDto authenticate(HttpServletResponse response, #RequestBody LoginRequestDto requestDto) throws IOException, JSONException {
return authenticationService.authenticate(requestDto, response);
}
}

Spring #RefreshScope is not working with #Component

I've the component class and pom.xml dependencies like below. The properties are never set and staying as null.
#Component
#RefreshScope
public class SecurityProperties1 {
#Value("${ad.url}")
public String adUrl;
#Value("${ad.manager.dn}")
public String managerDN;
#Value("${ad.manager.password}")
public String managerPassword;
#Value("${ad.search.base}")
public String searchBase;
#Value("${ad.user.filter}")
public String userFilter;
}
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>1.2.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-commons -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>1.1.4.RELEASE</version>
</dependency>
Also,
My Property source is like below
#Component
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
#Override
protected String resolvePlaceholder(String placeholder, Properties props) {
return DynamicProperty.getProperty(placeholder);
}
#Override
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
return DynamicProperty.getProperty(placeholder);
}
}
I had same problem. My solution: I added proxymode = default annotation
#Component
#RefreshScope(proxyMode = DEFAULT)
public class MyClass {
#Value("${test.value}")
private String testValue;
}
For newer version if somebody is facing this issue :
Make sure you have spring-cloud-starter-bootstrap dependency in classpath and also add spring.application.name property in your bootstrap.properties file
And annotated each class that is getting property from config server with #RefreshScope

Making Aspectj work on a Spring servlet bean

I am trying to get an aspectprofiler working on a Jersey servlet registered in a spring project. The aspectprofiler is loaded, but don't notice when methods within the Jersey servlet are run.
#EnableAutoConfiguration
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class App {
public static void main(final String[] args) {
final SpringApplicationBuilder sab = new SpringApplicationBuilder(ConsolidatedCustomerMasterApp.class);
sab.run(args);
}
#Bean
public ServletRegistrationBean jerseyServlet() {
final ServletRegistrationBean registration = new ServletRegistrationBean(new ServletContainer(), "/*");
registration.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, JerseyInitialization.class.getName());
return registration;
}
#Bean
public AspectProfiler profiler() {
return new AspectProfiler();
}
}
...
public class JerseyInitialization extends ResourceConfig {
public JerseyInitialization() {
packages("com.example.package");
}
...
package com.example.package;
//imports
#Path("/test")
public class RestService {
#GET
#Path("test")
#Produces(MediaType.TEXT_PLAIN)
public String test() {
return "Something";
}
}
...
#Aspect
public class AspectProfiler {
private static final DefaultApplicationProfiler PROFILER = new DefaultApplicationProfiler(
Arrays.<ProfilerOperator> asList(
new StatsdProfilerOperator(),
new LoggingProfilerOperator())
);
private static final String REST_MATCHER =
"execution(* com.example.package..*.*(..))";
#Around(REST_MATCHER)
public Object around(final ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("test");
return PROFILER.around(joinPoint);
}
}
On top of making the Jersey resource classes Spring #Components (and #ComponentScaning for them), you also need to make the ResourceConfig a Spring #Component also. You can see in the Spring Boot JerseyAutoConfigurer that it autowires the ResourceConfig, which it uses for the ServletContainer registration.
One thing to also note is that it creates its own ServletRegistrationBean
public ServletRegistrationBean jerseyServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(
new ServletContainer(this.config), this.path);
addInitParameters(registration);
registration.setName("jerseyServlet");
return registration;
}
When you declare your own, you are overriding this one. You are not adding any special functionality that is not already provided, so just leave the default. Any Jersey specific configurations can be added in the application.properties file or through code configurations.
As for dependencies, I'll just assume you have all the right dependencies. The following are what I used to test
<!-- all 1.2.7.RELEASE -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
See Also:
spring-boot-sample-jersey - from project samples
§26.2 JAX-RS and Jersey - from Spring Boot docs

Resources