Protecting REST API with OAuth2: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active - spring

I've been working for a few days to attempt to implement oauth2 protection on a REST API. I've tried a ton of different configurations but still haven't managed to get it to work.
I'm proving the code that I have right now, but I'm in no way married to this implementation. If you can show me some radically different way to accomplish what I want to accomplish, great.
My flow looks like this:
Client checks Auth Server, gets token.
Client sends token to Resource Server.
Resource Server uses Auth Server to make sure that token is valid.
The Auth Server works fine. I'm having trouble configuring the Resource Server.
Configs on Resource Server
Here's some of my configs. I have this bean:
Ouath Rest Template
#EnableOAuth2Client
#Configuration
#Import({PropertiesConfig.class}) //Imports properties from properties files.
public class OauthRestTemplateConfig {
#Bean
public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext oauth2ClientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2ResourceDetails(), oauth2ClientContext);
return template;
}
#Bean
OAuth2ProtectedResourceDetails oauth2ResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("theOauth");
details.setClientId("clientID");
details.setClientSecret("SecretKey");
details.setAccessTokenUri("https://theAuthenticationServer.com/oauthserver/oauth2/token");
details.setUserAuthorizationUri("https://theAuthenticationServer.com/oauthserver/oauth2/token");
details.setTokenName("oauth_token");
details.setPreEstablishedRedirectUri("http://localhost/login");
details.setUseCurrentUri(true);
return details;
}
}
Security Config
I use that bean in my main security config in Resource Server:
#Slf4j
#Configuration
#EnableWebSecurity
#EnableOAuth2Client
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true, proxyTargetClass = true)
#Import({PropertiesConfig.class, OauthRestTemplateConfig.class})
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("oAuth2RestTemplate")
private OAuth2RestTemplate oAuth2RestTemplate;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.accessDecisionManager(accessDecisionManager()) //This is a WebExpressionVoter. I don't think it's related to the problem so didn't include the source.
.antMatchers("/login").permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().authenticated();
http
.exceptionHandling()
.authenticationEntryPoint(delegatingAuthenticationEntryPoint());
http
.addFilterBefore(new OAuth2ClientContextFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(oauth2ClientAuthenticationProcessingFilter(), OAuth2ClientContextFilter.class)
;
}
private OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter() {
OAuth2ClientAuthenticationProcessingFilter
daFilter = new OAuth2ClientAuthenticationProcessingFilter("/api/**");
daFilter.setRestTemplate(oAuth2RestTemplate);
daFilter.setTokenServices(inMemoryTokenServices());
return daFilter;
}
private DefaultTokenServices inMemoryTokenServices() {
InMemoryTokenStore tok = new InMemoryTokenStore();
DefaultTokenServices tokenService = new DefaultTokenServices();
tokenService.setTokenStore(tok);
return tokenService;
}
}
Extra stuff in Security Config
Aaand, some of the beans which I believe are less relevant, but here they are in case you need them:
#Bean
public DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint() {
LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> matchers =
Maps.newLinkedHashMap();
//Match all HTTP methods
matchers.put(new RegexRequestMatcher("\\/api\\/v\\d+\\/.*", null), oAuth2AuthenticationEntryPoint());
matchers.put(AnyRequestMatcher.INSTANCE, casAuthenticationEntryPoint());
DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(matchers);
entryPoint.setDefaultEntryPoint(casAuthenticationEntryPoint());
return entryPoint;
}
#Bean(name = "casEntryPoint")
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(casUrl + "/login");
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
Error
Resource Server starts up just fine. Client gets its auth token from theAuthenticationServer.com and sends it in the request header to an api url. And I get the following error:
HTTP Status 500 - Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' 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.
Exception report
Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' 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.
The server encountered an internal error that prevented it from fulfilling this request.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' 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.
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:355)
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
com.sun.proxy.$Proxy26.getAccessToken(Unknown Source)
org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:94)
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
root cause
<pre>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.
org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
com.sun.proxy.$Proxy26.getAccessToken(Unknown Source)
org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:94)
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
I've tried a lot of different configs, looked up a ton of resources online, and I've gotten nowhere. Am I using the right classes? Any idea what configs I might need to change?

An even easier way to enable the request context listener is to add a bean annotation into your app.
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}

I ended up resolving this thing after looking into Spring documentation.
It turned out that the scope context didn't actually exist in my app, because I hadn't initialized it.
I initialized it by adding this listener:
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>

I'm proving the code that I have right now, but I'm in no way married
to this implementation. If you can show me some radically different
way to accomplish what I want to accomplish, great
If your main problem is implementing the Resource Server and also, you are open to totally different solutions, you can use Spring Boot's resource server auto configurations. This way you would have a ResourceServerConfiguration such as following:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
// you can put your application specific configurations here
// here i'm just authenticating every request
}
}
With an application.yml config file in your src/main/resources:
security:
oauth2:
client:
client-id: client
client-secret: secret
resource:
token-info-uri: http://localhost:8888/oauth/check_token
You should add your client-id, client-secret and token-info-uri there. token-info-uri is the endpoint on Authorization Server that our resource server is going to consult about the validity of passed Access Tokens.
With these arrangements, if the client fire a request to, say, /api/greet API:
GET /api/greet HTTP/1.1
Host: localhost:8080
Authorization: bearer cef63a29-f9aa-4dcf-9155-41fb035a6cdb
Our resource server will extract the Bearer access token from the request and send the following request to the authorization server to validate the access token:
GET /oauth/check_token?token=cef63a29-f9aa-4dcf-9155-41fb035a6cdb HTTP/1.1
Host: localhost:8888
Authorization: basic base64(client-id:client-secret)
If token was valid, authorization server send a 200 OK response with a JSON body like following:
{"exp":1457684735,"user_name":"me","authorities":["ROLE_USER"],"client_id":"client","scope":["auth"]}
Otherwise, it will return a 4xx Client Error.
This was a maven project with a pom.xml like following:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</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.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
</dependencies>
And a typical Application class:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You can check out the spring boot documentation on resource server auto configurations here.

I believe that the root of issue is that you create the OAuth2ClientAuthenticationProcessingFilter and OAuth2ClientContextFilter with new operator.
If you look at the stacktrace
org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
com.sun.proxy.$Proxy26.getAccessToken(Unknown Source)
org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:94)
there's a chain how it goes from OAuth2ClientAuthenticationProcessingFilter to JdkDynamicAopProxy and tries to get the bean. And I can assume because of that bean was created out of Spring container, it can't get the bean from the session scope.
Try to wrap your filters into #Bean annotation so to put them into context. Also, i believe it worth being set the correct scope: the request would match best here.

I faced the same problem when using spring-boot 1.4.1 with spock-spring 1.1-groovy-2.4-rc-2. The easiest way to fix it is to use Spock 1.0.
There is a bug reported already:
https://github.com/spockframework/spock/issues/655

public class ContextAwareCallable implements Callable {
private Callable<T> task;
private RequestAttributes context;
public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
this.task = task;
this.context = cloneRequestAttributes(context);
}
#Override
public T call() throws Exception {
if (context != null) {
RequestContextHolder.setRequestAttributes(context);
}
try {
return task.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
private RequestAttributes cloneRequestAttributes(RequestAttributes requestAttributes) {
RequestAttributes clonedRequestAttribute = null;
try {
clonedRequestAttribute =
new ServletRequestAttributes(((ServletRequestAttributes) requestAttributes).getRequest(),
((ServletRequestAttributes) requestAttributes).getResponse());
if (requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST).length > 0) {
for (String name : requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST)) {
clonedRequestAttribute.setAttribute(name,
requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST),
RequestAttributes.SCOPE_REQUEST);
}
}
return clonedRequestAttribute;
} catch (Exception e) {
return requestAttributes;
}
}
}
Then create executor
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
#Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
#Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
}

Related

Keycloak: Failed to load URLs in Spring Boot Application

I'm currently trying to develop a Spring Boot Rest Api which is secured with keycloak.
I get an error when I try to call a api which the user has to be identify.
The error message is following:
2020-04-10 16:09:00.324 WARN 44525 --- [nio-8080-exec-7]
o.keycloak.adapters.KeycloakDeployment : Failed to load URLs from
https://{{keycloakserver}}.de/auth/realms/{{realm}}/.well-known/openid-configuration
java.lang.RuntimeException: java.lang.RuntimeException: Stub!
at org.keycloak.adapters.KeycloakDeployment.getClient(KeycloakDeployment.java:327) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.KeycloakDeployment.getOidcConfiguration(KeycloakDeployment.java:219) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.KeycloakDeployment.resolveUrls(KeycloakDeployment.java:178) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.KeycloakDeployment.getRealmInfoUrl(KeycloakDeployment.java:232) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.rotation.AdapterTokenVerifier.createVerifier(AdapterTokenVerifier.java:107) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.rotation.AdapterTokenVerifier.verifyToken(AdapterTokenVerifier.java:47) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.BearerTokenRequestAuthenticator.authenticateToken(BearerTokenRequestAuthenticator.java:103) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.BearerTokenRequestAuthenticator.authenticate(BearerTokenRequestAuthenticator.java:88) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.RequestAuthenticator.authenticate(RequestAuthenticator.java:67) [keycloak-adapter-core-9.0.2.jar:9.0.2]
at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.attemptAuthentication(KeycloakAuthenticationProcessingFilter.java:154) [keycloak-spring-security-adapter-9.0.2.jar:9.0.2]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) [keycloak-spring-security-adapter-9.0.2.jar:9.0.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:97) [spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) [spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
I don't know what Failed to load URLs from means. I can access this side when I click on the link and the configuration file is shown.
Setup
Keycloak:
Keycloak Server is in the web, so no localhost.
I have a realm (test-realm) created
I have a client (test-client) created
I have a user (test-user) created
I have a role in the client (ADMIN) created
I have assigned the role (ADMIN) to the user (test-user)
The client protocol for the client is openid-connect and the access type is confidental.
Spring Boot:
The Spring Boot rest application is running on localhost:8080.
I added in the applications.properties following keycloak configs.
keycloak.realm={{test-realm}}
keycloak.auth-server-url = https://{{keycloakserver}}.de/auth
keycloak.resource = {{test-client}}
keycloak.ssl-required=external
keycloak.bearer-only=true
keycloak.principal-attribute=preferred_username
keycloak.use-resource-role-mappings = true
To make sure the test-user can only access one api call I use following config.
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/getTest")
.hasRole("ADMIN")
.anyRequest()
.authenticated();
}
Tests
When I call http://localhost:8080/getTest with Postman I get a correct 401 Unauthorized.
Then I called the same URL with Authorization and the access token of the logged in test-user.
With this second call I get the error message above.
Does anybody know anything about this?
If I missed a config value that you need to know, just ask.
Thanks for your help.
Edit:
SecurityConfig.java
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
/**
* Created by johannes on 07.04.20 for test App.
*/
#EnableWebSecurity
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
#Configuration
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(
new SessionRegistryImpl());
}
#Bean
#Primary
public KeycloakConfigResolver keycloakConfigResolver(KeycloakSpringBootProperties properties) {
return new CustomKeycloakSpringBootConfigResolver(properties);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/getTest")
.hasRole("ADMIN")
.anyRequest()
.authenticated();
}
}
CustomKeycloakSpringBootConfigResolver:
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
import org.springframework.context.annotation.Configuration;
/**
* Created by johannes on 10.04.20 for test App.*/
#Configuration
public class CustomKeycloakSpringBootConfigResolver extends KeycloakSpringBootConfigResolver {
private final KeycloakDeployment keycloakDeployment;
public CustomKeycloakSpringBootConfigResolver(KeycloakSpringBootProperties properties) {
keycloakDeployment = KeycloakDeploymentBuilder.build(properties);
}
#Override
public KeycloakDeployment resolve(HttpFacade.Request facade) {
return keycloakDeployment;
}
}
TestController.java (this is just the test getter):
#GetMapping("/getTest")
public #ResponseBody ResponseEntity getTest() {
return ResponseEntity.status(ResponseValues.ITEMDELETEFAILED.getResponseCode()).body(ResponseValues.ITEMDELETEFAILED.getResponseMessage());
}
Request was made with postman, this is the code:
curl --location --request GET 'http://localhost:8080/getTest' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUI...' \
--header 'Cookie: JSESSIONID=41E8E82178FA181817...'
In my case: Realm name was not correct. It is case sensitive. It worked when I change it to:
keycloak.realm = demo
I had written
keycloak.realm = Demo
I have the same problem, and I try a lot to find the answer at google, stackoverflow etc...
Finally, I catch the clue, to make it work, just remove the path of the keycloak.auth-server-url as http://192.168.0.119:8080 instead of http://192.168.0.119:8080/auth or something else.
... : Loaded URLs from http://192.168.0.119:8080/realms/spmia-realm/.well-known/openid-configuration
keycloak.realm=spmia-realm
keycloak.auth-server-url=http://192.168.0.119:8080
keycloak.ssl-required=external
keycloak.resource=ostock
keycloak.credentials.secret=FnUBprsgArHa7PkmR9HPWeXY0nJ22Ks1
keycloak.use-resource-role-mappings=true
keycloak.bearer-only=true
...
keycloak:
image: quay.io/keycloak/keycloak:18.0
restart: on-failure
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_DB: postgres
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak
KC_DB_URL: jdbc:postgresql://database:5432/keycloak
command:
- "start-dev"
depends_on:
database:
condition: service_healthy
ports:
- "8080:8080"
...
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>18.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
...
I had the same issue, for me it was also connected to a flawed config value.
keycloak.auth-server-url = https://{{keycloakserver}}.de/auth
First I forgot the /auth and then I used http instead of https.
I have been through this issue and solved it as follows
1- make sure of your yaml or properties file in the spring boot application
the following property should be configured based on the keycloak version you use. in my case i used v 17.0.1 and the issue arose because i added it this way
keycloak.auth-server-url=http://localhost:8080/auth rather than this keycloak.auth-server-url=http://localhost:8080
but if you use a lower version than 17 it must be
keycloak.auth-server-url=http://localhost:8080/auth
please make sure that you mentioned the right host and the right port,i just mentioned localhost and the port 8080 because this my case.
I solved the Issue own my own.
I noticed that my spring boot sdk was a android sdk. I don't know how this could happen.
I changed it to the java sdk, and it worked like a charm.
I had a similar issue. In my case, I didn't recognize that there was a single whitespace at the end of my "keycloak.auth-server-url" in my application.properties file.
This way, the keycloak adapter tried to access an invalid URL.
Maybe this helps someone who has a similar issue and finds this thread.
try to change hasRole to hasAuthority or add prefix ROLE_ before the role like this hasRole("ROLE_ADMIN")
I had entered the wrong value for keycloak.auth-server-url.

Create a spring session in amqp rpc client

I developped spring remoting amqp rpc applications.
That's works well for methods that don't use bean with Scope SESSION.
For the other methods, the client can't use spring session, and I get this exception
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.userSession': Scope 'session' 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(AbstractBeanFactory.java:362) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at io.kzreactive.akwtype.akwtypeback.common.service.UserSession$$EnhancerBySpringCGLIB$$55d53e95.setUser(<generated>) ~[classes/:na]
at io.kzreactive.akwtype.akwtypeback.engine.service.AppService.login(AppService.kt:30) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
at org.springframework.remoting.support.RemoteInvocation.invoke(RemoteInvocation.java:215) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.remoting.support.DefaultRemoteInvocationExecutor.invoke(DefaultRemoteInvocationExecutor.java:39) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at io.kzreactive.akwtype.akwtypeback.gateway.rabbitmq.SessionDefaultRemoteInvocationExecutor.invoke(RabbitMQSession.kt:48) ~[classes/:na]
at org.springframework.remoting.support.RemoteInvocationBasedExporter.invoke(RemoteInvocationBasedExporter.java:78) [spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.remoting.support.RemoteInvocationBasedExporter.invokeAndCreateResult(RemoteInvocationBasedExporter.java:114) [spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter.onMessage(AmqpInvokerServiceExporter.java:80) [spring-amqp-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1457) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1348) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1324) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1303) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:785) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:769) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:77) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1010) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at java.base/java.lang.Thread.run(Thread.java:844) ~[na:na]
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.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:55) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:350) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
... 24 common frames omitted
So I would create and use a spring session over rabbit mq
First, I managed to pass the sessionId in RPC call
on the server I add an attribute
class SessionDefaultRemoteInvocationFactory : DefaultRemoteInvocationFactory() {
override fun createRemoteInvocation(methodInvocation: MethodInvocation?): RemoteInvocation {
return super.createRemoteInvocation(methodInvocation)
.apply { addAttribute("sessionId", RequestContextHolder.currentRequestAttributes().sessionId) }
}
}
on the client I can read it
class SessionDefaultRemoteInvocationExecutor : DefaultRemoteInvocationExecutor() {
override fun invoke(invocation: RemoteInvocation?, targetObject: Any?): Any {
if (invocation is RemoteInvocation) {
invocation.getAttribute("sessionId")?.let {
val sessionId = it.toString()
SecurityContextHolder.getContext().authentication = AnonymousAuthenticationToken(sessionId,
"anonymousUser", listOf(SimpleGrantedAuthority("ROLE_ANONYMOUS")))
val attr = RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes // <= ERROR here
attr.request.getSession(true)
}
}
return super.invoke(invocation, targetObject)
}
}
but I don't manage to use it to create a spring session
How can I create a spring session in this NON http context
I tried to create a request context listener
#Configuration
#WebListener
class MyRequestContextListener : RequestContextListener()
but same error
For the moment I bypass the spring session and I inject a bean with the same behavior
#Component
#Scope("singleton")
class EngineThread(var engineSession: ThreadLocal<EngineSession> = ThreadLocal()) : IEngineSession by engineSession.getOrSet( { EngineSession() })
#Component
#Profile("engine & !test")
class SessionDefaultRemoteInvocationExecutor(val engineThread: EngineThread) : DefaultRemoteInvocationExecutor() {
val engineSessionStore : MutableMap<String, EngineSession> = mutableMapOf()
override fun invoke(invocation: RemoteInvocation?, targetObject: Any?): Any {
if (invocation is RemoteInvocation) {
invocation.getAttribute(SESSION_ID)?.let {
val sessionId = it.toString()
SecurityContextHolder.getContext().authentication = AnonymousAuthenticationToken(sessionId,
"anonymousUser", listOf(SimpleGrantedAuthority("ROLE_ANONYMOUS")))
if (! engineSessionStore.contains(sessionId)) {
engineSessionStore.put(sessionId, EngineSession())
}
engineThread.engineSession.set(engineSessionStore.getValue(sessionId))
}
}
return super.invoke(invocation, targetObject)
}
}
The job is done but I'm not very well with this solution
(I will add clean by softreference… but all this work is reinvent session instead of use spring session)

Is there a way to refresh the RequestMappingHandlerMapping?

I'm working on a Spring Boot Application - and adding #RestController beans after the RequestMapping has already been initialized.
I can see the new Controller beans are in the web context, but since they were added after, there is no mapping when the application finishes starting.
I was hoping I could call "afterPropertiesSet()" on the RequestMappingHandlerMapping, but I end up with ambiguous mappings [I assume calling the method re-registers all beans with mappings]
Is there a way to register certain beans, or refresh the RequestMappingHandlerMapping?
Caused by: java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:52765/error': {public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest), public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)}
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:367) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:314) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:61) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:352) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1160) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:940) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
... 26 common frames omitted
Asking another way -
I have a #RestController in a separate project that is not referenced by the configuration project.
I have a third project that references both the configuration project and the 'module' project with the #RestController.
How do I ensure the #RestController in the module project is added to the application context before the RequestMappingHandlerMapping is configured?
I created a custom annotation, thinking maybe I could use it somehow.. See the projects here -
Main platform - https://github.com/savantly-net/sprout-platform/tree/development/
Autoconfiguration project - https://github.com/savantly-net/sprout-platform/tree/development/spring/sprout-spring-boot-starter
Module Project - https://github.com/savantly-net/sprout-platform/tree/development/modules/sprout-wiki-module
Full Web Project - https://github.com/savantly-net/garden/tree/development
If you build this project, you will see the wiki module beans are added to the application context, but the #RestController is never mapped.
Any thoughts?
I changed the module declaration in the web app from a #Bean to an #Import and it resolved the issue. I'm assuming this bumped up the order of the beans in the module project.
Before -
#SpringBootApplication
public class GardenWebApplication {
public static void main(String[] args) {
SpringApplication.run(GardenWebApplication.class, args);
}
#Bean
public WikiModule wikiModule() {
return new WikiModule();
}
}
After -
#SpringBootApplication
#Import(WikiModule.class)
public class GardenWebApplication {
public static void main(String[] args) {
SpringApplication.run(GardenWebApplication.class, args);
}
}

Spring boot Integration unit testing NoSuchBeanDefinitionException exception

I created a sample IntegrationFlow as shown below:
#Configuration
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
public class RegisterHostFlow {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
#MessagingGateway
public interface RegisterHostGateway{
#Gateway(requestChannel = "registerHostInputChannel")
Host registerHost(Host host);
}
#Bean
public IntegrationFlow httpInboundGatewayFlow() {
return IntegrationFlows.from("registerHostInputChannel")
.handle((host, headers) -> {
return host;
})
.enrich(e -> e
.requestPayload(Message::getPayload)
.property("uuid", "34563456345634563456")
.property("id", "1234")
)
.get();
}
}
I am calling this from a spring MVC controller as below:
RegisterHostFlow.RegisterHostGateway registerHostGateway = applicationContext.getBean(RegisterHostFlow.RegisterHostGateway.class);
Host host1 = registerHostGateway.registerHost(host);
When I write a unit test to do some sanity testing as shown below, application fails to load with the error, NoSuchBeanException:
#RunWith(SpringRunner.class)
#WebMvcTest(HostController.class)
#EnableIntegration
#IntegrationComponentScan
public class HostControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void registerHost_passedInHost_returnJson() throws Exception {
this.mvc.perform(post("/hostservice/v1/hosts").contentType(MediaType.APPLICATION_JSON). content('someJsonStringGoesHere'))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
}
Below is the exception I see:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'registerHostInputChannel' available
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:155)
Any pointers to how to have ApplicationContext autowire the integration beans when in test mode?
According the #WebMvcTest JavaDocs:
* Typically {#code #WebMvcTest} is used in combination with {#link MockBean #MockBean} or
* {#link Import #Import} to create any collaborators required by your {#code #Controller}
* beans.
You have to make your test class config like:
#RunWith(SpringRunner.class)
#WebMvcTest(HostController.class)
#Import(RegisterHostFlow.class)
public class HostControllerTest {
So, this way you have an MVC slice and collaborator in face of Spring Integration and target flow configuration.

Spring Boot startup throws Exception: No thread-bound request found for request scoped bean

I'm trying to build a Proof-of-Concept using Spring Boot (1.3.5) with Jersey. My pom has the spring-boot-starter-jersey artifact listed as a dependency.
With only singleton beans defined, it works fine. However, I've now tried to inject request-scoped beans into a singleton bean and am running into the following exception:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.jndi': 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(AbstractBeanFactory.java:355) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.getTarget(CglibAopProxy.java:687) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:637) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at $java.util.Properties$$EnhancerBySpringCGLIB$$1a020092.size(<generated>) ~[na:na]
at org.springframework.jndi.JndiTemplate.createInitialContext(JndiTemplate.java:133) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.jndi.JndiTemplate.getContext(JndiTemplate.java:103) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:85) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.ejb.access.AbstractRemoteSlsbInvokerInterceptor.lookup(AbstractRemoteSlsbInvokerInterceptor.java:99) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.ejb.access.AbstractSlsbInvokerInterceptor.refreshHome(AbstractSlsbInvokerInterceptor.java:121) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.ejb.access.SimpleRemoteSlsbInvokerInterceptor.refreshHome(SimpleRemoteSlsbInvokerInterceptor.java:162) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.ejb.access.AbstractSlsbInvokerInterceptor.afterPropertiesSet(AbstractSlsbInvokerInterceptor.java:108) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean.afterPropertiesSet(SimpleRemoteStatelessSessionProxyFactoryBean.java:101) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
... 21 common frames omitted
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.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
... 40 common frames omitted
According to what I read, I need to enable the RequestContextListener, which I have done, but it hasn't made any difference:
#Configuration
public class AppConfig {
#Bean(name="jndi")
#Scope(scopeName=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
public Properties getJNDIContext(){
Properties p = new Properties();
p.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces" );
p.setProperty("java.naming.provider.url", "jnp://localhost:1099");
p.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.security.jndi.JndiLoginInitialContextFactory");
p.setProperty(Context.SECURITY_PRINCIPAL,"test" );
p.setProperty(Context.SECURITY_CREDENTIALS,"12345678" );
return p;
}
#Bean
public FactoryBean<?> getAthleteManagerFactory(#Qualifier("jndi") Properties p){
SimpleRemoteStatelessSessionProxyFactoryBean factory = new SimpleRemoteStatelessSessionProxyFactoryBean();
String beanName = "zone.jndi.ejb3.RemoteAthleteManager";
factory.setJndiName(beanName);
factory.setBusinessInterface(AthleteManager.class);
factory.setJndiEnvironment(p);
return factory;
}
#Bean
public RequestContextListener requestContextListener(){
return new RequestContextListener();
}
}
And my controller is fairly simple:
#Component
#Path("/athlete")
public class AthleteController {
#Autowired
private AthleteManager athleteManager;
....
....
}
I suspect the error is that the factory is trying to instantiate the object using a proxied request-scoped bean to inject into my Controller, and the request scope doesn't yet exist.
Is there a way to resolve this problem? I would have expected the proxyMode to handle the issue, but it doesn't seem to make any difference. I get similar (albeit slightly different) exception stack traces with or without the proxyMode enabled.
If I mark my factory as Lazy, it still doesn't make a difference as Spring needs to instantiate it (and its object) in order to autowire my controller class.
Is there any way to do this? My need is for a filter to override the credentials in the getJNDIContext() properties object at each call.
I finally found a solution to my issue, although I don't particularly like it. For starters, I need to delay the JNDI bean lookup/initialization from startup so that the container doesn't try to populate the JNDI properties until they are actually available (only at the Request time). I also noticed that I am unable to scope the JNDI to the request scope, and consequently have to look it up every time (or the authentication credentials may be incorrect).
#Bean(name="jndi")
#Scope(scopeName=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
public Properties getJNDIContext(){
Properties p = new Properties();
p.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces" );
p.setProperty("java.naming.provider.url", "jnp://localhost:1099");
p.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.security.jndi.JndiLoginInitialContextFactory");
return p;
}
#Bean
public FactoryBean<?> getLoginManagerFactory(#Qualifier("jndi") Properties p){
return getEjbFactoryBean(LoginManager.class, p );
}
/**
* Helper method to setup the factory
* #param clazz
* #param jndi
* #return
*/
private SimpleRemoteStatelessSessionProxyFactoryBean getEjbFactoryBean(Class clazz, Properties jndi){
SimpleRemoteStatelessSessionProxyFactoryBean factory = new SimpleRemoteStatelessSessionProxyFactoryBean();
// need to delay lookup since security params are only available in a request thread
factory.setLookupHomeOnStartup(false);
// refresh home in case EJB server is restarted
factory.setRefreshHomeOnConnectFailure(true);
// do not cache - need to lookup every time due to changing security params. They are only set upon initial bean lookup
factory.setCacheHome(false);
// set the JNDI properties
factory.setJndiEnvironment(jndi);
String beanName = REMOTE_EJB_PREFIX + clazz.getSimpleName();
factory.setJndiName(beanName);
factory.setBusinessInterface(clazz);
return factory;
}
Finally, I set up a filter to specify my credentials on every request:
#Provider
public class SecurityFilter implements ContainerRequestFilter {
#Autowired #Qualifier("jndi")
private Properties userCreds;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// extract username/password from basic auth
String[] auth = decode( requestContext.getHeaderString(HttpHeaders.AUTHORIZATION) );
userCreds.setProperty(Context.SECURITY_PRINCIPAL, auth[0] );
userCreds.setProperty(Context.SECURITY_CREDENTIALS, auth[1] );
}
}
Conceptually, this works, but it is very inefficient. I would like to find a solution that allows me to do the same, but at least scope my bean at the request level, so that it isn't retrieved every time.

Resources