From 9349393e2b0e10484de478a1604089d821340958 Mon Sep 17 00:00:00 2001 From: "andrea.terzani" Date: Wed, 16 Apr 2025 20:40:45 +0200 Subject: [PATCH] working msal --- pom.xml | 24 +++-- .../hermione/security/config/CorsConfig.java | 3 - .../security/config/SecurityConfig.java | 75 ++++++++-------- .../security/controllers/AuthController.java | 88 ++++--------------- .../security/controllers/LoginController.java | 87 ++++++++++++++++++ .../security/filter/JwtTokenFilter.java | 4 +- .../JwtService.java} | 55 ++++++++---- src/main/resources/application.properties | 6 +- 8 files changed, 205 insertions(+), 137 deletions(-) create mode 100644 src/main/java/com/olympus/hermione/security/controllers/LoginController.java rename src/main/java/com/olympus/hermione/security/{utility/JwtTokenProvider.java => services/JwtService.java} (65%) diff --git a/pom.xml b/pom.xml index 7e56c32..1b7edcc 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,15 @@ jtokkit 1.1.0 + + com.azure.spring + azure-spring-boot-starter-active-directory + 4.0.0 + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + @@ -97,18 +106,21 @@ io.jsonwebtoken jjwt-api 0.11.5 - - + + + + io.jsonwebtoken jjwt-impl 0.11.5 - - + runtime + + io.jsonwebtoken jjwt-jackson 0.11.5 - - + runtime + org.springframework.boot spring-boot-starter-security diff --git a/src/main/java/com/olympus/hermione/security/config/CorsConfig.java b/src/main/java/com/olympus/hermione/security/config/CorsConfig.java index 0553354..ae801fb 100644 --- a/src/main/java/com/olympus/hermione/security/config/CorsConfig.java +++ b/src/main/java/com/olympus/hermione/security/config/CorsConfig.java @@ -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"); diff --git a/src/main/java/com/olympus/hermione/security/config/SecurityConfig.java b/src/main/java/com/olympus/hermione/security/config/SecurityConfig.java index d1fc2a7..fc11ac7 100644 --- a/src/main/java/com/olympus/hermione/security/config/SecurityConfig.java +++ b/src/main/java/com/olympus/hermione/security/config/SecurityConfig.java @@ -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(); } + } diff --git a/src/main/java/com/olympus/hermione/security/controllers/AuthController.java b/src/main/java/com/olympus/hermione/security/controllers/AuthController.java index 678cd7b..be223ce 100644 --- a/src/main/java/com/olympus/hermione/security/controllers/AuthController.java +++ b/src/main/java/com/olympus/hermione/security/controllers/AuthController.java @@ -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)); } } + + diff --git a/src/main/java/com/olympus/hermione/security/controllers/LoginController.java b/src/main/java/com/olympus/hermione/security/controllers/LoginController.java new file mode 100644 index 0000000..6e97791 --- /dev/null +++ b/src/main/java/com/olympus/hermione/security/controllers/LoginController.java @@ -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 "); + } +} + diff --git a/src/main/java/com/olympus/hermione/security/filter/JwtTokenFilter.java b/src/main/java/com/olympus/hermione/security/filter/JwtTokenFilter.java index dd6dd26..f76c0e4 100644 --- a/src/main/java/com/olympus/hermione/security/filter/JwtTokenFilter.java +++ b/src/main/java/com/olympus/hermione/security/filter/JwtTokenFilter.java @@ -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; diff --git a/src/main/java/com/olympus/hermione/security/utility/JwtTokenProvider.java b/src/main/java/com/olympus/hermione/security/services/JwtService.java similarity index 65% rename from src/main/java/com/olympus/hermione/security/utility/JwtTokenProvider.java rename to src/main/java/com/olympus/hermione/security/services/JwtService.java index 8bf7c90..ba3eb9c 100644 --- a/src/main/java/com/olympus/hermione/security/utility/JwtTokenProvider.java +++ b/src/main/java/com/olympus/hermione/security/services/JwtService.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9955db8..bd9be03 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -74,4 +74,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 \ No newline at end of file +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/9dc4721e-4d54-4c40-a681-1dd740292901/ \ No newline at end of file