How to configure NGINX with websocket for React app hosted in Kubernetes - reactjs

The websocket (using #stomp/stompjs library) connection works ok on local development bench. Running the app on Azure AKS platform using an NGINX proxy for serving the React app (with also NGINX ingress controller) also works fine. The challenge, however, is the websocket connection in the latter environment.
Firefox browser returns the following error when trying to connect:
Uncaught (in promise) DOMException: An invalid or illegal string was specified ...
i stomp-handler.ts:31
(Async: promise callback)
s stomp-handler.ts:31
da stomp-handler.ts:31
da stomp-handler.ts:31
value client.ts:404
value client.ts:401
In React, the websocket configuration is:
const server = {
name: process.env.REACT_APP_BASE_URL_SOCKET,
}
...
const client = new Client();
client.configure({
brokerURL: server.name,
...
The environment variable is:
REACT_APP_BASE_URL_SOCKET=/ws
NGINX configuration for React app is specified as:
...
http {
...
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
# listen on port 80
listen 80;
server_name foo.com;
gzip off;
proxy_max_temp_file_size 0;
# save logs here
access_log /var/log/nginx/access.log compression;
root /usr/share/nginx/html;
index index.html index.htm;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
try_files $uri $uri/ /index.html =404;
}
location /ws {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://bar-api-service.default.svc.cluster.local:8000;
}
...
}
}
With Docker file as:
FROM nginx:alpine
COPY build/ /usr/share/nginx/html
# Copy our custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Meanwhile, the Kubernetes NGINX Ingress controller is configured as:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foo-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/secure-backends: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/websocket-services: bar-ui-service
nginx.org/websocket-services: bar-ui-service
spec:
tls:
- hosts:
- foo.com
secretName: tls-secret
rules:
- host: foo.com
http:
paths:
- pathType: Prefix
backend:
service:
name: bar-ui-service
port:
number: 80
path: /
With "bar-ui-service" as:
apiVersion: v1
kind: Service
metadata:
name: bar-ui-service
spec:
type: NodePort
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
selector:
app: bar-ui
And "bar-api-service" as:
apiVersion: v1
kind: Service
metadata:
name: bar-api-service
spec:
selector:
app: bar-api
tier: backend
ports:
port: 8000
targetPort: 8000
In the Spring Boot API serving the websocket, Spring Security is used, with OAuth2 resource server. Configuration of HttpSecurity is:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable()
.formLogin().disable()
.and()
.cors()
.and()
.authorizeRequests()
.antMatchers("/ws").permitAll()
And websocket broker config as:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("foo.com");
}
}
How do I get the websocket to work in k8s?

To solve this challenge in Kubernetes, I had to change the WebSocket library. Simply put, the '#stomp/stompjs' library (v6.1.2) cannot upgrade from http to ws. After trying out several libraries (yes, JavaScript WebSocket libraries seem to be a dime a dozen out there!), I settled on react-stomp-hooks (v2.1.0).
For those who might be curious about the fuller solution, read on.
In React (TS), the 'react-stomp-hook' library comes with a convenient Provider component for connecting to the server. This may be added to App.tsx like so:
...
import {
StompSessionProvider,
} from "react-stomp-hooks";
import WebSocketSubscriptions from './shared/WebSocketSubscription';
...
const socketHeaders = {
login: token.userId,
passcode: <can be anything>
}
return (
<React.Fragment>
{token &&
<StompSessionProvider
url={server.name}
connectHeaders={socketHeaders}>
<WebSocketSubscriptions />
</StompSessionProvider>}
...
The url ('server.name') is from an environment variable defined as:
REACT_APP_BASE_URL_SOCKET=/ws
for Kubernetes deployment, and:
REACT_APP_BASE_URL_SOCKET=http://localhost:8000/ws
in the local development environment.
In order to work, the web socket's connection header requires to pass down a user identifier to match with Spring Security's user authentication (shown below). In my example, this is provided by the OAuth 2.0 JWT token. However, the "passcode", although a required parameter, can be anything. Note the Provider enables subscriptions to be defined in a separate component (or components) using a useSubscription hook, for example:
import {
useSubscription,
} from "react-stomp-hooks";
const WebSocketSubscriptions: React.FC = () => {
useSubscription("/app/subscribe", (message) => {
console.log(`..subscribed ${message}`);
});
...
The React-serving NGINX proxy configuration is as follows (you can see the fuller implementation in my question above):
...
location /ws {
proxy_pass http://bar-api-service.default.svc.cluster.local:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
}
...
Meanwhile, the Kubernetes NGINX ingress controller need only deal with secure termination of traffic, like:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foo-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt
spec:
tls:
- hosts:
- foo.com
secretName: tls-secret
rules:
- host: foo.com
...
The heavy-lifting now comes in the Spring Boot (v2.7.3) API where the WebSocket server resides. Addition of Spring Security is what makes the solution hairy.
At least the following dependencies are required:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</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-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-messaging</artifactId>
<version>5.6.2</version>
</dependency>
First let's look at the websocket's support files:
The AuthChannelInterceptorAdaptor class authenticates the user:
#Component
public class AuthChannelInterceptorAdaptor implements ChannelInterceptor {
private static final String USERNAME_HEADER = "login";
private static final String PASSWORD_HEADER = "passcode";
private final WebSocketAuthenticatorService webSocketAuthenticatorService;
#Autowired
public AuthChannelInterceptorAdaptor(final WebSocketAuthenticatorService webSocketAuthenticatorService) {
this.webSocketAuthenticatorService = webSocketAuthenticatorService;
}
#Override
public Message<?> preSend(final Message<?> message, final MessageChannel channel)
throws AuthenticationException {
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT == accessor.getCommand()) {
final String username = accessor.getFirstNativeHeader(USERNAME_HEADER);
final String password = accessor.getFirstNativeHeader(PASSWORD_HEADER);
final UsernamePasswordAuthenticationToken user =
webSocketAuthenticatorService.getAuthenticatedOrFail(username, password);
accessor.setUser(user);
}
return message;
}
}
As mentioned earlier, for an API server using the OAuth 2.0 Resource library, only the username need be provided; password can be anything. Spring Boot Security simply authenticates the user id (from the websocket's header login) and accepts the connection.
The authentication is carried out in the WebSocketAuthenticatorService class:
/*
courtesy: Anthony Raymond: https://stackoverflow.com/questions/45405332/websocket-authentication-and-authorization-in-spring
*/
#Component
public class WebSocketAuthenticatorService {
public UsernamePasswordAuthenticationToken getAuthenticatedOrFail(final String username,
final String password)
throws AuthenticationException {
if (username == null || username.trim().isEmpty()) {
throw new AuthenticationCredentialsNotFoundException("Username was null or empty.");
}
if (password == null || password.trim().isEmpty()) {
throw new AuthenticationCredentialsNotFoundException("Password was null or empty.");
}
// You can add your own logic for retrieving user from, say, db
//if (fetchUserFromDb(username, password) == null) {
// throw new BadCredentialsException("Bad credentials for user " + username);
// Null credentials, we do not pass the password along
return new UsernamePasswordAuthenticationToken(
username,
null,
Collections.singleton((GrantedAuthority) () -> "USER") // MUST provide at least one role
);
}
}
The WebSocketAuthorizationSecurityConfig class extends AbstractSecurityWebSocketMessageBrokerConfigurer, allowing configuration of inbound messages:
#Configuration
public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) {
// You can customize your authorization mapping here.
messages.anyMessage().authenticated();
}
#Override
protected boolean sameOriginDisabled() {
return false;
}
}
Then we come to the WebSocketBrokerConfig class, which implements WebSocketMessageBrokerConfigurer:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("http://localhost:3000", "foo.com")
.withSockJS();
}
}
This in-memory broker is good for training and POCs. In production, however, a more robust provider like RabbitMQ or Kafka is called for.
Another implementation of the WebSocketMessageBrokerConfigurer is required for configuring the inbound channel. This is implemented in the class WebSocketSecurityConfig:
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
#Autowired
private AuthChannelInterceptorAdaptor authChannelInterceptorAdapter;
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
// Endpoints are already registered on WebSocketConfig, no need to add more.
}
#Override
public void configureClientInboundChannel(final ChannelRegistration registration) {
registration.setInterceptors(authChannelInterceptorAdapter);
}
}
Note the #Order annotation, which configures a high precedence for the channel.
The React app client move from a ws-based websocket connection to an http-brokered one also requires a CORS filter to work. The example filter looks like:
/*
courtesy of https://stackoverflow.com/users/3669624/cнŝdk
*/
#Component
public class CORSFilter implements Filter {
private final List<String> allowedOrigins = Arrays.asList(
"http://localhost:3000", "foo.com");
public void destroy() {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// Lets make sure that we are working with HTTP (that is, against HttpServletRequest and HttpServletResponse objects)
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// Access-Control-Allow-Origin
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", allowedOrigins.contains(origin) ? origin : "");
response.setHeader("Vary", "Origin");
// Access-Control-Max-Age
response.setHeader("Access-Control-Max-Age", "3600");
// Access-Control-Allow-Credentials
response.setHeader("Access-Control-Allow-Credentials", "true");
// Access-Control-Allow-Methods
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
// Access-Control-Allow-Headers
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, " + "X-CSRF-TOKEN");
}
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {
}
}
The WebSocketController class simply provides an endpoint for the websocket's subscriptions and support for discrete '/queues'.
#Controller
public class WebSocketController {
#MessageMapping("/subscribe")
#SendToUser("/queue/notification")
public String replyToAccountFromClient(#Payload String message,
Principal user) {
return String.format("hello: %s", message);
}
#MessageExceptionHandler
#SendTo("/queue/errors")
public String handleException(Throwable exception) {
return exception.getMessage();
}
}
Finally, the HttpSecurity configuration looks like the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable()
.formLogin().disable()
.addFilterAt(corsFilter, BasicAuthenticationFilter.class)
.csrf().ignoringAntMatchers(API_URL_PREFIX)
.and()
.cors()
.and()
.authorizeRequests()
.antMatchers("/ws/**").permitAll()
...
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer.jwt(jwt ->
jwt.jwtAuthenticationConverter(
getJwtAuthenticationConverter())))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

Related

React and nginx: refused to connect to localhost

I have my React (frontend) and Django REST (backend) running on a remote Ubuntu server with nginx. I also have a simple reverse proxy defined in conf.d/bookmarks.conf to manage all of that:
server {
listen 80;
listen [::]:80;
location /api/ { # Backend
proxy_pass http://127.0.0.1:1337/api/;
}
location / { # Frontend
root /var/www/bookmarks/html/;
index index.html;
try_files $uri $uri/ /index.html;
}
}
I run my Django app with runserver python manage.py runserver 127.0.0.1:1337, and React static files are stored in the folder described above
I try to connect to API with React:
const generateOpendIdLink = async () => {
const generateOpendIdLinkEndpoint = 'http://127.0.0.1/api/opendid-link-generator/';
const requestOptions = {
method: 'GET',
headers: {'Content-Type': 'application/json'},
};
try {
const response = await fetch(generateOpendIdLinkEndpoint, requestOptions);
if (response.status == 200) {
...
};
} catch (error) {
console.log(error);
};
};
And get an error
GET http://127.0.0.1/api/opendid-link-generator/ net::ERR_CONNECTION_REFUSED
This is quite odd, because I could connect to API from the web no problem: running the same GET on the server IP address http://XX.XXX.XX.XX/api/opendid-link-generator/ from Postman works as expected. This is also true when I change 127.0.0.1 for the server IP in the React code, it all starts to work.
I also set ALLOWED_HOSTS = ['*'] for test purposes while trying to solve this problem, and it did not help. Does anyone know what could be the source of the problem?
I don't know anything about javascript, but let me give it a shot:
const generateOpendIdLinkEndpoint = 'http://127.0.0.1/api/opendid-link-generator/';
This definition does not include the port! Most likely it looks up the default http port at :80 then. Try to include the port and let me know!

nginx serve both django static files and react static files

Im recently trying to impement nginx to link up my django based backend and react based frontend. Here is my nginx config file:
upstream api {
server backend:8000;
}
server {
listen 8080;
location /api/ {
uwsgi_pass backend:8000;
include /etc/nginx/uwsgi_params;
}
location /admin/ {
uwsgi_pass backend:8000;
include /etc/nginx/uwsgi_params;
}
# ignore cache frontend
location ~* (service-worker\.js)$ {
add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
expires off;
proxy_no_cache 1;
}
location / {
root /var/www/frontend;
try_files $uri /index.html;
}
location /static {
alias /vol/static;
}
location /static/rest_framework/ {
alias /vol/static;
}
}
However, the issue is that my manage.py collect static will place the django static files into /vol/static however, my react build is in /var/www/frontend/ therefore when I try accessing the default / url, it returns a blank page. However, if i remove the
location /static {
alias /vol/static;
}
location /static/rest_framework/ {
alias /vol/static;
}
It will serve my my django admin page without any css. How do i resolve this easily without putting both static files in the same directory?

Nginx configuration - Can't make connection for separate 3 individual servers (Client/API/DB)

What I am trying to do
I'm trying to setup connection between 3 individual servers (Client, API, and mySQL) by creating 3 VMs locally with following ip addresses and simple testing React/Node code below.
Client with React JS 192.168.56.103 Port 3000
API with Node JS 192.168.56.104 Port 4000
mySQL 192.168.56.105
Problem
The API server can connect to mySQL server successfully. However, Client server seems to unable connect to API server. I got the following error
GET http://192.168.56.103:3000/192.168.56.104/products 404 (Not Found)
and this error
SyntaxError: Unexpected token < in JSON at position 0
I'm trying to solve this issue for almost a day now. Please help.
Nginx Configuration
I am using Nginx as web server on both Client and API server. Here is Client Nginx configuration.
server {
listen 80 default_server;
listen [::]:80 default_server;
root /sample_application/sample_front/public;
index index.html ;
server_name _;
location / {
try_files $uri $uri/ =404;
}
Here is API Nginx configuration.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location /products {
proxy_pass http://localhost:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
React/Node code
The following is part of React js code. I have already test this out locally on localhost (without VMs) They all works perfectly fine. But when on VM it shows only static information but data from the back-end and database.
getProducts = _ => {
fetch(BACK_URL + '/products')
//fetch(BACK_URL + ':' +BACK_PORT + '/products')
.then(response => response.json())
.then(response => this.setState({ products: response.data }))
.catch(err => console.error(err))
}
addProduct = _ => {
const { product } = this.state;
fetch(BACK_URL + '/products/add?name=' + product.name)
//fetch(BACK_URL + ':' +BACK_PORT + '/products/add?name=' + product.name)
.then(this.getProducts)
.catch(err => console.error(err))
}
The following is part of Node js code. The console log as 'Connected success'. Accessing 192.168.56.104:4000, data from the database display successfully.
connection.connect(function(err) {
if (err) throw(err);
console.log("Connected success");
});
app.use(cors());
app.get('/products', (req, res) => {
connection.query(SELECT_ALL_PRODUCTS_QUERY, (err, results) => {
if(err) {
return res.send(err)
}
else {
return res.json({
data: results
})
}
});
});
app.listen(BACK_URL.BACK_PORT, () => {
console.log('Back-end listening on port' + ' ' + BACK_URL.BACK_PORT)
});
Note
I have test React/Node/mySQL locally runing npm start and node command on localhost. Opening through browser, they work perfectly.
My Knowledge.
New to both coding and server deployment.
I found the solution.
Just add http:// before the ipaddress

.Net Core 2.1 React and Signalr 1.0.4 throwing a /negotiate 404 error

I am currently working with React and SignalR. I am using .Net Core 2.1 and the Microsoft.AspNetCore.App package to get the recent SignalR. I have installed #aspnet/signalr as well and I keep getting a 404 error because it is still attempting to go to the /negotiate endpoint which I know it doesn't use anymore. I have confirmed I am up to date on on all packages. Any pointers on where I should be going?
const hubConnection = new HubConnectionBuilder().withUrl('http://localhost:3000/ChatHub').build();
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
//{
// builder
// .AllowAnyMethod()
// .AllowAnyHeader()
// .WithOrigins("http://localhost:3000");
//}));
services.AddSignalR();
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
//app.UseCors("CorsPolicy");
app.UseFileServer();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/ChatHub");
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
//app.UseMvc(routes =>
//{
// routes.MapRoute(
// name: "default",
// template: "{controller}/{action=Index}/{id?}");
//});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
I keep getting a 404 error because it is still attempting to go to the /negotiate endpoint which I know it doesn't use anymore
ASP.NET Core SignalR definitely still uses the /negotiate endpoint. In the previews, we used an OPTIONS request instead but that caused a lot of problems so we went back to the /negotiate endpoint.
It looks like you're using a "development server" (spa.UseReactDevelopmentServer in your Startup). That usually means that the development server is serving up your HTML/JS content from a different server than your ASP.NET Core app is running on (rather than just being static files served by the ASP.NET Core app). If that's the case, you need to reference the ASP.NET Core server using a full URL when you connect.
This is because your HTML/JS content is being served by the development server at http://localhost:X but your ASP.NET Core server is running at http://localhost:Y. So when you use /ChatHub as a URL, the browser interprets that as http://localhost:X/ChatHub, so you aren't hitting your ASP.NET Core app (with the SignalR server) but rather then dev server, which has no content at that URL and produces a 404.

Spring microservices SSO with ReactJS

I'm working on single-sign-on implementation in my project with microservice architecture application, using spring-cloud-netflix. At the moment I have done with OAuth2 service and gateway service.
Here's my security config for gateway:
/**
* SSO security config.
*/
#Configuration
#EnableZuulProxy
#EnableOAuth2Sso
#EnableWebSecurity
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2ClientAuthenticationProcessingFilter ssoFilter;
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private SsoLogoutSuccessHandler logoutSuccessHandler;
/**
* SSO http config.
*
* #param http configurer
* #throws Exception exception
*/
#Override
public void configure(HttpSecurity http) throws Exception {
http
// .cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/test", "/favicon.ico", "/sockjs-node/**", "/static/**", "/*.js", "/*.jpg",
"/rest/**", "/uaa/**", "/backend/**",
"/users/**", "/files/**", "/roles/**").permitAll()
.anyRequest().authenticated().and()
// .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.logout().logoutSuccessHandler(logoutSuccessHandler)
.and()
.sessionManagement().maximumSessions(1)
.expiredUrl("/")
.maxSessionsPreventsLogin(false);
// http.addFilterAfter(ssoFilter, BasicAuthenticationFilter.class);
}
/**
* OAuth2 config.
*/
#Configuration
protected static class OAuth2Config {
#Bean
public OAuth2ClientAuthenticationProcessingFilter ssoFilter(
SsoLoginSuccessHandler ssoLoginSuccessHandler,
OAuth2ClientContext beaconOAuth2ClientContext,
RemoteTokenServices remoteTokenServices,
OAuth2ProtectedResourceDetails resourceDetails) {
OAuth2ClientAuthenticationProcessingFilter filter =
new OAuth2ClientAuthenticationProcessingFilter("/login");
filter.setRestTemplate(new OAuth2RestTemplate(resourceDetails,
beaconOAuth2ClientContext));
filter.setTokenServices(remoteTokenServices);
// filter.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler());
return filter;
}
//
// #Bean
// public CorsConfigurationSource corsConfigurationSource() {
// final CorsConfiguration configuration = new CorsConfiguration();
// configuration.setAllowedOrigins(ImmutableList.of("*"));
// configuration.setAllowedMethods(ImmutableList.of("HEAD",
// "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
// configuration.setAllowCredentials(true);
// configuration.setAllowedHeaders(
// ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
// final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// source.registerCorsConfiguration("/**", configuration);
// return source;
// }
}
}
gateway yaml config
security:
oauth2:
client:
preEstablishedRedirectUri: http://localhost:3000/login
registeredRedirectUri: http://localhost:3000/login
accessTokenUri: http://localhost:10035/uaa/oauth/token
userAuthorizationUri: http://localhost:10035/uaa/oauth/authorize
clientId: react_app
clientSecret: react_app_secret
useCurrentUri: true
resource:
tokenInfoUri: http://localhost:10035/uaa/oauth/check_token
I'm bit confused, how i should authorize react app client, if it running on different port (webpack dev server, port 3000)?
One alternative is to setup a proxy using webpack, for example, in your webpack configuration add it like this:
devServer: {
contentBase: '/static',
historyApiFallback: true,
port: 3000,
compress: false,
inline: false,
hot: true,
host: '0.0.0.0',
proxy: {
'/api': { // <---- Intercept all calls to `/api`
target: 'http://localhost:8080', // <--- Your API server in a different port
changeOrigin: true,
secure: false,
},
},
},
The previous configuration setup a proxy, so whenever there's a request to /api, it will proxy the request to your server running in the other port.

Resources