From bb4662ad22e4c654afab58d46f08bea001371364 Mon Sep 17 00:00:00 2001 From: sumedh Date: Fri, 18 Apr 2025 17:19:25 +0530 Subject: [PATCH] Add JWT authentication support with login and token refresh endpoints --- pom.xml | 11 ++ .../security/config/SecurityConfig.java | 84 +++++++------ .../security/controllers/AuthController.java | 88 +++---------- .../security/controllers/LoginController.java | 82 +++++++++++++ .../security/filter/JwtTokenFilter.java | 5 +- .../apollo/security/services/JwtService.java | 116 ++++++++++++++++++ .../security/utility/JwtTokenProvider.java | 88 ------------- src/main/resources/application.yml | 5 + 8 files changed, 276 insertions(+), 203 deletions(-) create mode 100644 src/main/java/com/olympus/apollo/security/controllers/LoginController.java create mode 100644 src/main/java/com/olympus/apollo/security/services/JwtService.java delete mode 100644 src/main/java/com/olympus/apollo/security/utility/JwtTokenProvider.java diff --git a/pom.xml b/pom.xml index b793e50..930fa58 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,8 @@ 21 1.0.0-M6 2023.0.3 + 21 + 21 @@ -47,6 +49,15 @@ org.springframework.boot spring-boot-starter-data-mongodb + + com.azure.spring + azure-spring-boot-starter-active-directory + 4.0.0 + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/olympus/apollo/security/config/SecurityConfig.java b/src/main/java/com/olympus/apollo/security/config/SecurityConfig.java index 50e215f..fa22199 100644 --- a/src/main/java/com/olympus/apollo/security/config/SecurityConfig.java +++ b/src/main/java/com/olympus/apollo/security/config/SecurityConfig.java @@ -1,14 +1,15 @@ package com.olympus.apollo.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; @@ -24,18 +25,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() { @@ -47,38 +41,50 @@ public class SecurityConfig { return authProvider; } -//@Bean -//@Override -//public AuthenticationManager authenticationManagerBean() throws Exception { -// return super.authenticationManagerBean(); -//} + + + @Bean + PasswordEncoder passwordEncoder() { + return NoOpPasswordEncoder.getInstance(); + } @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { - return authConfig.getAuthenticationManager(); - } - - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - - @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("/**").permitAll() - .anyRequest().authenticated()); //permitAll()); - - http.authenticationProvider(authenticationProvider()); - - http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + 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(); } -} \ No newline at end of file + // 🔓 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 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/apollo/security/controllers/AuthController.java b/src/main/java/com/olympus/apollo/security/controllers/AuthController.java index 46cc661..f31ddce 100644 --- a/src/main/java/com/olympus/apollo/security/controllers/AuthController.java +++ b/src/main/java/com/olympus/apollo/security/controllers/AuthController.java @@ -1,90 +1,32 @@ package com.olympus.apollo.security.controllers; -import java.security.Principal; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; +import com.olympus.apollo.security.services.JwtService; 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.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; 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.apollo.security.dto.AuthenticationRequest; -import com.olympus.apollo.security.dto.AuthenticationResponse; -import com.olympus.apollo.security.dto.FetchUserResponse; -import com.olympus.apollo.security.entity.User; -import com.olympus.apollo.security.utility.JwtTokenProvider; +import java.util.Map; @RestController -@CrossOrigin -@RequestMapping("/api/auth") +@RequestMapping("/msauth") public class AuthController { - @Autowired - private AuthenticationManager authenticationManager; - @Autowired - private JwtTokenProvider 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.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); + private final JwtService jwtService; + 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/apollo/security/controllers/LoginController.java b/src/main/java/com/olympus/apollo/security/controllers/LoginController.java new file mode 100644 index 0000000..2d407ec --- /dev/null +++ b/src/main/java/com/olympus/apollo/security/controllers/LoginController.java @@ -0,0 +1,82 @@ +package com.olympus.apollo.security.controllers; + +import com.olympus.apollo.security.dto.AuthenticationRequest; +import com.olympus.apollo.security.dto.AuthenticationResponse; +import com.olympus.apollo.security.dto.FetchUserResponse; +import com.olympus.apollo.security.entity.User; +import com.olympus.apollo.security.services.JwtService; +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.*; + + +@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/apollo/security/filter/JwtTokenFilter.java b/src/main/java/com/olympus/apollo/security/filter/JwtTokenFilter.java index 208b1d4..878757a 100644 --- a/src/main/java/com/olympus/apollo/security/filter/JwtTokenFilter.java +++ b/src/main/java/com/olympus/apollo/security/filter/JwtTokenFilter.java @@ -2,6 +2,7 @@ package com.olympus.apollo.security.filter; import java.io.IOException; +import com.olympus.apollo.security.services.JwtService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; @@ -11,8 +12,6 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import com.olympus.apollo.security.utility.JwtTokenProvider; - import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -26,7 +25,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/apollo/security/services/JwtService.java b/src/main/java/com/olympus/apollo/security/services/JwtService.java new file mode 100644 index 0000000..c9c6cd2 --- /dev/null +++ b/src/main/java/com/olympus/apollo/security/services/JwtService.java @@ -0,0 +1,116 @@ +package com.olympus.apollo.security.services; + +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; + +@Service +@Slf4j +public class JwtService { + + private final long EXPIRATION_MS = 3600_000; // 1 hour + + + public static final String SECRET = "5367566B59703373367639792F423F4528482B4D6251655468576D5A713474375367566B59703373367639792F423F4528482B4D6251655468576D5A71347437"; + + + private Key getSignKey() { + byte[] keyBytes = Decoders.BASE64.decode(SECRET); + return Keys.hmacShaKeyFor(keyBytes); + } + + + public String generateInternalToken(Jwt azureJwt) { + Date now = new Date(); + Date expiry = new Date(now.getTime() + EXPIRATION_MS); + + String username=""; + String email = azureJwt.getClaim("email"); + String username_claim = azureJwt.getClaim("username"); + String unique_name = azureJwt.getClaim("unique_name"); + + if(username_claim == null){ + username = unique_name; + } + + if ( username.contains("@") ){ + username = username.split("@")[0]; + } + return Jwts.builder() + .setSubject(username) + .setIssuedAt(new Date()) + .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); + + 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 ")) { + return bearerToken.substring(7); + } + return null; + } + + // Check if the token is valid and not expired + public boolean validateToken(String token) { + + try { + Jwts.parser().setSigningKey(getSignKey()).parseClaimsJws(token); + return true; + } catch (MalformedJwtException ex) { + log.error("Invalid JWT token"); + } catch (ExpiredJwtException ex) { + log.error("Expired JWT token"); + } catch (UnsupportedJwtException ex) { + log.error("Unsupported JWT token"); + } catch (IllegalArgumentException ex) { + log.error("JWT claims string is empty"); + } catch (SignatureException e) { + log.error("there is an error with the signature of you token "); + } + return false; + } + + // Extract the username from the JWT token + public String getUsername(String token) { + + return Jwts.parser() + .setSigningKey(getSignKey()) + .parseClaimsJws(token) + .getBody() + .getSubject(); + } +} \ No newline at end of file diff --git a/src/main/java/com/olympus/apollo/security/utility/JwtTokenProvider.java b/src/main/java/com/olympus/apollo/security/utility/JwtTokenProvider.java deleted file mode 100644 index cb00594..0000000 --- a/src/main/java/com/olympus/apollo/security/utility/JwtTokenProvider.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.olympus.apollo.security.utility; - -import org.springframework.stereotype.Component; - -import io.jsonwebtoken.*; -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.util.StringUtils; -import java.security.Key; -import java.util.Date; - -@Component -@Slf4j -public class JwtTokenProvider { - - Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512); - - - public static final String SECRET = "5367566B59703373367639792F423F4528482B4D6251655468576D5A713474375367566B59703373367639792F423F4528482B4D6251655468576D5A71347437"; - - - private Key getSignKey() { - byte[] keyBytes = Decoders.BASE64.decode(SECRET); - return Keys.hmacShaKeyFor(keyBytes); - } - - - public String createToken(Authentication authentication) { - - UserDetails userDetails = (UserDetails) authentication.getPrincipal(); - Date now = new Date(); - Date expiryDate = new Date(now.getTime() + 3600000); - - return Jwts.builder() - .setSubject(userDetails.getUsername()) - .setIssuedAt(new Date()) - .setExpiration(expiryDate) - .signWith(getSignKey(), SignatureAlgorithm.HS512) - //.signWith(SignatureAlgorithm.HS512, SECRET) - .compact(); - } - - - public String resolveToken(HttpServletRequest request) { - - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } - - // Check if the token is valid and not expired - public boolean validateToken(String token) { - - try { - Jwts.parser().setSigningKey(getSignKey()).parseClaimsJws(token); - return true; - } catch (MalformedJwtException ex) { - log.error("Invalid JWT token"); - } catch (ExpiredJwtException ex) { - log.error("Expired JWT token"); - } catch (UnsupportedJwtException ex) { - log.error("Unsupported JWT token"); - } catch (IllegalArgumentException ex) { - log.error("JWT claims string is empty"); - } catch (SignatureException e) { - log.error("there is an error with the signature of you token "); - } - return false; - } - - // Extract the username from the JWT token - public String getUsername(String token) { - - return Jwts.parser() - .setSigningKey(getSignKey()) - .parseClaimsJws(token) - .getBody() - .getSubject(); - } -} - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 14bd3c8..b7b1e82 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,6 +13,11 @@ generic-file-parser-module: spring: application: name: apollo + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://sts.windows.net/e0793d39-0939-496d-b129-198edd916feb/ ai: azure: openai: