Spring Security OAuth2 client_credentials + Webclient without webserver - spring-boot

I have an application, that currently uses RestTemplate + OAuth2. The application itself is NOT a Spring MVC app, so for example no ports are open (no #GetMapping what so ever).
I am migrating from Spring Security OAuth 2.x to Spring Security 5.2.x. using this guide:
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
Config is:
#Configuration
public class WebClientConfig {
#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
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("regid");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
}
application.properties:
spring.security.oauth2.client.registration.regid.provider=regid
spring.security.oauth2.client.registration.regid.client-id=java-web
spring.security.oauth2.client.registration.regid.client-secret=secret
spring.security.oauth2.client.registration.regid.authorization-grant-type=client_credentials
spring.security.oauth2.client.provider.regid.token-uri=https://...token
spring.security.oauth2.client.provider.regid.jwk-set-uri=https://..certs
The problem is:
This app runs on a server that has port 8080 already allocated.
Previous version (Spring Oauth2 + RestTemplate) did NOT open any ports, especially 8080.
It seems that I can not use WebClient without making this app a webapp, listening on port 8080, which I cannot open, and I don't want to change to a random port to avoid collision. The app just simply does not need any ports to be opened.
I've tried several things, for example:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
<exclusion>
<artifactId>tomcat-embed-core</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
<exclusion>
<artifactId>tomcat-embed-websocket</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
</exclusions>
</dependency>
And also:
spring.main.web-application-type=none
etc., but every single time I've encountered a "random" NoClassDefFound javax.servlet.*, or: "Could not autowire. No beans of 'x' type found."
How can I setup the app:
using newest Spring Security with OAuth2
using WebClient
not being a webapp itself
?
thank you

Related

Use tomcat instead of netty for rscket

I want to leverage on servlets and filters so I want to use tomcat and, in general, servlet 3.1 to handle the communication.
I tried to do the following:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-reactor-netty</artifactId>
</exclusion>
</exclusions>
It works bringing up a tomcat but I lost the endpoint!
Here's what I do to register it:
spring.rsocket.server:
transport: websocket
mapping-path: /topics
and:
#Configuration
public class RSocketConfig {
#Bean
public Mono<RSocketRequester> rSocketRequester(
RSocketStrategies rSocketStrategies,
RSocketProperties rSocketProps) {
return RSocketRequester.builder()
.rsocketStrategies(rSocketStrategies)
.connectWebSocket(getURI(rSocketProps));
}
private URI getURI(RSocketProperties rSocketProps) {
return URI.create(String.format("ws://localhost:%d%s",
rSocketProps.getServer().getPort(), rSocketProps.getServer().getMappingPath()));
}

Configure Client Credentials Flow with spring gateway and Oauth2

I have some problems with the configuration of the Client Credentials flow in my Client app (My Spring Gateway).
My Authorization server is functional and tests with Postman without any problem.
But in my Client application, it seems that the oauth2 configuration is broken without error of compilation.
When I call a protected resource on my server resource, my client application seems to attempt to call an URL in its base and not the authorization server.
See the code
My configuration file:
security:
oauth2:
client:
registration:
apigateway:
provider: apigateway
client-id: apigateway
client-secret: password
scope: generate_token,read,write
authorization-grant-type: client_credentials
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
provider:
apigateway:
token-uri: https://localhost:9001/oauth/token
My Client dependency:
<dependencies>
<!-- ************ SPRING DEPENDENCIES ************ -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</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-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
</dependencies>
Configuration of my WebClient:
public class WebClientSecurityCustomizer implements WebClientCustomizer {
private ServerOAuth2AuthorizedClientExchangeFilterFunction securityExchangeFilterFunction;
public WebClientSecurityCustomizer(
ServerOAuth2AuthorizedClientExchangeFilterFunction securityExchangeFilterFunction) {
this.securityExchangeFilterFunction = securityExchangeFilterFunction;
}
#Override
public void customize(WebClient.Builder webClientBuilder) {
SslProvider sslProvider = SslProvider.builder().sslContext(
SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)
)
.defaultConfiguration(SslProvider.DefaultConfigurationType.NONE).build();
TcpClient tcpClient = TcpClient.create().secure(sslProvider);
HttpClient httpClient = HttpClient.from(tcpClient);
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpClient);
webClientBuilder.clientConnector(httpConnector);
webClientBuilder.filters((filterFunctions) -> {
if (!filterFunctions.contains(this.securityExchangeFilterFunction)) {
filterFunctions.add(0, this.securityExchangeFilterFunction);
}
});
}
}
#Configuration
public class WebClientSecurityConfiguration {
#Bean
public WebClientSecurityCustomizer webClientSecurityCustomizer(
ReactiveClientRegistrationRepository clientRegistrations) {
// Provides support for an unauthenticated user such as an application
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
// Build up a new WebClientCustomizer implementation to inject the oauth filter
// function into the WebClient.Builder instance
return new WebClientSecurityCustomizer(oauth);
}
/**
* Helper function to include the Spring CLIENT_REGISTRATION_ID_ATTR_NAME in a
* properties Map
*
* #param provider - OAuth2 authorization provider name
* #return consumer properties Map
*/
public static Consumer<Map<String, Object>> getExchangeFilterWith(String provider) {
return ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(provider);
}
}
My caller on the resource:
return webClientBuilder.build().get().uri(uri+"{accessToken}", accessToken)
.attributes(
WebClientSecurityConfiguration.getExchangeFilterWith("apigateway"))
.retrieve()
.bodyToMono(String.class)
.flatMap(response -> {
ServerHttpRequest request = exchange.getRequest().mutate()
.header(jwtHeader, String.format("%s %s", jwtPrefix, response))
.build();
return chain.filter(exchange.mutate().request(request).build());
});
}
And to finish, the error generated in the client application (it seems that Authorization and resource serve don't receive request):
2019-12-02 13:53:50.543 ERROR 11492 --- [ctor-http-nio-2] a.w.r.e.AbstractErrorWebExceptionHandler : [405f3f3c] 500 Server Error for HTTP GET "/oauth2/authorization/apigateway"
java.lang.IllegalArgumentException: Invalid Authorization Grant Type (client_credentials) for Client Registration with Id: apigateway
at org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver.authorizationRequest(DefaultServerOAuth2AuthorizationRequestResolver.java:156) ~[spring-security-oauth2-client-5.2.1.RELEASE.jar:5.2.1.RELEASE]
Thanks a lot for your help.
Add the below-mentioned class to your spring boot project. Then, it works properly without the above issue.
You need to extend "WebSecurityConfigurerAdapter" for any custom security class.
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/**");
}
}

application.yaml spring.activemq.broker-url not set

I'm trying to set my spring app to listen to a JMS queue.
I try to set the broker-url in my application.yml but it always seems to default back to "localhost:61616".
The application.yml file is loaded from another application, but I don't think that matters since other properties in the file are read (the name of the queues for example)
Here is the message I get :
o.a.a.t.failover.FailoverTransport;Failed to connect to [tcp://localhost:61616] after: 40 attempt(s) continuing to retry.
What I tried
I've tried following the answer to this question : Camel ActiveMQ + Spring boot not reading spring activemq configurations
Which is my exact issue
But when I try adding the dependency and creating that class I get this error :
Parameter 0 of method createComponent in xxx.xxxxx.xxxx.configuration.ActiveMQComponentConfig required a bean of type 'javax.jms.ConnectionFactory' that could not be found.
I'm quite new to Spring boot / ActiveMQ and don't really know what to do about this.
Here's the relevant part of my pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath />
</parent>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-camel</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
</exclusion>
</exclusions>
</dependency>
And my application.yml :
spring:
aop:
proxy-target-class: true
cache:
ehcache:
config: classpath:ehcache.xml
activemq:
broker-url: tcp://foo:12345
pool:
enabled: true
max-connections: 5
Any help would be greatly appreciated, I already spent a fair amount of time on this and not making any progress
This is what I ended up doing to get it to work again. I still don't know why Spring's autoconfig stopped working but that solved it
#Configuration
public class JmsConfig {
#Value("${spring.activemq.broker-url}")
String BROKER_URL;
#Value("${spring.activemq.user}")
String BROKER_USERNAME;
#Value("${spring.activemq.password}")
String BROKER_PASSWORD;
#Bean
public ActiveMQConnectionFactory connectionFactory(){
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(BROKER_URL);
connectionFactory.setUserName(BROKER_USERNAME);
connectionFactory.setPassword(BROKER_PASSWORD);
return connectionFactory;
}
#Bean
public JmsTemplate jmsTemplate(){
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(connectionFactory());
return template;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrency("1-1");
return factory;
}
}

Decode Spring Boot 2.1 OAuth2 encoded JWT on Resource Server

Trying to upgrade existing resource services from Spring Boot 1.x to 2.x. Spring Security 4.5 is running on the authentication server and encodes JWT tokens like this:
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
A resource server upgraded to Spring Boot 2.1.3.RELEASE throws this error:
OAuth2AuthenticationProcessingFilter:165 :
Authentication request failed: error="invalid_token",
error_description="Invalid access token:****..."
The log reveals that the OAuth2AuthenticationProcessingFilter is using the MappingJackson2HttpMessageConverter to extract the JWT token. Spring Security auto-configuration should provide the JwtAccessTokenConverter bean instead of the MappingJackson2HttpMessageConverter bean since there is a key-value in my properties file:
security:
oauth2:
resource:
jwt:
key-value: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
Here is the Spring Security ResourceServerTokenServiceConfiguration class that should detect it. The property matches "security.oauth2.resource.jwt.key-value".
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth JWT Condition");
Environment environment = context.getEnvironment();
String keyValue = environment
.getProperty("security.oauth2.resource.jwt.key-value");
String keyUri = environment
.getProperty("security.oauth2.resource.jwt.key-uri");
if (StringUtils.hasText(keyValue) || StringUtils.hasText(keyUri)) {
return ConditionOutcome
.match(message.foundExactly("provided public key"));
}
return ConditionOutcome
.noMatch(message.didNotFind("provided public key").atAll());
}
These are the resource server security dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
This is the resource server configuration. It is essentially the same as it was with Spring Boot 1.5.x.
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "my-service";
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID);
}
}
What am I missing?
The problem was an issue with the properties. I had moved "security.oauth2" to "spring.security.oauth2." When I turned on logging for org.springframework.boot.autoconfigure.security.oauth2 and saw this:
did not match due to OAuth Client ID did not find security.oauth2.client.client-id property
So, I decided to try moving the oauth2 properties back out from under "spring." and it was able to extract the JWT token.

Spring boot 2 reactive web websocket conflict with datarest

I'm using spring boot 2 to create a project and use websocket using reactive web dependency. My application is worked correctly until I add datarest dependency. after I add datarest dependency application give
' failed: Error during WebSocket handshake: Unexpected response code: 404
is any way to resolve this conflict?.
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.integration/spring-integration-file -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-file</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
WebSocketConfiguration
#Configuration
public class WebSocketConfiguration {
#Bean
public IntegrationFlow fileFlow(PublishSubscribeChannel channel, #Value("file://${HOME}/Desktop/in") File file) {
FileInboundChannelAdapterSpec in = Files.inboundAdapter(file).autoCreateDirectory(true);
return IntegrationFlows.from(
in,
p -> p.poller(pollerFactory -> {
return pollerFactory.fixedRate(1000);
})
).channel(channel).get();
}
#Bean
#Primary
public PublishSubscribeChannel incomingFilesChannel() {
return new PublishSubscribeChannel();
}
#Bean
public WebSocketHandlerAdapter webSocketHandlerAdapter() {
return new WebSocketHandlerAdapter();
}
#Bean
public WebSocketHandler webSocketHandler(PublishSubscribeChannel channel) {
return session -> {
Map<String, MessageHandler> connections = new ConcurrentHashMap<>();
Flux<WebSocketMessage> publisher = Flux.create((Consumer<FluxSink<WebSocketMessage>>) fluxSink -> {
connections.put(session.getId(), new ForwardingMessageHandler(session, fluxSink));
channel.subscribe(connections.get(session.getId()));
}).doFinally(signalType -> {
channel.unsubscribe(connections.get(session.getId()));
connections.remove(session.getId());
});
return session.send(publisher);
};
}
#Bean
public HandlerMapping handlerMapping(WebSocketHandler webSocketHandler) {
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(10);
handlerMapping.setUrlMap(Collections.singletonMap("/ws/files", webSocketHandler));
return handlerMapping;
}
}
spring-boot-starter-data-rest brings spring-boot-starter-web as a transitive dependency (so basically Spring MVC). This makes Spring Boot configure your application as a Spring MVC web application.
Spring Data REST does not currently support Spring WebFlux (see this issue for more information on that).
Your only choice is to remove the Spring Data REST dependency, as you can't have both Spring MVC and Spring WebFlux in the same Spring Boot application.

Resources