Request from Angular to Spring Gateway fails CORS error - spring

The project uses Spring-Security OAUTH 2. An Angular application is used as a web client. And as a Keycloak authorization server.
The Angular application sends requests to the Spring application via the Spring GateWay.
When I try to send a Get request, I get an error
has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
file Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.amrut.prabhu</groupId>
<artifactId>spring-cloud-gateway-keycloak-oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring Cloud Gateway Oauth2 With Keycloak</name>
<description>spring cloud gateway with keycloak oauth2</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
<lombok.version>1.18.22</lombok.version>
<logback-access-spring-boot-starter.version>3.1.2</logback-access-spring-boot-starter.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SecurityConfig
#Configuration
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ServerLogoutSuccessHandler handler) {
http
.cors()
.and()
.csrf().disable()
.authorizeExchange()
.pathMatchers("/actuator/**", "/","/logout.html")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.oauth2Login() // to redirect to oauth2 login page.
.and()
.logout()
.logoutSuccessHandler(handler)
;
return http.build();
}
#Bean
public ServerLogoutSuccessHandler keycloakLogoutSuccessHandler(ReactiveClientRegistrationRepository repository) {
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedServerLogoutSuccessHandler(repository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/logout.html");
return oidcLogoutSuccessHandler;
}
}
I tried to register in the Spring Gateway properties file, from this post
cors
spring:
cloud:
gateway:
default-filters:
- TokenRelay
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
I also tried to determine the global configuration of CORS, according to this article
baeldung.com
#Configuration
#EnableWebFlux
public class CorsGlobalConfiguration implements WebFluxConfigurer {
#Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("PUT")
.allowedMethods("GET")
.allowedHeaders("Baeldung-Allowed", "Baledung-Another-Allowed")
.exposedHeaders("Baeldung-Allowed", "Baeldung-Exposed")
.maxAge(3600);
}
}
I still get an error
What am I doing wrong?
I add all the necessary headers.
What else does he need?
And the #CrossOrigin annotation on the method always worked before.
The following two options also did not help solve the problem:
#Configuration
public class CorsWebFilterConfig {
#Bean
CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
corsConfig.setMaxAge(8000L);
corsConfig.addAllowedMethod("PUT");
corsConfig.addAllowedMethod("GET");
// corsConfig.addAllowedHeader("Baeldung-Allowed");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
}
OR
#Configuration
public class CorsWebFilterConfig implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
ServerHttpRequest request = serverWebExchange.getRequest();
ServerHttpResponse response = serverWebExchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://localhost:4200");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "18000L");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();//HERE
}
return webFilterChain.filter(serverWebExchange);
}
}
The annotation on the method does not work either
#CrossOrigin(origins = "http://localhost:4200")
Here are the contents of the network tab, the first request is OPTIONS, the second is GET
Request URL: http://10.151.68.8:8484/auth/realms/demo/protocol/openid-connect/auth?response_type=code&client_id=spring-gateway-client&scope=message.write&state=dnAY-OwNUBGKuvZE5-3AH3L6v9W8OA5V67bD-U2YgiA%3D&redirect_uri=http://localhost:9090/login/oauth2/code/keycloak
Request Method: OPTIONS
Status Code: 200 OK
Remote Address: 10.151.68.8:8484
Referrer Policy: no-referrer
Response
Connection: keep-alive
Content-Length: 25
Content-Type: application/json
Date: Wed, 16 Nov 2022 07:34:59 GMT
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Request
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Access-Control-Request-Headers: authorization
Access-Control-Request-Method: GET
Connection: keep-alive
Host: 10.151.68.8:8484
Origin: null
Sec-Fetch-Mode: cors
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
For some reason, the Origin = null field in the request
Perhaps this is the reason for cors?
It turns out that Gateway sends a verification request to the authorization server with Origin = null and receives a cors error.

My hints are:
check what are the headers of your requests/responses in your browser
the gateway cors config could contain these as well:
cors()
.configurationSource(corsConfigurationSource()).and()...
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// other settings of configuration
configuration.applyPermitDefaultValues();
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Maybe you checked the following link, but if not then worth a try in case the code above did not help: https://reflectoring.io/spring-cors/

The problem turned out to be that as a result of double forwarding of requests, the origin field became null. And when Keycloak received such a request, it threw a cors error. I found a temporary solution to this problem (thanks to the user #ch4mp):
I disable oauth2 on the gateway
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency> -->
in the properties file I write
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
exposed-headers: "*"
and I add two beans
#Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder, Function<GatewayFilterSpec, UriSpec> brutalCorsFilters) {
return builder
.routes()
.route(p -> p.path("/users/**").filters(brutalCorsFilters).uri("https://localhost:9443"))
.route(p -> p.path("/greet/**").filters(brutalCorsFilters).uri("https://localhost:9445"))
.build();
}
#Bean
Function<GatewayFilterSpec, UriSpec> brutalCorsFilters() {
return f -> f
.setResponseHeader("Access-Control-Allow-Origin", "*")
.setResponseHeader("Access-Control-Allow-Methods", "*")
.setResponseHeader("Access-Control-Expose-Headers", "*");
}
The solution is temporary, but I haven't found another one. And what offers baeldung.com it doesn't work.

Related

SpringBoot OAuth2 Client Google RequestMapping not working in RestController

I am trying to build a SpringBoot OAuth2 Application but stuck with initiating the next POST request of access_token.
This is my code https://github.com/sangeeta-p-shetty/springboot_oauth2_google.git
It runs on http://localhost:8080/security. Currently it successfully redirects to Google and on Sign In redirects to the Redirect URL
Primarily, I am stuck at 3 areas:
#RequestMapping in #RestController is not working.
As RequestMapping is not working, the sample code even though successfully authorizes I am not able to initiate the next POST request of AccessToken. Wanted guidance on how to capture the response from Google and initiate a new request.
I wanted to redirect to OAuth2 only if specific URL is hit in the application. Currently Redirection is happening by launch of context. i.e http://localhost:8080/security
SecurityConfig.java
package com.google.config;
import ....;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// only disable these during testing or for non-browser clients
.cors().disable()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.loginPage("/oauth2/authorization/google")
.authorizationEndpoint()
.authorizationRequestResolver(
new CustomAuthorizationRequestResolver(
clientRegistrationRepository(), "/oauth2/authorization"));
}
}
application.yml
server:
port: 8080
servlet:
context-path: /security
spring:
security:
oauth2:
client:
registration:
google:
client-id: test.apps.googleusercontent.com
client-secret: test
redirect-uri: http://localhost:8080/security/welcome.html
authorize-uri: https://accounts.google.com/o/oauth2/v2/auth
scope: email
response-type: code
cookie-path-domain: /
cookie-secure: true
provider:
google:
issuer-uri: https://accounts.google.com
SpringBootApplication class
package com.google;
#SpringBootApplication
#ComponentScan("com.google")
public class OidcExampleApp {
public static void main(String[] args) {
SpringApplication.run(OidcExampleApp.class, args);
}
}
AppController.java
Currently in the github code, this class is not included. But below is the intended code which does not seem to get called.
package com.google.controller;
import ...;
#RestController
public class AppController {
#RequestMapping(value="/welcome") // here even If I give value="/welcome.html") it does not work
public String getHi() {
System.out.println("End Point hit*************************************");
return "Hi";
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.codetinkering</groupId>
<artifactId>spring-boot-oauth2-oidc-google</artifactId>
<version>1.0-SNAPSHOT</version>
<name>oauth2-example</name>
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</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-web</artifactId>
</dependency>
</dependencies>
<build>
<finalName>security</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.google.OidcExampleApp</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Logs Screenshot

Spring Gateway and Auth0: IllegalArgumentException: Unable to find GatewayFilterFactory with name TokenRelay

Im trying to build a spring gateway which is getting JWT and is sending the tokens to all underlying services. For this I use the following dependencies:
<!-- Spring Boot Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<!-- Spring Boot Dependencies -->
<!-- Spring Cloud Dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud Dependencies -->
I configured my application for Auth0:
spring:
cloud:
gateway:
routes:
- id: my-service
uri: http://localhost:8001/
predicates:
- Path=/comments
filters:
- TokenRelay= #to send the token to the underlying service
- RemoveRequestHeader=Cookie #remove cookies since underlying services don't need them
security:
oauth2:
resourceserver:
jwt:
issuer-uri: #my issuer-uri
audience: #my audience
I implemented the audience validator and the jwt decoder like described here:
#Configuration
#ConditionalOnProperty(name = {"spring.security.oauth2.resourceserver.jwt.issuer-uri"})
public class AuthenticationOauth2Configuration {
#Value("${spring.security.oauth2.resourceserver.jwt.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Bean(name = "customJwtDecoder")
public JwtDecoder getJwtDecoder() {
final NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(issuer);
final OAuth2TokenValidator<Jwt> audienceValidator = new JwtAudienceValidator(audience);
final OAuth2TokenValidator<Jwt> issuer = JwtValidators.createDefaultWithIssuer(this.issuer);
final OAuth2TokenValidator<Jwt> audience = new DelegatingOAuth2TokenValidator<>(issuer, audienceValidator);
jwtDecoder.setJwtValidator(audience);
return jwtDecoder;
}
}
public class JwtAudienceValidator implements OAuth2TokenValidator<Jwt> {
private final String audience;
public JwtAudienceValidator(final String audience) {
this.audience = audience;
}
#Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
final OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
if (jwt.getAudience().contains(audience)) {
return OAuth2TokenValidatorResult.success();
}
return OAuth2TokenValidatorResult.failure(error);
}
}
However when Im starting the gateway service im getting the following error:
Caused by: reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.IllegalArgumentException: Unable to find GatewayFilterFactory with name TokenRelay
Caused by: java.lang.IllegalArgumentException: Unable to find GatewayFilterFactory with name TokenRelay
I literally cant find any resources on how to fix this.
You need org.springframework.boot:spring-boot-starter-oauth2-client as said here.
But I don't think you need it as soon as you use resource server. Gateway will forward your headers downstream without any configuration, so you will be able to find the authorization header there.
In order for spring cloud gateway to pass tokens to downstream services and validate tokens you need the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
To give an example to what Eduard Khachirov said:
dependencies:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
service application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://<AUTH0_DOMAIN>/
auth0:
audience: <AUTH0_API_AUDIENCE>
service security config:
#Configuration
#EnableWebSecurity
public class Oauth2ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${auth0.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
#Bean
public JwtDecoder jwtDecoder() {
final NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(issuer);
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
JwtValidators.createDefaultWithIssuer(issuer),
new AudienceValidator(audience)));
return jwtDecoder;
}
static class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private final String audience;
public AudienceValidator(final String audience) {
this.audience = audience;
}
public OAuth2TokenValidatorResult validate(final Jwt jwt) {
if (jwt.getAudience().contains(audience)) {
return OAuth2TokenValidatorResult.success();
}
return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "The required audience is missing", null));
}
}
}
gateway application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://<AUTH0_DOMAIN>/
cloud:
gateway:
routes:
- id: my-service
uri: lb://MY-SERVICE
predicates:
- Path=/api
loadbalancer:
ribbon:
enabled: false
gateway security config:
#Configuration
#EnableWebFluxSecurity
public class Oauth2ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
}
I had the same problem, I needed an OAuth2 consumer acts as a Client and forwards the incoming token to outgoing resource requests.
As I were using a Spring Cloud Gateway embedded reverse proxy then I could ask it to forward OAuth2 access tokens downstream to the services it is proxying. Thus the SSO app above can be enhanced simply like this (using TokenRelay Filter):
spring:
cloud:
gateway:
routes:
- id: resource
uri: http://localhost:9000
predicates:
- Path=/resource
filters:
- TokenRelay=
To enable this for Spring Cloud Gateway add the following dependencies
org.springframework.boot:spring-boot-starter-oauth2-client
org.springframework.cloud:spring-cloud-starter-security.
I had this pom.xml configuration:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-core</artifactId>
<version>${springdoc.openapi.webflux}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>${springdoc.openapi.webflux}</version>
</dependency>
</dependencies>

Websockets on Spring boot website hosted on Azure returns Error 503/403

I have built a webapp on Spring Boot using Spring Security with Azure AD authentication and Websockets to communicate with clients. Locally this works perfectly but when i deploy it as an Azure Web App the Websocket connection fails with Error 503 and 403.
I've tried searching both here and Google for answers. Some of the answers point to an App setting where you can toggle websocket support in the web app on Azure but that setting is not there anymore. A lot of solutions i found are about 5 years old and not much relevant to my situation.
I'll share some code but it's pretty basic and mostly fetched from the guides online from Microsoft and Spring.
Jacascript that connect to the websocket endpoint:
stompClient = Stomp.over(socket);
stompClient.connect({}, onConnected, onError);
My websocketconfiguration:
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
Websecurity configuration:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(oidcUserService);
http.headers().frameOptions().disable();
}
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.groupId</groupId>
<artifactId>artifactId</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Name</name>
<description>This is a description</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-active-directory-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</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-websocket</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-webapp-maven-plugin</artifactId>
<version>1.5.4</version>
<configuration>
<deploymentType>jar</deploymentType>
<!-- configure app to run on port 80, required by App Service -->
<appSettings>
<property>
<name>JAVA_OPTS</name>
<value>-Dserver.port=80</value>
</property>
</appSettings>
<!-- Web App information -->
<resourceGroup>myResourceGroup</resourceGroup>
<appName>myAppName</appName>
<region>myRegion</region>
<pricingTier>S1</pricingTier>
<!-- Java Runtime Stack for Web App on Linux -->
<linuxRuntime>jre8</linuxRuntime>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-spring-boot-bom</artifactId>
<version>2.1.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
I expect the websocket to connect as it does locally but i get this 503 error message reply:
Content-Length: 260
Content-Type: text/html
ETag: "5ce7bd82-104"
Server: nginx
Date: Fri, 31 May 2019 07:59:58 GMT
Followed by these:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: application/json;charset=UTF-8
Expires: 0
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Date: Fri, 31 May 2019 07:59:59 GMT
Edit:
If i go to the requested url directly i get an error saying Can "Upgrade" only to "WebSocket".
Edit2:
If i tail my web app logs in the Azure client this message pops up:
0 transport error)], stompSubProtocol[processed CONNECT(0)-CONNECTED(0)-DISCONNECT(0)], stompBrokerRelay[null], inboundChannel[pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 975], outboundChannel[pool
size = 0, active threads = 0, queued tasks = 0, completed tasks = 325], sockJsScheduler[pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 53124]```
It sounds like you might be running a web app container if the web socket setting is not visible.
 
To enable the WebSocket please run the below cmdlet for your site and let us know your results.
 
az webapp config set --web-sockets-enabled true --name <sitename> --resource-group <resourcegroupname>
Revisiting this after a couple of months. We hosted the app on another service and tried to deploy yet again to Azure just for kicks, and websockets worked.
I have not done any changes to the app or in azure.

Spring Boot security is not working in the simplest case

I have a very simple spring boot application to test out some security features. But I failed at the first step and I don't really understand why.
My application is a WebFlux application and has 2 users and 2 endpoints. One of the endpoint can be reached with user having "ADMIN" role, but for the other the user should only be authenticated. But I alway get HTTP 401 (Unauthorized) response.
Here is my pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-security</name>
<description>Demo project for Spring Boot Security</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</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>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<release>11</release>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
These are my endpoints:
#Configuration
public class WebConfiguration {
Mono<ServerResponse> message(ServerRequest request) {
return ServerResponse.ok().body(Mono.just("Hello World!"), String.class);
}
Mono<ServerResponse> somethingElse(ServerRequest request) {
return ServerResponse.ok().body(Mono.just("Hello Else!"), String.class);
}
#Bean
RouterFunction<?> routes() {
return RouterFunctions.route().GET("/message", this::message)
.GET("/else", this::somethingElse)
.build();
}
}
And finally the security config:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
ReactiveUserDetailsService userDetailsService() {
UserDetails user1 = User.withDefaultPasswordEncoder()
.username("notAdmin")
.password("notAdmin")
.roles("SYSTEM", "USER")
.build();
UserDetails user2 = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN", "USER")
.build();
return new MapReactiveUserDetailsService(user1,user2);
}
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/message")
.hasRole("ROLE_ADMIN")
.anyExchange()
.authenticated()
.and()
.build();
}
}
And this is how I call the service and what I get in response (pls note, the response is the same for both endpoints):
$ curl -v -uadmin:admin http://localhost:8080/else
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'admin'
> GET /else HTTP/1.1
> Host: localhost:8080
> Authorization: Basic YWRtaW46YWRtaW4=
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
* Authentication problem. Ignoring this.
< WWW-Authenticate: Basic realm="Realm"
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< Referrer-Policy: no-referrer
< content-length: 0
<
* Connection #0 to host localhost left intact
What am I doing wrong?
Update:
Just right after I clicked the submit button I found out what is the problem. An .httpBasic() is missing. Here is the correct security config:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
ReactiveUserDetailsService userDetailsService() {
UserDetails user1 = User.withDefaultPasswordEncoder()
.username("notAdmin")
.password("notAdmin")
.roles("SYSTEM", "USER")
.build();
UserDetails user2 = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN", "USER")
.build();
return new MapReactiveUserDetailsService(user1,user2);
}
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/message")
.hasRole("ADMIN")
.anyExchange()
.authenticated()
.and()
.httpBasic()
.and()
.build();
}
}
Sorry, if I caused any inconvenience!

spring config server encrypt forbidden

I've configured a spring cloud config server to use oAuth2 for security. Everything is working well, except the encrypt end point. When I try to access /encrypt I get a 403 Forbidden. I am including the Authorization Bearer token in the header. Is there a way to allow the encrypt end point to be called when the server is secured with oAuth, or is it always blocked? Let me know if you would like to see any config files for this server.
Just for reference, here are the things that are working.
calling /encrypt/status produces {"status":"OK"}
The git repository is being pulled because I can access a property file from the server.
oAuth authentication is working with Google because it takes me through the logon process.
Here is the spring security settings.
security:
require-ssl: true
auth2:
client:
clientId: PROVIDED BY GOOGLE
clientSecret: PROVIDED BY GOOGLE
accessTokenUri: https://www.googleapis.com/oauth2/v4/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
scope:
- openid
- email
- profile
resource:
userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
preferTokenInfo: true
server:
port: 8443
ssl:
key-store-type: PKCS12
key-store: /spring-config-server/host/tomcat-keystore.p12
key-alias: tomcat
key-store-password: ${KEYSTORE_PASSWORD}
Here are my dependencies from the POM file so you can see the version of the libraries I'm using.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.M8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
I solve it implementing this WebSecurityConfigurer. It disables CSRF and set basic authentication.In Spring Boot 2.0.0 you cannot disable CSRF using properties it forces you to implement a java security config bean.
package my.package.config.server;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated().and()
.httpBasic();
;
}
}
Hope it helps
We must implement WebSecurityConfigurerAdapter in configuration related class. So that the encrypt/decrypt services can be accessible. Make sure that you have configured secret.key in bootstrap.properties or application.properties.
WebSecurityConfigurerAdapter is deprecated
https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
Try the following instead of:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
public class SecurityConfiguration {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated().and()
.httpBasic();
return http.build();
}
}
To fix this issue, I needed to extend WebSecurityConfigurerAdapter and in the configure method I disabled CSRF token.
http
.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**", "/error**")
.permitAll()
.anyRequest().authenticated();

Resources