Compare commits

...

9 Commits

15 changed files with 286 additions and 32 deletions
Split View
  1. +2
    -0
      demo-client-app/.gitignore
  2. +8
    -1
      demo-client-app/package.json
  3. +13
    -0
      demo-client-app/runProxy.js
  4. +73
    -14
      demo-client-app/src/App.js
  5. +3
    -3
      demo-client-app/src/index.js
  6. +47
    -4
      demo-client-app/yarn.lock
  7. +2
    -0
      demo-resource-server/build.gradle
  8. +2
    -0
      demo-resource-server/lombok.config
  9. +2
    -3
      demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/config/WebSecurityConfig.java
  10. +76
    -0
      demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/config/WebSocketConfig.java
  11. +22
    -0
      demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/config/WebSocketSecurityConfig.java
  12. +0
    -2
      demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/service/UserService.java
  13. +1
    -3
      demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/web/controller/UserInfoController.java
  14. +32
    -0
      demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/websocket/controller/SimpleController.java
  15. +3
    -2
      demo-resource-server/src/main/resources/application.yml

+ 2
- 0
demo-client-app/.gitignore View File

@ -1,5 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
/.eslintcache
# dependencies
/node_modules
/.pnp


+ 8
- 1
demo-client-app/package.json View File

@ -2,6 +2,7 @@
"name": "demo-client-app",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8081",
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
@ -11,7 +12,9 @@
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
"sockjs-client": "^1.5.0",
"web-vitals": "^0.2.4",
"webstomp-client": "^1.2.6"
},
"scripts": {
"start": "react-scripts start",
@ -36,5 +39,9 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"express": "^4.17.1",
"http-proxy-middleware": "^1.0.6"
}
}

+ 13
- 0
demo-client-app/runProxy.js View File

@ -0,0 +1,13 @@
const { createProxyMiddleware } = require("http-proxy-middleware");
const express = require('express');
const PORT = 8080;
console.log(`Starting Proxy on port ${PORT}`);
const app = express();
app.use(createProxyMiddleware({ target: "http://localhost:8081", changeOrigin: true, ws: true }));
app.listen(PORT, () => {
console.log(`Started on http://localhost:${PORT}`);
});

+ 73
- 14
demo-client-app/src/App.js View File

@ -1,19 +1,78 @@
import { useCallback } from "react";
import { useCallback, useEffect } from "react";
import Stomp from "webstomp-client";
import SockJS from "sockjs-client";
import { proxy } from "../package.json";
import logo from "./logo.svg";
import "./App.css";
let stomp;
const connect = async (token) => {
if (stomp != null) return;
let url = "/api/ws";
let message = "Connect to Websocket";
// webpack-dev-server also uses sockjs-client;
// default proxy may incorrectly route requests
// resulting in transport switch,
// which is slow on https.
//
// Hence, we cannot rely on default proxy.
// Requests should be sent directly to a websocket.
if (process.env.NODE_ENV !== "production") {
url = `${proxy}${url}`;
message += " Directly";
}
console.info(message);
const sock = new SockJS(url);
stomp = Stomp.over(sock);
// disable stomp logging
stomp.debug = (msg) => {};
stomp.connect(
// { "X-Authorization": token.split(".").slice(0, 2).join(".") + ".alkdjalskdjals" },
{ "Authorization": `Bearer ${token}` },
(frame) => {
console.log("Connected", frame);
const requestUrl = "/app/simple";
let params = {
tratata: 1,
gratata: 0,
};
stomp.subscribe("/user/simple", (results) => {
console.log(`msg from server: ${results.body}`);
});
stomp.send(requestUrl, JSON.stringify(params));
},
(error) => {
console.error("Disconnected: ", error);
},
);
};
function App({ keycloak }) {
const fetchFromApi = useCallback(async (url) => {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${keycloak.token}`,
},
});
if (response.ok) {
alert(await response.text());
} else {
console.error(response.statusText);
}
const fetchFromApi = useCallback(
async (url) => {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${keycloak.token}`,
},
});
if (response.ok) {
alert(await response.text());
} else {
console.error(response.statusText);
}
},
[keycloak.token],
);
useEffect(() => {
connect(keycloak.token);
}, [keycloak.token]);
return (
@ -27,7 +86,7 @@ function App({ keycloak }) {
className="App-link"
onClick={(e) => {
e.preventDefault();
fetchFromApi("http://localhost:8081/api/userinfo");
fetchFromApi("/api/userinfo");
}}
>
Get user email
@ -37,7 +96,7 @@ function App({ keycloak }) {
className="App-link"
onClick={(e) => {
e.preventDefault();
fetchFromApi("http://localhost:8081/api/users");
fetchFromApi("/api/users");
}}
>
Get all users


+ 3
- 3
demo-client-app/src/index.js View File

@ -7,7 +7,7 @@ import reportWebVitals from './reportWebVitals';
import Keycloak from "keycloak-js";
const initOptions = {
url: 'http://localhost:8080/auth', realm: 'demorealm', clientId: 'react-app', onLoad: 'login-required'
url: 'https://keycloak.cloud.teslametric.com/auth', realm: 'demorealm', clientId: 'react-app', onLoad: 'login-required'
}
const keycloak = Keycloak(initOptions);
@ -36,8 +36,8 @@ keycloak.init({ onLoad: initOptions.onLoad }).then((auth) => {
if (refreshed) {
console.info('Token refreshed' + refreshed);
} else {
console.warn('Token not refreshed, valid for '
+ Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');
// console.warn('Token not refreshed, valid for '
// + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');
}
}).catch(() => {
console.error('Failed to refresh token');


+ 47
- 4
demo-client-app/yarn.lock View File

@ -1687,6 +1687,13 @@
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==
"@types/http-proxy@^1.17.4":
version "1.17.5"
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.5.tgz#c203c5e6e9dc6820d27a40eb1e511c70a220423d"
integrity sha512-GNkDE7bTv6Sf8JbV2GksknKOsk7OznNYHSdrtvPJXO0qJ9odZig6IZKUi5RFGi6d1bf6dgIAe4uXi3DBc7069Q==
dependencies:
"@types/node" "*"
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@ -3840,7 +3847,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@^3.1.1, debug@^3.2.5:
debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
@ -4840,7 +4847,7 @@ faye-websocket@^0.10.0:
dependencies:
websocket-driver ">=0.5.1"
faye-websocket@~0.11.1:
faye-websocket@^0.11.3, faye-websocket@~0.11.1:
version "0.11.3"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
@ -5534,7 +5541,18 @@ http-proxy-middleware@0.19.1:
lodash "^4.17.11"
micromatch "^3.1.10"
http-proxy@^1.17.0:
http-proxy-middleware@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-1.0.6.tgz#0618557722f450375d3796d701a8ac5407b3b94e"
integrity sha512-NyL6ZB6cVni7pl+/IT2W0ni5ME00xR0sN27AQZZrpKn1b+qRh+mLbBxIq9Cq1oGfmTc7BUq4HB77mxwCaxAYNg==
dependencies:
"@types/http-proxy" "^1.17.4"
http-proxy "^1.18.1"
is-glob "^4.0.1"
lodash "^4.17.20"
micromatch "^4.0.2"
http-proxy@^1.17.0, http-proxy@^1.18.1:
version "1.18.1"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
@ -6667,7 +6685,7 @@ json-stringify-safe@~5.0.1:
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
json3@^3.3.2:
json3@^3.3.2, json3@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
@ -9832,6 +9850,18 @@ sockjs-client@1.4.0:
json3 "^3.3.2"
url-parse "^1.4.3"
sockjs-client@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.0.tgz#2f8ff5d4b659e0d092f7aba0b7c386bd2aa20add"
integrity sha512-8Dt3BDi4FYNrCFGTL/HtwVzkARrENdwOUf1ZoW/9p3M8lZdFT35jVdrHza+qgxuG9H3/shR4cuX/X9umUrjP8Q==
dependencies:
debug "^3.2.6"
eventsource "^1.0.7"
faye-websocket "^0.11.3"
inherits "^2.0.4"
json3 "^3.3.3"
url-parse "^1.4.7"
sockjs@0.3.20:
version "0.3.20"
resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855"
@ -10752,6 +10782,14 @@ url-parse@^1.4.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
url-parse@^1.4.7:
version "1.5.0"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.0.tgz#90aba6c902aeb2d80eac17b91131c27665d5d828"
integrity sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@ -11055,6 +11093,11 @@ websocket-extensions@>=0.1.1:
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
webstomp-client@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/webstomp-client/-/webstomp-client-1.2.6.tgz#57e8a044bac0a08bfc3d0e54d43760c2aeefadab"
integrity sha512-9HajO6Ki2ViEGIusLZtjM2lcO2VaQUvtXhLQQ4Cm543RLjfTCEgI3sFaiXts3TvfZgrtY/vI/+qUkm2qWD/NVg==
whatwg-encoding@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"


+ 2
- 0
demo-resource-server/build.gradle View File

@ -22,6 +22,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.security:spring-security-messaging'
runtimeOnly 'org.postgresql:postgresql'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'


+ 2
- 0
demo-resource-server/lombok.config View File

@ -0,0 +1,2 @@
# This file is generated by the 'io.freefair.lombok' Gradle plugin
config.stopBubbling = true

+ 2
- 3
demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/config/WebSecurityConfig.java View File

@ -10,14 +10,13 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/userinfo", "/user/**")
.authenticated()
// .hasAuthority("SCOPE_web-api")
.anyRequest()
.authenticated()
.permitAll()
.and()
.oauth2ResourceServer()
.jwt();


+ 76
- 0
demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/config/WebSocketConfig.java View File

@ -0,0 +1,76 @@
package ru.digitalbanana.demoresourceserver.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
/**
* Web Socket configuration
* Created by dima on 8/12/16.
*/
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Autowired
private JwtDecoder jwtDecoder;
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor!=null && StompCommand.CONNECT.equals(accessor.getCommand())) {
List<String> authorization = accessor.getNativeHeader("Authorization");
String accessToken = authorization != null ? authorization.get(0).split(" ")[1] : "";
Jwt jwt = jwtDecoder.decode(accessToken);
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
Authentication authentication = converter.convert(jwt);
accessor.setUser(authentication);
}
return message;
}
});
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/simple");
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS();
}
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setSendBufferSizeLimit(1024 * 512 * 1024); // default : 512 * 1024
}
}

+ 22
- 0
demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/config/WebSocketSecurityConfig.java View File

@ -0,0 +1,22 @@
package ru.digitalbanana.demoresourceserver.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
/**
* Simple web socket security Created by dima on 8/13/16.
*/
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages.anyMessage().authenticated();
}
@Override
protected boolean sameOriginDisabled() {
return true;
}
}

+ 0
- 2
demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/service/UserService.java View File

@ -1,7 +1,5 @@
package ru.digitalbanana.demoresourceserver.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


+ 1
- 3
demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/web/controller/UserInfoController.java View File

@ -7,7 +7,6 @@ import java.util.stream.StreamSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@ -16,7 +15,6 @@ import ru.digitalbanana.demoresourceserver.persistence.model.UserEntity;
import ru.digitalbanana.demoresourceserver.service.UserService;
@Slf4j
@CrossOrigin(origins = "*")
@RestController
public class UserInfoController {
@ -29,7 +27,7 @@ public class UserInfoController {
@GetMapping(value = "/userinfo")
public String userinfo(@AuthenticationPrincipal Jwt principal) {
return principal.getClaimAsString("email");
return principal.getClaimAsString("sub");
}
@GetMapping(value = "/users")


+ 32
- 0
demo-resource-server/src/main/java/ru/digitalbanana/demoresourceserver/websocket/controller/SimpleController.java View File

@ -0,0 +1,32 @@
package ru.digitalbanana.demoresourceserver.websocket.controller;
import java.security.Principal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import lombok.extern.slf4j.Slf4j;
@Controller
@Slf4j
public class SimpleController {
@Autowired
private SimpMessageSendingOperations messaging;
@MessageMapping("/simple")
public void simple(@AuthenticationPrincipal Principal principal) {
String userId = principal.getName();
log.info("Received simple message from {}", userId);
messaging.convertAndSendToUser(
userId,
"/simple",
"Hello, my friend!!!!!"
);
}
}

+ 3
- 2
demo-resource-server/src/main/resources/application.yml View File

@ -4,6 +4,7 @@ server:
context-path: /api
forward-headers-strategy: framework # nginx reverse proxy
logging.level:
'[ru.digitalbanana.demoresourceserver]': info
'[org.springframework.web]': debug
####### resource server configuration properties
spring:
@ -18,5 +19,5 @@ spring:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/auth/realms/demorealm
jwk-set-uri: http://localhost:8080/auth/realms/demorealm/protocol/openid-connect/certs
issuer-uri: https://keycloak.cloud.teslametric.com/auth/realms/demorealm
jwk-set-uri: https://keycloak.cloud.teslametric.com/auth/realms/demorealm/protocol/openid-connect/certs

Loading…
Cancel
Save