JAVA-14288 Rename spring-5-reactive-modules to spring-reactive-modules (#12659)
* JAVA-14288 Rename spring-5-reactive-modules to spring-reactive-modules * JAVA-14288 Remove failing module * JAVA-14288 Revert commenting spring-cloud-openfeign-2 module
This commit is contained in:
12
spring-reactive-modules/spring-5-reactive-security/.gitignore
vendored
Normal file
12
spring-reactive-modules/spring-5-reactive-security/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#folders#
|
||||
.idea
|
||||
/target
|
||||
/neoDb*
|
||||
/data
|
||||
/src/main/webapp/WEB-INF/classes
|
||||
*/META-INF/*
|
||||
|
||||
# Packaged files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
12
spring-reactive-modules/spring-5-reactive-security/README.md
Normal file
12
spring-reactive-modules/spring-5-reactive-security/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## Spring 5 Reactive Security Examples
|
||||
|
||||
This module contains articles about reactive Spring Security 5
|
||||
|
||||
### The Course
|
||||
The "REST With Spring" Classes: http://bit.ly/restwithspring
|
||||
|
||||
### Relevant Articles
|
||||
|
||||
- [Spring Boot Actuator](https://www.baeldung.com/spring-boot-actuators)
|
||||
- [Guide to the AuthenticationManagerResolver in Spring Security](https://www.baeldung.com/spring-security-authenticationmanagerresolver)
|
||||
- [Spring Webflux and CORS](https://www.baeldung.com/spring-webflux-cors)
|
||||
128
spring-reactive-modules/spring-5-reactive-security/pom.xml
Normal file
128
spring-reactive-modules/spring-5-reactive-security/pom.xml
Normal file
@@ -0,0 +1,128 @@
|
||||
<?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>
|
||||
<artifactId>spring-5-reactive-security</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-5-reactive-security</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>spring 5 security sample project about new features</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.spring.reactive</groupId>
|
||||
<artifactId>spring-reactive-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectreactor</groupId>
|
||||
<artifactId>reactor-spring</artifactId>
|
||||
<version>${reactor-spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.json.bind</groupId>
|
||||
<artifactId>javax.json.bind-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>geronimo-json_1.1_spec</artifactId>
|
||||
<version>${geronimo-json_1.1_spec.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.johnzon</groupId>
|
||||
<artifactId>johnzon-jsonb</artifactId>
|
||||
</dependency>
|
||||
<!-- utils -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<!-- runtime and test scoped -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${commons-collections4.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava2</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
<version>${rxjava-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<version>${project-reactor-test}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</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>
|
||||
<configuration>
|
||||
<mainClass>com.baeldung.reactive.actuator.Spring5ReactiveApplication</mainClass>
|
||||
<layout>JAR</layout>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<reactor-spring.version>1.0.1.RELEASE</reactor-spring.version>
|
||||
<rxjava-version>2.1.12</rxjava-version>
|
||||
<johnzon.version>1.1.3</johnzon.version>
|
||||
<jsonb-api.version>1.0</jsonb-api.version>
|
||||
<geronimo-json_1.1_spec.version>1.0</geronimo-json_1.1_spec.version>
|
||||
<project-reactor-test>3.1.6.RELEASE</project-reactor-test>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baeldung.reactive.actuator;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class DownstreamServiceHealthIndicator implements ReactiveHealthIndicator {
|
||||
|
||||
@Override
|
||||
public Mono<Health> health() {
|
||||
return checkDownstreamServiceHealth().onErrorResume(
|
||||
ex -> Mono.just(new Health.Builder().down(ex).build())
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<Health> checkDownstreamServiceHealth() {
|
||||
// we could use WebClient to check health reactively
|
||||
return Mono.just(new Health.Builder().up().build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.baeldung.reactive.actuator;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
@Endpoint(id = "features")
|
||||
public class FeaturesEndpoint {
|
||||
|
||||
private Map<String, Feature> features = new ConcurrentHashMap<>();
|
||||
|
||||
@ReadOperation
|
||||
public Map<String, Feature> features() {
|
||||
return features;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Feature feature(@Selector String name) {
|
||||
return features.get(name);
|
||||
}
|
||||
|
||||
@WriteOperation
|
||||
public void configureFeature(@Selector String name, Feature feature) {
|
||||
features.put(name, feature);
|
||||
}
|
||||
|
||||
@DeleteOperation
|
||||
public void deleteFeature(@Selector String name) {
|
||||
features.remove(name);
|
||||
}
|
||||
|
||||
public static class Feature {
|
||||
private Boolean enabled;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.baeldung.reactive.actuator;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.boot.actuate.info.InfoEndpoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@EndpointWebExtension(endpoint = InfoEndpoint.class)
|
||||
public class InfoWebEndpointExtension {
|
||||
|
||||
private final InfoEndpoint delegate;
|
||||
|
||||
public InfoWebEndpointExtension(InfoEndpoint infoEndpoint) {
|
||||
this.delegate = infoEndpoint;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<Map> info() {
|
||||
Map<String, Object> info = this.delegate.info();
|
||||
Integer status = getStatus(info);
|
||||
return new WebEndpointResponse<>(info, status);
|
||||
}
|
||||
|
||||
private Integer getStatus(Map<String, Object> info) {
|
||||
// return 5xx if this is a snapshot
|
||||
return 200;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.baeldung.reactive.actuator;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Spring5ReactiveApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Spring5ReactiveApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baeldung.reactive.actuator;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class WebSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securitygWebFilterChain(
|
||||
ServerHttpSecurity http) {
|
||||
|
||||
return http.authorizeExchange()
|
||||
.pathMatchers("/actuator/**").permitAll()
|
||||
.anyExchange().authenticated()
|
||||
.and().build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.baeldung.reactive.authresolver;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
@EnableWebFlux
|
||||
@SpringBootApplication
|
||||
public class AuthResolverApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AuthResolverApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.baeldung.reactive.authresolver;
|
||||
|
||||
import java.security.Principal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
public class AuthResolverController {
|
||||
|
||||
@GetMapping("/customer/welcome")
|
||||
public Mono<String> sayWelcomeToCustomer(Mono<Principal> principal) {
|
||||
return principal
|
||||
.map(Principal::getName)
|
||||
.map(name -> String.format("Welcome to our site, %s!", name));
|
||||
}
|
||||
|
||||
@GetMapping("/employee/welcome")
|
||||
public Mono<String> sayWelcomeToEmployee(Mono<Principal> principal) {
|
||||
return principal
|
||||
.map(Principal::getName)
|
||||
.map(name -> String.format("Welcome to our company, %s!", name));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.baeldung.reactive.authresolver;
|
||||
|
||||
import java.util.Collections;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableReactiveMethodSecurity
|
||||
public class CustomWebSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
return http
|
||||
.authorizeExchange()
|
||||
.pathMatchers("/**")
|
||||
.authenticated()
|
||||
.and()
|
||||
.httpBasic()
|
||||
.disable()
|
||||
.addFilterAfter(authenticationWebFilter(), SecurityWebFiltersOrder.REACTOR_CONTEXT)
|
||||
.build();
|
||||
}
|
||||
|
||||
public AuthenticationWebFilter authenticationWebFilter() {
|
||||
return new AuthenticationWebFilter(resolver());
|
||||
}
|
||||
|
||||
public ReactiveAuthenticationManagerResolver<ServerWebExchange> resolver() {
|
||||
return exchange -> {
|
||||
if (exchange
|
||||
.getRequest()
|
||||
.getPath()
|
||||
.subPath(0)
|
||||
.value()
|
||||
.startsWith("/employee")) {
|
||||
return Mono.just(employeesAuthenticationManager());
|
||||
}
|
||||
return Mono.just(customersAuthenticationManager());
|
||||
};
|
||||
}
|
||||
|
||||
public ReactiveAuthenticationManager customersAuthenticationManager() {
|
||||
return authentication -> customer(authentication)
|
||||
.switchIfEmpty(Mono.error(new UsernameNotFoundException(authentication
|
||||
.getPrincipal()
|
||||
.toString())))
|
||||
.map(b -> new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
|
||||
authentication.getCredentials(),
|
||||
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public ReactiveAuthenticationManager employeesAuthenticationManager() {
|
||||
return authentication -> employee(authentication)
|
||||
.switchIfEmpty(Mono.error(new UsernameNotFoundException(authentication
|
||||
.getPrincipal()
|
||||
.toString())))
|
||||
.map(
|
||||
b -> new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
|
||||
authentication.getCredentials(),
|
||||
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Mono<String> customer(Authentication authentication) {
|
||||
return Mono.justOrEmpty(authentication
|
||||
.getPrincipal()
|
||||
.toString()
|
||||
.startsWith("customer") ? authentication
|
||||
.getPrincipal()
|
||||
.toString() : null);
|
||||
}
|
||||
|
||||
public Mono<String> employee(Authentication authentication) {
|
||||
return Mono.justOrEmpty(authentication
|
||||
.getPrincipal()
|
||||
.toString()
|
||||
.startsWith("employee") ? authentication
|
||||
.getPrincipal()
|
||||
.toString() : null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.baeldung.reactive.cors.annotated;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@SpringBootApplication
|
||||
public class CorsOnAnnotatedElementsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(CorsOnAnnotatedElementsApplication.class);
|
||||
app.setDefaultProperties(Collections.singletonMap("server.port", "8081"));
|
||||
app.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain corsAnnotatedSpringSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.csrf().disable();
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.baeldung.reactive.cors.annotated.controllers;
|
||||
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@CrossOrigin(value = { "http://allowed-origin.com" }, allowedHeaders = { "Baeldung-Another-Allowed" }, maxAge = 900)
|
||||
@RestController
|
||||
@RequestMapping("/cors-on-controller")
|
||||
public class CorsOnClassController {
|
||||
|
||||
@PutMapping("/regular-endpoint")
|
||||
public Mono<String> corsDisabledEndpoint() {
|
||||
return Mono.just("Regular endpoint");
|
||||
}
|
||||
|
||||
@CrossOrigin
|
||||
@PutMapping("/cors-enabled-endpoint")
|
||||
public Mono<String> corsEnabledEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint");
|
||||
}
|
||||
|
||||
@CrossOrigin({ "http://another-allowed-origin.com" })
|
||||
@PutMapping("/cors-enabled-origin-restrictive-endpoint")
|
||||
public Mono<String> corsEnabledOriginRestrictiveEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint - Origin Restrictive");
|
||||
}
|
||||
|
||||
@CrossOrigin(allowedHeaders = { "Baeldung-Allowed" })
|
||||
@PutMapping("/cors-enabled-header-restrictive-endpoint")
|
||||
public Mono<String> corsEnabledHeaderRestrictiveEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint - Header Restrictive");
|
||||
}
|
||||
|
||||
@CrossOrigin(exposedHeaders = { "Baeldung-Exposed" })
|
||||
@PutMapping("/cors-enabled-exposed-header-endpoint")
|
||||
public Mono<String> corsEnabledExposedHeadersEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint - Exposed Header");
|
||||
}
|
||||
|
||||
@PutMapping("/cors-enabled-mixed-config-endpoint")
|
||||
@CrossOrigin(allowedHeaders = { "Baeldung-Allowed", "Baeldung-Other-Allowed" }, exposedHeaders = { "Baeldung-Allowed", "Baeldung-Exposed" }, maxAge = 3600)
|
||||
public Mono<String> corsEnabledHeaderExposedEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint - Mixed Config");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.baeldung.reactive.cors.annotated.controllers;
|
||||
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/cors-on-methods")
|
||||
public class CorsOnMethodsController {
|
||||
|
||||
@PutMapping("/cors-disabled-endpoint")
|
||||
public Mono<String> corsDisabledEndpoint() {
|
||||
return Mono.just("CORS disabled endpoint");
|
||||
}
|
||||
|
||||
@CrossOrigin
|
||||
@PutMapping("/cors-enabled-endpoint")
|
||||
public Mono<String> corsEnabledEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint");
|
||||
}
|
||||
|
||||
@CrossOrigin({ "http://allowed-origin.com" })
|
||||
@PutMapping("/cors-enabled-origin-restrictive-endpoint")
|
||||
public Mono<String> corsEnabledOriginRestrictiveEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint - Origin Restrictive");
|
||||
}
|
||||
|
||||
@CrossOrigin(allowedHeaders = { "Baeldung-Allowed" })
|
||||
@PutMapping("/cors-enabled-header-restrictive-endpoint")
|
||||
public Mono<String> corsEnabledHeaderRestrictiveEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint - Header Restrictive");
|
||||
}
|
||||
|
||||
@CrossOrigin(exposedHeaders = { "Baeldung-Exposed" })
|
||||
@PutMapping("/cors-enabled-exposed-header-endpoint")
|
||||
public Mono<String> corsEnabledExposedHeadersEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint - Exposed Header");
|
||||
}
|
||||
|
||||
@PutMapping("/cors-enabled-mixed-config-endpoint")
|
||||
@CrossOrigin(allowedHeaders = { "Baeldung-Allowed", "Baeldung-Other-Allowed" }, exposedHeaders = { "Baeldung-Allowed", "Baeldung-Exposed" }, maxAge = 3600)
|
||||
public Mono<String> corsEnabledHeaderExposedEndpoint() {
|
||||
return Mono.just("CORS enabled endpoint - Mixed Config");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.baeldung.reactive.cors.global;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@SpringBootApplication(exclude = { MongoAutoConfiguration.class,
|
||||
MongoDataAutoConfiguration.class,
|
||||
MongoReactiveDataAutoConfiguration.class,
|
||||
MongoReactiveAutoConfiguration.class }
|
||||
)
|
||||
public class CorsGlobalConfigApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(CorsGlobalConfigApplication.class);
|
||||
app.setDefaultProperties(Collections.singletonMap("server.port", "8082"));
|
||||
app.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain corsGlobalSpringSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.csrf().disable();
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.baeldung.reactive.cors.global.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.config.CorsRegistry;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class CorsGlobalConfiguration implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry corsRegistry) {
|
||||
corsRegistry.addMapping("/**")
|
||||
.allowedOrigins("http://allowed-origin.com")
|
||||
.allowedMethods("PUT")
|
||||
.allowedHeaders("Baeldung-Allowed", "Baledung-Another-Allowed")
|
||||
.exposedHeaders("Baeldung-Allowed", "Baeldung-Exposed")
|
||||
.maxAge(3600);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.baeldung.reactive.cors.global.controllers;
|
||||
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController("FurtherCorsConfigsController-cors-on-global-config-and-more")
|
||||
@RequestMapping("/cors-on-global-config-and-more")
|
||||
public class FurtherCorsConfigsController {
|
||||
|
||||
@DeleteMapping("/further-mixed-config-endpoint")
|
||||
@CrossOrigin(methods = { RequestMethod.DELETE },
|
||||
allowedHeaders = { "Baeldung-Other-Allowed" },
|
||||
exposedHeaders = { "Baeldung-Other-Exposed" }
|
||||
)
|
||||
public Mono<String> corsEnabledHeaderExposedEndpoint() {
|
||||
return Mono.just("CORS Global Configured + Request Configs.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baeldung.reactive.cors.global.controllers;
|
||||
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController("RegularRestController-cors-on-global-config")
|
||||
@RequestMapping("/cors-on-global-config")
|
||||
public class RegularRestController {
|
||||
|
||||
@PutMapping("/regular-put-endpoint")
|
||||
public Mono<String> regularPutEndpoint() {
|
||||
return Mono.just("Regular PUT endpoint");
|
||||
}
|
||||
|
||||
@DeleteMapping("/regular-delete-endpoint")
|
||||
public Mono<String> regularDeleteEndpoint() {
|
||||
return Mono.just("Regular DELETE endpoint");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.baeldung.reactive.cors.global.functional.handlers;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class CorsGlobalFunctionalHandler {
|
||||
|
||||
public Mono<ServerResponse> useHandler(final ServerRequest request) {
|
||||
final String responseMessage = "CORS GLOBAL CONFIG IS NOT EFFECTIVE ON FUNCTIONAL ENDPOINTS!!!";
|
||||
|
||||
return ServerResponse.ok()
|
||||
.body(Mono.just(responseMessage), String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.baeldung.reactive.cors.global.functional.routers;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import com.baeldung.reactive.cors.global.functional.handlers.CorsGlobalFunctionalHandler;
|
||||
|
||||
@Configuration
|
||||
public class CorsRouterFunctions {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> corsGlobalRouter(@Autowired CorsGlobalFunctionalHandler handler) {
|
||||
return RouterFunctions.route(RequestPredicates.PUT("/global-config-on-functional/cors-disabled-functional-endpoint"), handler::useHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.baeldung.reactive.cors.webfilter;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@SpringBootApplication(exclude = { MongoAutoConfiguration.class,
|
||||
MongoDataAutoConfiguration.class,
|
||||
MongoReactiveDataAutoConfiguration.class,
|
||||
MongoReactiveAutoConfiguration.class }
|
||||
)
|
||||
public class CorsWebFilterApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(CorsWebFilterApplication.class);
|
||||
app.setDefaultProperties(Collections.singletonMap("server.port", "8083"));
|
||||
app.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain corsWebfilterSpringSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.csrf().disable();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baeldung.reactive.cors.webfilter.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsWebFilter;
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
@Configuration
|
||||
public class CorsWebFilterConfig {
|
||||
|
||||
@Bean
|
||||
CorsWebFilter corsWebFilter() {
|
||||
CorsConfiguration corsConfig = new CorsConfiguration();
|
||||
corsConfig.setAllowedOrigins(Arrays.asList("http://allowed-origin.com"));
|
||||
corsConfig.setMaxAge(8000L);
|
||||
corsConfig.addAllowedMethod("PUT");
|
||||
corsConfig.addAllowedHeader("Baeldung-Allowed");
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
|
||||
source.registerCorsConfiguration("/**", corsConfig);
|
||||
|
||||
return new CorsWebFilter(source);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.baeldung.reactive.cors.webfilter.controllers;
|
||||
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
//@RestController
|
||||
//@RequestMapping("/web-filter-and-more-on-annotated")
|
||||
public class FurtherCorsConfigsController {
|
||||
|
||||
@DeleteMapping("/further-mixed-config-endpoint")
|
||||
@CrossOrigin(methods = { RequestMethod.DELETE },
|
||||
allowedHeaders = { "Baeldung-Other-Allowed" },
|
||||
exposedHeaders = { "Baeldung-Other-Exposed" }
|
||||
)
|
||||
public Mono<String> corsEnabledHeaderExposedEndpoint() {
|
||||
final String responseMessage = "CORS @CrossOrigin IS NOT EFFECTIVE with WebFilter!!!";
|
||||
|
||||
return Mono.just(responseMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baeldung.reactive.cors.webfilter.controllers;
|
||||
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
//@RestController
|
||||
//@RequestMapping("/web-filter-on-annotated")
|
||||
public class RegularRestController {
|
||||
|
||||
@PutMapping("/regular-put-endpoint")
|
||||
public Mono<String> regularPutEndpoint() {
|
||||
return Mono.just("Regular PUT endpoint");
|
||||
}
|
||||
|
||||
@DeleteMapping("/regular-delete-endpoint")
|
||||
public Mono<String> regularDeleteEndpoint() {
|
||||
return Mono.just("Regular DELETE endpoint");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baeldung.reactive.cors.webfilter.functional.handlers;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class CorsWithWebFilterHandler {
|
||||
|
||||
public Mono<ServerResponse> useHandler(final ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(Mono.just("Functional Endpoint"), String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.baeldung.reactive.cors.webfilter.functional.routers;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import com.baeldung.reactive.cors.webfilter.functional.handlers.CorsWithWebFilterHandler;
|
||||
|
||||
@Configuration
|
||||
public class CorsWithWebFilterRouterFunctions {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> corsWebfilterRouter(@Autowired CorsWithWebFilterHandler handler) {
|
||||
return RouterFunctions.route(RequestPredicates.PUT("/web-filter-on-functional/functional-endpoint"), handler::useHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.webflux;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Employee {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
// standard getters and setters
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.baeldung.webflux;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class EmployeeConfig {
|
||||
|
||||
@Bean
|
||||
public HandlerMapping handlerMapping() {
|
||||
Map<String, WebSocketHandler> map = new HashMap<>();
|
||||
map.put("/employee-feed", new EmployeeWebSocketHandler());
|
||||
|
||||
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
|
||||
mapping.setUrlMap(map);
|
||||
mapping.setOrder(10);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketHandlerAdapter handlerAdapter() {
|
||||
return new WebSocketHandlerAdapter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.baeldung.webflux;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class EmployeeCreationEvent {
|
||||
private String employeeId;
|
||||
private String creationTime;
|
||||
public EmployeeCreationEvent(String employeeId, String creationTime) {
|
||||
super();
|
||||
this.employeeId = employeeId;
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.baeldung.webflux;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
public class EmployeeWebSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public MapReactiveUserDetailsService userDetailsService() {
|
||||
UserDetails user = User
|
||||
.withUsername("admin")
|
||||
.password(passwordEncoder().encode("password"))
|
||||
.roles("ADMIN")
|
||||
.build();
|
||||
return new MapReactiveUserDetailsService(user);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.csrf()
|
||||
.disable()
|
||||
.authorizeExchange()
|
||||
.pathMatchers(HttpMethod.POST, "/employees/update")
|
||||
.hasRole("ADMIN")
|
||||
.pathMatchers("/**")
|
||||
.permitAll()
|
||||
.and()
|
||||
.httpBasic();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.baeldung.webflux;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.web.reactive.socket.WebSocketMessage;
|
||||
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
|
||||
import org.springframework.web.reactive.socket.client.WebSocketClient;
|
||||
|
||||
public class EmployeeWebSocketClient {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
WebSocketClient client = new ReactorNettyWebSocketClient();
|
||||
|
||||
client.execute(URI.create("ws://localhost:8080/employee-feed"), session -> session.receive()
|
||||
.map(WebSocketMessage::getPayloadAsText)
|
||||
.doOnNext(System.out::println)
|
||||
.then())
|
||||
.block(); // to subscribe and return the value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.baeldung.webflux;
|
||||
|
||||
import static java.time.LocalDateTime.now;
|
||||
import static java.util.UUID.randomUUID;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||
import org.springframework.web.reactive.socket.WebSocketSession;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component("EmployeeWebSocketHandler")
|
||||
public class EmployeeWebSocketHandler implements WebSocketHandler {
|
||||
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(WebSocketSession webSocketSession) {
|
||||
|
||||
Flux<String> employeeCreationEvent = Flux.generate(sink -> {
|
||||
EmployeeCreationEvent event = new EmployeeCreationEvent(randomUUID().toString(), now().toString());
|
||||
try {
|
||||
sink.next(om.writeValueAsString(event));
|
||||
} catch (JsonProcessingException e) {
|
||||
sink.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
return webSocketSession.send(employeeCreationEvent
|
||||
.map(webSocketSession::textMessage)
|
||||
.delayElements(Duration.ofSeconds(1)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
logging.level.root=INFO
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
|
||||
info.app.name=Spring Boot 2 actuator Application
|
||||
management.endpoint.health.group.custom.include=diskSpace,ping
|
||||
management.endpoint.health.group.custom.show-components=always
|
||||
management.endpoint.health.group.custom.show-details=always
|
||||
management.endpoint.health.group.custom.status.http-mapping.up=207
|
||||
@@ -0,0 +1 @@
|
||||
hello
|
||||
@@ -0,0 +1 @@
|
||||
test
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Baeldung: Spring 5 Reactive Client WebSocket (Browser)</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="events"></div>
|
||||
<script>
|
||||
var clientWebSocket = new WebSocket("ws://localhost:8080/event-emitter");
|
||||
clientWebSocket.onopen = function() {
|
||||
console.log("clientWebSocket.onopen", clientWebSocket);
|
||||
console.log("clientWebSocket.readyState", "websocketstatus");
|
||||
clientWebSocket.send("event-me-from-browser");
|
||||
}
|
||||
clientWebSocket.onclose = function(error) {
|
||||
console.log("clientWebSocket.onclose", clientWebSocket, error);
|
||||
events("Closing connection");
|
||||
}
|
||||
clientWebSocket.onerror = function(error) {
|
||||
console.log("clientWebSocket.onerror", clientWebSocket, error);
|
||||
events("An error occured");
|
||||
}
|
||||
clientWebSocket.onmessage = function(error) {
|
||||
console.log("clientWebSocket.onmessage", clientWebSocket, error);
|
||||
events(error.data);
|
||||
}
|
||||
function events(responseEvent) {
|
||||
document.querySelector(".events").innerHTML += responseEvent + "<br>";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
version="3.1">
|
||||
|
||||
<display-name>Spring Functional Application</display-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>functional</servlet-name>
|
||||
<servlet-class>com.baeldung.functional.RootServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
<async-supported>true</async-supported>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>functional</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
|
||||
</web-app>
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baeldung;
|
||||
|
||||
import com.baeldung.reactive.actuator.Spring5ReactiveApplication;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Spring5ReactiveApplication.class)
|
||||
public class SpringContextTest {
|
||||
|
||||
@Test
|
||||
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.baeldung.reactive.actuator;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Spring5ReactiveApplication.class)
|
||||
public class ActuatorInfoIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void whenGetInfo_thenReturns200() throws IOException {
|
||||
final ResponseEntity<String> responseEntity = this.restTemplate.getForEntity("/actuator/info", String.class);
|
||||
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenFeatures_thenReturns200() throws IOException {
|
||||
final ResponseEntity<String> responseEntity = this.restTemplate.getForEntity("/actuator/features", String.class);
|
||||
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.baeldung.reactive.authresolver;
|
||||
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AuthResolverApplication.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class AuthResolverIntegrationTest {
|
||||
@Autowired
|
||||
private WebTestClient testClient;
|
||||
|
||||
@Test
|
||||
public void givenCustomerCredential_whenWelcomeCustomer_thenExpectOk() {
|
||||
testClient
|
||||
.get()
|
||||
.uri("/customer/welcome")
|
||||
.header("Authorization", "Basic " + Base64Utils.encodeToString("customer1:pass1".getBytes()))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmployeeCredential_whenWelcomeCustomer_thenExpect401Status() {
|
||||
testClient
|
||||
.get()
|
||||
.uri("/customer/welcome")
|
||||
.header("Authorization", "Basic " + Base64Utils.encodeToString("employee1:pass1".getBytes()))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmployeeCredential_whenWelcomeEmployee_thenExpectOk() {
|
||||
testClient
|
||||
.get()
|
||||
.uri("/employee/welcome")
|
||||
.header("Authorization", "Basic " + Base64Utils.encodeToString("employee1:pass1".getBytes()))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenCustomerCredential_whenWelcomeEmployee_thenExpect401Status() {
|
||||
testClient
|
||||
.get()
|
||||
.uri("/employee/welcome")
|
||||
.header("Authorization", "Basic " + Base64Utils.encodeToString("customer1:pass1".getBytes()))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isUnauthorized();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.baeldung.reactive.cors;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;
|
||||
|
||||
import com.baeldung.reactive.cors.annotated.CorsOnAnnotatedElementsApplication;
|
||||
|
||||
@SpringBootTest(classes = CorsOnAnnotatedElementsApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class CorsOnAnnotatedElementsLiveTest {
|
||||
|
||||
private static final String BASE_URL = "http://localhost:8081";
|
||||
private static final String BASE_CORS_ON_METHODS_URL = "/cors-on-methods";
|
||||
private static final String BASE_CORS_ON_CONTROLLER_URL = "/cors-on-controller";
|
||||
private static final String CONTROLLER_CORS_ALLOWED_ORIGIN = "http://allowed-origin.com";
|
||||
private static final String CORS_DEFAULT_ORIGIN = "http://default-origin.com";
|
||||
|
||||
private static WebTestClient client;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
client = WebTestClient.bindToServer()
|
||||
.baseUrl(BASE_URL)
|
||||
.defaultHeader("Origin", CORS_DEFAULT_ORIGIN)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingMethodCorsEnabledEndpoint_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.put()
|
||||
.uri(BASE_CORS_ON_METHODS_URL + "/cors-enabled-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightMethodCorsEnabled_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_CORS_ON_METHODS_URL + "/cors-enabled-endpoint")
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", "*");
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Methods", "PUT");
|
||||
response.expectHeader()
|
||||
.exists("Access-Control-Max-Age");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingMethodCorsDisabledEndpoint_thenObtainResponseWithoutCorsHeaders() {
|
||||
ResponseSpec response = client.put()
|
||||
.uri(BASE_CORS_ON_METHODS_URL + "/cors-disabled-put-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.doesNotExist("Access-Control-Allow-Origin");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingMethodCorsRestrictiveOrigin_thenObtainForbiddenResponse() {
|
||||
ResponseSpec response = client.put()
|
||||
.uri(BASE_CORS_ON_METHODS_URL + "/cors-enabled-origin-restrictive-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightMethodCorsRestrictiveOrigin_thenObtainForbiddenResponse() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_CORS_ON_METHODS_URL + "/cors-enabled-origin-restrictive-endpoint")
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightMethodCorsRestrictiveHeader_thenObtainResponseWithAllowedHeaders() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_CORS_ON_METHODS_URL + "/cors-enabled-header-restrictive-endpoint")
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.header("Access-Control-Request-Headers", "Baeldung-Not-Allowed, Baeldung-Allowed")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Headers", "Baeldung-Allowed");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightControllerCorsRegularEndpoint_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_CORS_ON_CONTROLLER_URL + "/regular-endpoint")
|
||||
.header("Origin", CONTROLLER_CORS_ALLOWED_ORIGIN)
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", CONTROLLER_CORS_ALLOWED_ORIGIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightControllerCorsRestrictiveOrigin_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_CORS_ON_CONTROLLER_URL + "/cors-enabled-origin-restrictive-endpoint")
|
||||
.header("Origin", CONTROLLER_CORS_ALLOWED_ORIGIN)
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", CONTROLLER_CORS_ALLOWED_ORIGIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightControllerCorsRestrictiveOriginWithAnotherAllowedOrigin_thenObtainResponseWithCorsHeaders() {
|
||||
final String anotherAllowedOrigin = "http://another-allowed-origin.com";
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_CORS_ON_CONTROLLER_URL + "/cors-enabled-origin-restrictive-endpoint")
|
||||
.header("Origin", anotherAllowedOrigin)
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", anotherAllowedOrigin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightControllerCorsExposingHeaders_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_CORS_ON_CONTROLLER_URL + "/cors-enabled-exposed-header-endpoint")
|
||||
.header("Origin", CONTROLLER_CORS_ALLOWED_ORIGIN)
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Expose-Headers", "Baeldung-Exposed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.baeldung.reactive.cors;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;
|
||||
|
||||
import com.baeldung.reactive.cors.global.CorsGlobalConfigApplication;
|
||||
|
||||
@SpringBootTest(classes = CorsGlobalConfigApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class CorsOnGlobalConfigLiveTest {
|
||||
|
||||
private static final String BASE_URL = "http://localhost:8082";
|
||||
private static final String BASE_REGULAR_URL = "/cors-on-global-config";
|
||||
private static final String BASE_EXTRA_CORS_CONFIG_URL = "/cors-on-global-config-and-more";
|
||||
private static final String BASE_FUNCTIONALS_URL = "/global-config-on-functional";
|
||||
private static final String CORS_DEFAULT_ORIGIN = "http://allowed-origin.com";
|
||||
|
||||
private static WebTestClient client;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
client = WebTestClient.bindToServer()
|
||||
.baseUrl(BASE_URL)
|
||||
.defaultHeader("Origin", CORS_DEFAULT_ORIGIN)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingRegularEndpoint_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.put()
|
||||
.uri(BASE_REGULAR_URL + "/regular-put-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", CORS_DEFAULT_ORIGIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingRegularDeleteEndpoint_thenObtainForbiddenResponse() {
|
||||
ResponseSpec response = client.delete()
|
||||
.uri(BASE_REGULAR_URL + "/regular-delete-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightAllowedDeleteEndpoint_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_EXTRA_CORS_CONFIG_URL + "/further-mixed-config-endpoint")
|
||||
.header("Access-Control-Request-Method", "DELETE")
|
||||
.header("Access-Control-Request-Headers", "Baeldung-Not-Allowed, Baeldung-Allowed, Baeldung-Other-Allowed")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", CORS_DEFAULT_ORIGIN);
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Methods", "PUT,DELETE");
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Headers", "Baeldung-Allowed, Baeldung-Other-Allowed");
|
||||
response.expectHeader()
|
||||
.exists("Access-Control-Max-Age");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestAllowedDeleteEndpoint_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.delete()
|
||||
.uri(BASE_EXTRA_CORS_CONFIG_URL + "/further-mixed-config-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightFunctionalEndpoint_thenObtain404Response() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_FUNCTIONALS_URL + "/cors-disabled-functional-endpoint")
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isNotFound();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestFunctionalEndpoint_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.put()
|
||||
.uri(BASE_FUNCTIONALS_URL + "/cors-disabled-functional-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", CORS_DEFAULT_ORIGIN);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.baeldung.reactive.cors;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;
|
||||
|
||||
import com.baeldung.reactive.cors.webfilter.CorsWebFilterApplication;
|
||||
|
||||
@SpringBootTest(classes = CorsWebFilterApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class CorsOnWebFilterLiveTest {
|
||||
|
||||
private static final String BASE_URL = "http://localhost:8083";
|
||||
private static final String BASE_REGULAR_URL = "/web-filter-on-annotated";
|
||||
private static final String BASE_EXTRA_CORS_CONFIG_URL = "/web-filter-and-more-on-annotated";
|
||||
private static final String BASE_FUNCTIONALS_URL = "/web-filter-on-functional";
|
||||
private static final String CORS_DEFAULT_ORIGIN = "http://allowed-origin.com";
|
||||
|
||||
private static WebTestClient client;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
client = WebTestClient.bindToServer()
|
||||
.baseUrl(BASE_URL)
|
||||
.defaultHeader("Origin", CORS_DEFAULT_ORIGIN)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingRegularEndpoint_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.put()
|
||||
.uri(BASE_REGULAR_URL + "/regular-put-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", CORS_DEFAULT_ORIGIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingRegularDeleteEndpoint_thenObtainForbiddenResponse() {
|
||||
ResponseSpec response = client.delete()
|
||||
.uri(BASE_REGULAR_URL + "/regular-delete-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightDeleteEndpointWithExtraConfigs_thenObtainForbiddenResponse() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_EXTRA_CORS_CONFIG_URL + "/further-mixed-config-endpoint")
|
||||
.header("Access-Control-Request-Method", "DELETE")
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestDeleteEndpointWithExtraConfigs_thenObtainForbiddenResponse() {
|
||||
ResponseSpec response = client.delete()
|
||||
.uri(BASE_EXTRA_CORS_CONFIG_URL + "/further-mixed-config-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenPreflightFunctionalEndpoint_thenObtain404Response() {
|
||||
ResponseSpec response = client.options()
|
||||
.uri(BASE_FUNCTIONALS_URL + "/functional-endpoint")
|
||||
.header("Access-Control-Request-Method", "PUT")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", CORS_DEFAULT_ORIGIN);
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Methods", "PUT");
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Max-Age", "8000");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestFunctionalEndpoint_thenObtainResponseWithCorsHeaders() {
|
||||
ResponseSpec response = client.put()
|
||||
.uri(BASE_FUNCTIONALS_URL + "/functional-endpoint")
|
||||
.exchange();
|
||||
|
||||
response.expectHeader()
|
||||
.valueEquals("Access-Control-Allow-Origin", CORS_DEFAULT_ORIGIN);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Reference in New Issue
Block a user