Merge commit 'd6ab7788a14d20306ebe43a2adfca425c2712491'

This commit is contained in:
andrea.terzani
2025-04-17 16:26:45 +02:00
8 changed files with 207 additions and 137 deletions

24
pom.xml
View File

@@ -47,6 +47,15 @@
<artifactId>jtokkit</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>azure-spring-boot-starter-active-directory</artifactId>
<version>4.0.0</version> <!-- or latest -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
@@ -97,18 +106,21 @@
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>

View File

@@ -8,13 +8,10 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Value("${hermione.fe.url}")
private String hermione_frontend_url;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(hermione_frontend_url)
.allowedOriginPatterns("**")
.allowedHeaders("*")
.allowedMethods("GET", "POST", "PUT", "DELETE","OPTIONS");

View File

@@ -1,16 +1,15 @@
package com.olympus.hermione.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@@ -19,6 +18,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
import com.olympus.hermione.security.filter.JwtTokenFilter;
import com.olympus.hermione.security.services.CustomUserDetailsService;
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@@ -26,18 +26,11 @@ public class SecurityConfig {
@Autowired
CustomUserDetailsService userDetailsService;
/* @Autowired
private AuthEntryPointJwt unauthorizedHandler;
*/
@Bean
public JwtTokenFilter authenticationJwtTokenFilter() {
return new JwtTokenFilter();
}
//@Override
//public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
//}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
@@ -49,16 +42,6 @@ public class SecurityConfig {
return authProvider;
}
//@Bean
//@Override
//public AuthenticationManager authenticationManagerBean() throws Exception {
// return super.authenticationManagerBean();
//}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
@@ -66,23 +49,43 @@ public class SecurityConfig {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public SecurityFilterChain oauth2Security(HttpSecurity http) throws Exception {
http
.securityMatcher("/msauth/**") // Match only specific OAuth2 endpoints
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt()); // Enable OAuth2 JWT Validation
return http.build();
}
// 🔓 2. Default security for all other URLs
@Bean
public SecurityFilterChain defaultSecurity(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**", "/login").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.requestMatchers("/test/**").permitAll()
.anyRequest().permitAll());//.authenticated());
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.build();
}
}

View File

@@ -1,88 +1,30 @@
package com.olympus.hermione.security.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.olympus.hermione.security.dto.AuthenticationRequest;
import com.olympus.hermione.security.dto.AuthenticationResponse;
import com.olympus.hermione.security.dto.FetchUserResponse;
import com.olympus.hermione.security.entity.User;
import com.olympus.hermione.security.utility.JwtTokenProvider;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import com.olympus.hermione.security.services.JwtService;
@RestController
@RequestMapping("/api/auth")
@RequestMapping("/msauth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider jwtTokenProvider;
private final JwtService jwtService;
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@RequestBody AuthenticationRequest authenticationRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.createToken(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authorization", jwt);
httpHeaders.add("access-control-expose-headers", "authorization");
AuthenticationResponse authenticationResponse = new AuthenticationResponse(jwt, (User) authentication.getPrincipal());
return ResponseEntity.ok().headers(httpHeaders).body(authenticationResponse);
public AuthController(JwtService jwtService) {
this.jwtService = jwtService;
}
@GetMapping("/fetch-user")
public FetchUserResponse fetchUser(Authentication authentication) {
User principal = (User) authentication.getPrincipal();
principal.setPassword(null);
FetchUserResponse fetchUserResponse = new FetchUserResponse();
fetchUserResponse.setData(principal);
return fetchUserResponse;
}
@GetMapping("/refresh-token")
public ResponseEntity<?> refreshToken(Authentication authentication) {
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.createToken(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authorization", jwt);
httpHeaders.add("access-control-expose-headers", "authorization");
AuthenticationResponse authenticationResponse = new AuthenticationResponse(jwt, (User) authentication.getPrincipal());
return ResponseEntity.ok().headers(httpHeaders).body(authenticationResponse);
}
@GetMapping("/test")
public ResponseEntity<?> test() {
return ResponseEntity.ok(" you have access now ");
@PostMapping("/exchange")
public ResponseEntity<?> exchangeToken(@AuthenticationPrincipal Jwt azureJwt) {
String internalToken = jwtService.generateInternalToken(azureJwt);
return ResponseEntity.ok(Map.of("token", internalToken));
}
}

View File

@@ -0,0 +1,87 @@
package com.olympus.hermione.security.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.olympus.hermione.security.dto.AuthenticationRequest;
import com.olympus.hermione.security.dto.AuthenticationResponse;
import com.olympus.hermione.security.dto.FetchUserResponse;
import com.olympus.hermione.security.entity.User;
import com.olympus.hermione.security.services.JwtService;
@RestController
@RequestMapping("/api/auth")
public class LoginController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtService jwtTokenProvider;
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@RequestBody AuthenticationRequest authenticationRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.generateInternalTokenFromUsername(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authorization", jwt);
httpHeaders.add("access-control-expose-headers", "authorization");
AuthenticationResponse authenticationResponse = new AuthenticationResponse(jwt, (User) authentication.getPrincipal());
return ResponseEntity.ok().headers(httpHeaders).body(authenticationResponse);
}
@GetMapping("/fetch-user")
public FetchUserResponse fetchUser(Authentication authentication) {
User principal = (User) authentication.getPrincipal();
principal.setPassword("fake");
FetchUserResponse fetchUserResponse = new FetchUserResponse();
fetchUserResponse.setData(principal);
return fetchUserResponse;
}
@GetMapping("/refresh-token")
public ResponseEntity<?> refreshToken(Authentication authentication) {
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.generateInternalTokenFromUsername(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authorization", jwt);
httpHeaders.add("access-control-expose-headers", "authorization");
AuthenticationResponse authenticationResponse = new AuthenticationResponse(jwt, (User) authentication.getPrincipal());
return ResponseEntity.ok().headers(httpHeaders).body(authenticationResponse);
}
@GetMapping("/test")
public ResponseEntity<?> test() {
return ResponseEntity.ok(" you have access now ");
}
}

View File

@@ -11,7 +11,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.olympus.hermione.security.utility.JwtTokenProvider;
import com.olympus.hermione.security.services.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -26,7 +26,7 @@ import lombok.NoArgsConstructor;
public class JwtTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider jwtTokenProvider;
private JwtService jwtTokenProvider;
@Autowired
private UserDetailsService userDetailsService;

View File

@@ -1,24 +1,31 @@
package com.olympus.hermione.security.utility;
package com.olympus.hermione.security.services;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.*;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.security.Key;
import java.util.Date;
@Component
@Slf4j
public class JwtTokenProvider {
@Service
@Slf4j
public class JwtService {
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
private final long EXPIRATION_MS = 3600_000; // 1 hour
public static final String SECRET = "5367566B59703373367639792F423F4528482B4D6251655468576D5A713474375367566B59703373367639792F423F4528482B4D6251655468576D5A71347437";
@@ -28,25 +35,41 @@ public class JwtTokenProvider {
byte[] keyBytes = Decoders.BASE64.decode(SECRET);
return Keys.hmacShaKeyFor(keyBytes);
}
public String createToken(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
public String generateInternalToken(Jwt azureJwt) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + 3600000);
Date expiry = new Date(now.getTime() + EXPIRATION_MS);
String email =azureJwt.getClaim("email");
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setSubject(email)
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.setExpiration(expiry)
.signWith(getSignKey(), SignatureAlgorithm.HS512)
//.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public String generateInternalTokenFromUsername(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Date now = new Date();
Date expiry = new Date(now.getTime() + EXPIRATION_MS);
public String resolveToken(HttpServletRequest request) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(getSignKey(), SignatureAlgorithm.HS512)
.compact();
}
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
@@ -84,4 +107,4 @@ public class JwtTokenProvider {
.getBody()
.getSubject();
}
}
}

View File

@@ -51,6 +51,8 @@ logging.level.org.springframework.ai.chat.client.advisor=INFO
eureka.client.serviceUrl.defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
eureka.instance.preferIpAddress: true
#logging.level.root=TRACE
hermione.fe.url = http://localhost:5173
@@ -74,4 +76,8 @@ file.upload-dir=/mnt/hermione_storage/documents/file_input_scenarios/
generic-file-parser-module.url=http://generic-file-parser-module-service.olympus.svc.cluster.local:8080
java-parser-module.url: http://java-parser-module-service.olympus.svc.cluster.local:8080
java-re-module.url: http://java-re-module-service.olympus.svc.cluster.local:8080
jsp-parser-module.url: http://jsp-parser-module-service.olympus.svc.cluster.local:8080
jsp-parser-module.url: http://jsp-parser-module-service.olympus.svc.cluster.local:8080
spring.security.oauth2.resourceserver.jwt.issuer-uri= https://sts.windows.net/e0793d39-0939-496d-b129-198edd916feb/