From 8f092630e35816afc73b1ba59b505136eb74e4b2 Mon Sep 17 00:00:00 2001 From: Paresh Arvind Patil Date: Thu, 5 Jun 2025 10:24:01 -0700 Subject: [PATCH 1/4] Add websocket auth code --- CallAutomation_Live_Transcription/pom.xml | 10 +++++++++ .../JwtHandshakeInterceptor.java | 0 .../communication/callautomation/JwtUtil.java | 21 +++++++++++++++++++ .../callautomation/SecurityConfig.java | 19 +++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java create mode 100644 CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtUtil.java create mode 100644 CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java diff --git a/CallAutomation_Live_Transcription/pom.xml b/CallAutomation_Live_Transcription/pom.xml index 60fed6ce..7bc2d4ab 100644 --- a/CallAutomation_Live_Transcription/pom.xml +++ b/CallAutomation_Live_Transcription/pom.xml @@ -135,6 +135,16 @@ json 20231013 + + + org.springframework.boot + spring-boot-starter-security + + + io.jsonwebtoken + jjwt + 0.9.1 + diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java new file mode 100644 index 00000000..e69de29b diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtUtil.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtUtil.java new file mode 100644 index 00000000..ea8cfbe3 --- /dev/null +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtUtil.java @@ -0,0 +1,21 @@ +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureException; + +public class JwtUtil { + + private static final String SECRET_KEY = ""; + + public static boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); + return true; + } catch (SignatureException e) { + return false; + } + } + + public static Claims getClaims(String token) { + return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); + } +} diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java new file mode 100644 index 00000000..8b847651 --- /dev/null +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java @@ -0,0 +1,19 @@ +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf().disable() + .authorizeHttpRequests() + .requestMatchers("/ws/**").permitAll() // WebSocket handshake is handled separately + .anyRequest().authenticated(); + + return http.build(); + } +} From 4dbccdb319df4d0348d4714193c4b844adb492ce Mon Sep 17 00:00:00 2001 From: Paresh Arvind Patil Date: Thu, 5 Jun 2025 11:39:51 -0700 Subject: [PATCH 2/4] Add websocket auth implementation --- .../JwtHandshakeInterceptor.java | 36 +++++++++++++++++++ .../callautomation/WebSocketConfig.java | 1 + .../callautomation/WebSocketHandler.java | 9 +++++ 3 files changed, 46 insertions(+) diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java index e69de29b..4acb44fa 100644 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java @@ -0,0 +1,36 @@ +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import java.util.Map; + +public class JwtHandshakeInterceptor implements HandshakeInterceptor { + + @Override + public boolean beforeHandshake(ServerHttpRequest request, + ServerHttpResponse response, + WebSocketHandler wsHandler, + Map attributes) { + String token = request.getHeaders().getFirst("Authorization"); + + if (token != null && token.startsWith("Bearer ")) { + token = token.substring(7); + // Add Util to validate the token + if (JwtUtil.validateToken(token)) { + attributes.put("jwt", token); + return true; + } + } + + return false; + } + + @Override + public void afterHandshake(ServerHttpRequest request, + ServerHttpResponse response, + WebSocketHandler wsHandler, + Exception exception) { + // No-op + } +} diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketConfig.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketConfig.java index 20ab61f5..adbd3db1 100644 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketConfig.java +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketConfig.java @@ -12,6 +12,7 @@ public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new WebSocketHandler(), "/ws") // Registering WebSocket handler + .addInterceptors(new JwtHandshakeInterceptor()) .setAllowedOrigins("*"); // Allow connections from any origin (adjust as needed) } } diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketHandler.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketHandler.java index cb3569d9..cf092406 100644 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketHandler.java +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketHandler.java @@ -58,4 +58,13 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message) // Send an echo response back to the client session.sendMessage(new TextMessage("Echo: " + payload)); } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + String token = (String) session.getAttributes().get("jwt"); + Claims claims = JwtUtil.getClaims(token); + String username = claims.getSubject(); + System.out.println("Authenticated user: " + username); + } + } From 325025d0e9b36490d3ce14c18d311f847b396f47 Mon Sep 17 00:00:00 2001 From: Paresh Arvind Patil Date: Thu, 5 Jun 2025 12:19:24 -0700 Subject: [PATCH 3/4] updated the auth logic --- CallAutomation_Live_Transcription/pom.xml | 14 ++++++- .../JwtHandshakeInterceptor.java | 36 ------------------ .../communication/callautomation/JwtUtil.java | 21 ----------- .../callautomation/SecurityConfig.java | 37 ++++++++++++++++++- .../callautomation/WebSocketConfig.java | 1 - .../callautomation/WebSocketHandler.java | 9 ----- 6 files changed, 48 insertions(+), 70 deletions(-) delete mode 100644 CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java delete mode 100644 CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtUtil.java diff --git a/CallAutomation_Live_Transcription/pom.xml b/CallAutomation_Live_Transcription/pom.xml index 7bc2d4ab..aeb6d082 100644 --- a/CallAutomation_Live_Transcription/pom.xml +++ b/CallAutomation_Live_Transcription/pom.xml @@ -135,7 +135,6 @@ json 20231013 - org.springframework.boot spring-boot-starter-security @@ -145,6 +144,18 @@ jjwt 0.9.1 + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-oauth2-jose + + + org.springframework.security + spring-security-oauth2-resource-server + @@ -197,7 +208,6 @@ - diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java deleted file mode 100644 index 4acb44fa..00000000 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtHandshakeInterceptor.java +++ /dev/null @@ -1,36 +0,0 @@ -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.server.HandshakeInterceptor; - -import java.util.Map; - -public class JwtHandshakeInterceptor implements HandshakeInterceptor { - - @Override - public boolean beforeHandshake(ServerHttpRequest request, - ServerHttpResponse response, - WebSocketHandler wsHandler, - Map attributes) { - String token = request.getHeaders().getFirst("Authorization"); - - if (token != null && token.startsWith("Bearer ")) { - token = token.substring(7); - // Add Util to validate the token - if (JwtUtil.validateToken(token)) { - attributes.put("jwt", token); - return true; - } - } - - return false; - } - - @Override - public void afterHandshake(ServerHttpRequest request, - ServerHttpResponse response, - WebSocketHandler wsHandler, - Exception exception) { - // No-op - } -} diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtUtil.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtUtil.java deleted file mode 100644 index ea8cfbe3..00000000 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/JwtUtil.java +++ /dev/null @@ -1,21 +0,0 @@ -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureException; - -public class JwtUtil { - - private static final String SECRET_KEY = ""; - - public static boolean validateToken(String token) { - try { - Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); - return true; - } catch (SignatureException e) { - return false; - } - } - - public static Claims getClaims(String token) { - return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); - } -} diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java index 8b847651..47f08a45 100644 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java @@ -12,8 +12,43 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .csrf().disable() .authorizeHttpRequests() .requestMatchers("/ws/**").permitAll() // WebSocket handshake is handled separately - .anyRequest().authenticated(); + .anyRequest() + .and() + .oauth2ResourceServer() + .jwt() + .decoder(jwtDecoder()); return http.build(); } + + class AudienceValidator implements OAuth2TokenValidator { + private String audience; + + OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null); + + public AudienceValidator(String audience) { + this.audience = audience; + } + + @Override + public OAuth2TokenValidatorResult validate(Jwt token) { + if (token.getAudience().contains(audience)) { + return OAuth2TokenValidatorResult.success(); + } else { + return OAuth2TokenValidatorResult.failure(error); + } + } + } + + JwtDecoder jwtDecoder() + { + OAuth2TokenValidator withAudience = new AudienceValidator(audience); + OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuer); + OAuth2TokenValidator validator = new DelegatingOAuth2TokenValidator<>(withAudience, withIssuer); + NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(issuer); + jwtDecoder.setJwtValidator(validator); + + return jwtDecoder; + } + } diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketConfig.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketConfig.java index adbd3db1..20ab61f5 100644 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketConfig.java +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketConfig.java @@ -12,7 +12,6 @@ public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new WebSocketHandler(), "/ws") // Registering WebSocket handler - .addInterceptors(new JwtHandshakeInterceptor()) .setAllowedOrigins("*"); // Allow connections from any origin (adjust as needed) } } diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketHandler.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketHandler.java index cf092406..cb3569d9 100644 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketHandler.java +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/WebSocketHandler.java @@ -58,13 +58,4 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message) // Send an echo response back to the client session.sendMessage(new TextMessage("Echo: " + payload)); } - - @Override - public void afterConnectionEstablished(WebSocketSession session) throws Exception { - String token = (String) session.getAttributes().get("jwt"); - Claims claims = JwtUtil.getClaims(token); - String username = claims.getSubject(); - System.out.println("Authenticated user: " + username); - } - } From bc2253468dfba417790198e9867158ca92d658a8 Mon Sep 17 00:00:00 2001 From: Paresh Arvind Patil Date: Thu, 5 Jun 2025 12:41:21 -0700 Subject: [PATCH 4/4] add issuer and audience --- .../com/communication/callautomation/SecurityConfig.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java index 47f08a45..a9042a30 100644 --- a/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java +++ b/CallAutomation_Live_Transcription/src/main/java/com/communication/callautomation/SecurityConfig.java @@ -6,6 +6,12 @@ @Configuration public class SecurityConfig { + @Value("ACS resource ID") + private String audience; + + @Value("https://acscallautomation.communication.azure.com") + private String issuer; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http