diff --git a/README.md b/README.md index e3e7f79..a65578f 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,4 @@ To build and run the project, follow these steps: * Run the project: mvn spring-boot:run -> The application will be available at http://localhost:8080. +END. diff --git a/pom.xml b/pom.xml index 9c14b26..4f45c65 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.4 + 3.3.2 com.alibou diff --git a/src/main/java/com/alibou/security/SecurityApplication.java b/src/main/java/com/alibou/security/SecurityApplication.java index 1448cff..282185d 100644 --- a/src/main/java/com/alibou/security/SecurityApplication.java +++ b/src/main/java/com/alibou/security/SecurityApplication.java @@ -2,7 +2,6 @@ import com.alibou.security.auth.AuthenticationService; import com.alibou.security.auth.RegisterRequest; -import com.alibou.security.user.Role; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -16,33 +15,30 @@ @EnableJpaAuditing(auditorAwareRef = "auditorAware") public class SecurityApplication { - public static void main(String[] args) { - SpringApplication.run(SecurityApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(SecurityApplication.class, args); + } - @Bean - public CommandLineRunner commandLineRunner( - AuthenticationService service - ) { - return args -> { - var admin = RegisterRequest.builder() - .firstname("Admin") - .lastname("Admin") - .email("admin@mail.com") - .password("password") - .role(ADMIN) - .build(); - System.out.println("Admin token: " + service.register(admin).getAccessToken()); + @Bean + public CommandLineRunner commandLineRunner(AuthenticationService service) { + return args -> { + var admin = RegisterRequest.builder() + .firstname("Admin") + .lastname("Admin") + .email("admin@mail.com") + .password("password") + .role(ADMIN) + .build(); + System.out.println("Admin token: " + service.register(admin).getAccessToken()); - var manager = RegisterRequest.builder() - .firstname("Admin") - .lastname("Admin") - .email("manager@mail.com") - .password("password") - .role(MANAGER) - .build(); - System.out.println("Manager token: " + service.register(manager).getAccessToken()); - - }; - } + var manager = RegisterRequest.builder() + .firstname("Manager") + .lastname("Manager") + .email("manager@mail.com") + .password("password") + .role(MANAGER) + .build(); + System.out.println("Manager token: " + service.register(manager).getAccessToken()); + }; + } } diff --git a/src/main/java/com/alibou/security/auditing/ApplicationAuditAware.java b/src/main/java/com/alibou/security/auditing/ApplicationAuditAware.java index 3f8172f..5a5a179 100644 --- a/src/main/java/com/alibou/security/auditing/ApplicationAuditAware.java +++ b/src/main/java/com/alibou/security/auditing/ApplicationAuditAware.java @@ -9,6 +9,7 @@ import java.util.Optional; public class ApplicationAuditAware implements AuditorAware { + @Override public Optional getCurrentAuditor() { Authentication authentication = @@ -16,7 +17,7 @@ public Optional getCurrentAuditor() { .getContext() .getAuthentication(); if (authentication == null || - !authentication.isAuthenticated() || + !authentication.isAuthenticated() || authentication instanceof AnonymousAuthenticationToken ) { return Optional.empty(); diff --git a/src/main/java/com/alibou/security/auth/AuthenticationController.java b/src/main/java/com/alibou/security/auth/AuthenticationController.java index e1d5107..9594bf7 100644 --- a/src/main/java/com/alibou/security/auth/AuthenticationController.java +++ b/src/main/java/com/alibou/security/auth/AuthenticationController.java @@ -16,28 +16,20 @@ @RequiredArgsConstructor public class AuthenticationController { - private final AuthenticationService service; - - @PostMapping("/register") - public ResponseEntity register( - @RequestBody RegisterRequest request - ) { - return ResponseEntity.ok(service.register(request)); - } - @PostMapping("/authenticate") - public ResponseEntity authenticate( - @RequestBody AuthenticationRequest request - ) { - return ResponseEntity.ok(service.authenticate(request)); - } - - @PostMapping("/refresh-token") - public void refreshToken( - HttpServletRequest request, - HttpServletResponse response - ) throws IOException { - service.refreshToken(request, response); - } - - + private final AuthenticationService service; + + @PostMapping("/register") + public ResponseEntity register(@RequestBody RegisterRequest request) { + return ResponseEntity.ok(service.register(request)); + } + + @PostMapping("/authenticate") + public ResponseEntity authenticate(@RequestBody AuthenticationRequest request) { + return ResponseEntity.ok(service.authenticate(request)); + } + + @PostMapping("/refresh-token") + public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException { + service.refreshToken(request, response); + } } diff --git a/src/main/java/com/alibou/security/auth/AuthenticationRequest.java b/src/main/java/com/alibou/security/auth/AuthenticationRequest.java index 6d72722..8297f1e 100644 --- a/src/main/java/com/alibou/security/auth/AuthenticationRequest.java +++ b/src/main/java/com/alibou/security/auth/AuthenticationRequest.java @@ -11,6 +11,6 @@ @NoArgsConstructor public class AuthenticationRequest { - private String email; - String password; + private String email; + String password; } diff --git a/src/main/java/com/alibou/security/auth/AuthenticationResponse.java b/src/main/java/com/alibou/security/auth/AuthenticationResponse.java index c10bbb6..a0964bc 100644 --- a/src/main/java/com/alibou/security/auth/AuthenticationResponse.java +++ b/src/main/java/com/alibou/security/auth/AuthenticationResponse.java @@ -12,8 +12,8 @@ @NoArgsConstructor public class AuthenticationResponse { - @JsonProperty("access_token") - private String accessToken; - @JsonProperty("refresh_token") - private String refreshToken; + @JsonProperty("access_token") + private String accessToken; + @JsonProperty("refresh_token") + private String refreshToken; } diff --git a/src/main/java/com/alibou/security/auth/AuthenticationService.java b/src/main/java/com/alibou/security/auth/AuthenticationService.java index 53193a7..a70e83c 100644 --- a/src/main/java/com/alibou/security/auth/AuthenticationService.java +++ b/src/main/java/com/alibou/security/auth/AuthenticationService.java @@ -25,96 +25,94 @@ @Service @RequiredArgsConstructor public class AuthenticationService { - private final UserRepository repository; - private final TokenRepository tokenRepository; - private final PasswordEncoder passwordEncoder; - private final JwtService jwtService; - private final AuthenticationManager authenticationManager; - public AuthenticationResponse register(RegisterRequest request) { - var user = User.builder() - .firstname(request.getFirstname()) - .lastname(request.getLastname()) - .email(request.getEmail()) - .password(passwordEncoder.encode(request.getPassword())) - .role(request.getRole()) - .build(); - var savedUser = repository.save(user); - var jwtToken = jwtService.generateToken(user); - var refreshToken = jwtService.generateRefreshToken(user); - saveUserToken(savedUser, jwtToken); - return AuthenticationResponse.builder() - .accessToken(jwtToken) - .refreshToken(refreshToken) - .build(); - } + private final UserRepository repository; + private final TokenRepository tokenRepository; + private final PasswordEncoder passwordEncoder; + private final JwtService jwtService; + private final AuthenticationManager authenticationManager; - public AuthenticationResponse authenticate(AuthenticationRequest request) { - authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken( - request.getEmail(), - request.getPassword() - ) - ); - var user = repository.findByEmail(request.getEmail()) - .orElseThrow(); - var jwtToken = jwtService.generateToken(user); - var refreshToken = jwtService.generateRefreshToken(user); - revokeAllUserTokens(user); - saveUserToken(user, jwtToken); - return AuthenticationResponse.builder() - .accessToken(jwtToken) - .refreshToken(refreshToken) - .build(); - } - - private void saveUserToken(User user, String jwtToken) { - var token = Token.builder() - .user(user) - .token(jwtToken) - .tokenType(TokenType.BEARER) - .expired(false) - .revoked(false) - .build(); - tokenRepository.save(token); - } - - private void revokeAllUserTokens(User user) { - var validUserTokens = tokenRepository.findAllValidTokenByUser(user.getId()); - if (validUserTokens.isEmpty()) - return; - validUserTokens.forEach(token -> { - token.setExpired(true); - token.setRevoked(true); - }); - tokenRepository.saveAll(validUserTokens); - } - - public void refreshToken( - HttpServletRequest request, - HttpServletResponse response - ) throws IOException { - final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); - final String refreshToken; - final String userEmail; - if (authHeader == null ||!authHeader.startsWith("Bearer ")) { - return; + public AuthenticationResponse register(RegisterRequest request) { + var user = User.builder() + .firstname(request.getFirstname()) + .lastname(request.getLastname()) + .email(request.getEmail()) + .password(passwordEncoder.encode(request.getPassword())) + .role(request.getRole()) + .build(); + var savedUser = repository.save(user); + var jwtToken = jwtService.generateToken(user); + var refreshToken = jwtService.generateRefreshToken(user); + saveUserToken(savedUser, jwtToken); + return AuthenticationResponse.builder() + .accessToken(jwtToken) + .refreshToken(refreshToken) + .build(); } - refreshToken = authHeader.substring(7); - userEmail = jwtService.extractUsername(refreshToken); - if (userEmail != null) { - var user = this.repository.findByEmail(userEmail) - .orElseThrow(); - if (jwtService.isTokenValid(refreshToken, user)) { - var accessToken = jwtService.generateToken(user); + + public AuthenticationResponse authenticate(AuthenticationRequest request) { + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + request.getEmail(), + request.getPassword() + ) + ); + var user = repository.findByEmail(request.getEmail()) + .orElseThrow(); + var jwtToken = jwtService.generateToken(user); + var refreshToken = jwtService.generateRefreshToken(user); revokeAllUserTokens(user); - saveUserToken(user, accessToken); - var authResponse = AuthenticationResponse.builder() - .accessToken(accessToken) + saveUserToken(user, jwtToken); + return AuthenticationResponse.builder() + .accessToken(jwtToken) .refreshToken(refreshToken) .build(); - new ObjectMapper().writeValue(response.getOutputStream(), authResponse); - } } - } + + private void saveUserToken(User user, String jwtToken) { + var token = Token.builder() + .user(user) + .token(jwtToken) + .tokenType(TokenType.BEARER) + .expired(false) + .revoked(false) + .build(); + tokenRepository.save(token); + } + + private void revokeAllUserTokens(User user) { + var validUserTokens = tokenRepository.findAllValidTokenByUser(user.getId()); + if (validUserTokens.isEmpty()) + return; + validUserTokens.forEach(token -> { + token.setExpired(true); + token.setRevoked(true); + }); + tokenRepository.saveAll(validUserTokens); + } + + public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException { + final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + final String refreshToken; + final String userEmail; + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return; + } + refreshToken = authHeader.substring(7); + userEmail = jwtService.extractUsername(refreshToken); + if (userEmail != null) { + var user = this.repository.findByEmail(userEmail) + .orElseThrow(); + if (jwtService.isTokenValid(refreshToken, user)) { + var accessToken = jwtService.generateToken(user); + revokeAllUserTokens(user); + saveUserToken(user, accessToken); + var authResponse = AuthenticationResponse.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + new ObjectMapper().writeValue(response.getOutputStream(), authResponse); + } + } + } } diff --git a/src/main/java/com/alibou/security/auth/RegisterRequest.java b/src/main/java/com/alibou/security/auth/RegisterRequest.java index 4f51665..c8d0e97 100644 --- a/src/main/java/com/alibou/security/auth/RegisterRequest.java +++ b/src/main/java/com/alibou/security/auth/RegisterRequest.java @@ -12,9 +12,9 @@ @NoArgsConstructor public class RegisterRequest { - private String firstname; - private String lastname; - private String email; - private String password; - private Role role; + private String firstname; + private String lastname; + private String email; + private String password; + private Role role; } diff --git a/src/main/java/com/alibou/security/book/Book.java b/src/main/java/com/alibou/security/book/Book.java index 3f041af..809e72e 100644 --- a/src/main/java/com/alibou/security/book/Book.java +++ b/src/main/java/com/alibou/security/book/Book.java @@ -32,22 +32,15 @@ public class Book { private String isbn; @CreatedDate - @Column( - nullable = false, - updatable = false - ) + @Column(nullable = false, updatable = false) private LocalDateTime createDate; @LastModifiedDate @Column(insertable = false) private LocalDateTime lastModified; - @CreatedBy - @Column( - nullable = false, - updatable = false - ) + @Column(nullable = false, updatable = false) private Integer createdBy; @LastModifiedBy diff --git a/src/main/java/com/alibou/security/book/BookController.java b/src/main/java/com/alibou/security/book/BookController.java index 4c45728..c878424 100644 --- a/src/main/java/com/alibou/security/book/BookController.java +++ b/src/main/java/com/alibou/security/book/BookController.java @@ -18,9 +18,7 @@ public class BookController { private final BookService service; @PostMapping - public ResponseEntity save( - @RequestBody BookRequest request - ) { + public ResponseEntity save(@RequestBody BookRequest request) { service.save(request); return ResponseEntity.accepted().build(); } diff --git a/src/main/java/com/alibou/security/config/ApplicationConfig.java b/src/main/java/com/alibou/security/config/ApplicationConfig.java index ae71abf..423e1ad 100644 --- a/src/main/java/com/alibou/security/config/ApplicationConfig.java +++ b/src/main/java/com/alibou/security/config/ApplicationConfig.java @@ -20,35 +20,34 @@ @RequiredArgsConstructor public class ApplicationConfig { - private final UserRepository repository; - - @Bean - public UserDetailsService userDetailsService() { - return username -> repository.findByEmail(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); - } - - @Bean - public AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); - authProvider.setUserDetailsService(userDetailsService()); - authProvider.setPasswordEncoder(passwordEncoder()); - return authProvider; - } - - @Bean - public AuditorAware auditorAware() { - return new ApplicationAuditAware(); - } - - @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { - return config.getAuthenticationManager(); - } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - + private final UserRepository repository; + + @Bean + public UserDetailsService userDetailsService() { + return username -> repository.findByEmail(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + } + +// @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService()); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public AuditorAware auditorAware() { + return new ApplicationAuditAware(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } diff --git a/src/main/java/com/alibou/security/config/JwtAuthenticationFilter.java b/src/main/java/com/alibou/security/config/JwtAuthenticationFilter.java index d6e55d1..ac1800c 100644 --- a/src/main/java/com/alibou/security/config/JwtAuthenticationFilter.java +++ b/src/main/java/com/alibou/security/config/JwtAuthenticationFilter.java @@ -26,46 +26,44 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtService jwtService; - private final UserDetailsService userDetailsService; - private final TokenRepository tokenRepository; + private final JwtService jwtService; + private final UserDetailsService userDetailsService; + private final TokenRepository tokenRepository; - @Override - protected void doFilterInternal( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain - ) throws ServletException, IOException { - if (request.getServletPath().contains("/api/v1/auth")) { - filterChain.doFilter(request, response); - return; + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + if (request.getServletPath().contains("/api/v1/auth")) { + filterChain.doFilter(request, response); + return; + } + final String authHeader = request.getHeader("Authorization"); + final String jwt; + final String userEmail; + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + jwt = authHeader.substring(7); + userEmail = jwtService.extractUsername(jwt); + if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); + var isTokenValid = tokenRepository.findByToken(jwt) + .map(t -> !t.isExpired() && !t.isRevoked()) + .orElse(false); + if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + filterChain.doFilter(request, response); } - final String authHeader = request.getHeader("Authorization"); - final String jwt; - final String userEmail; - if (authHeader == null ||!authHeader.startsWith("Bearer ")) { - filterChain.doFilter(request, response); - return; - } - jwt = authHeader.substring(7); - userEmail = jwtService.extractUsername(jwt); - if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); - var isTokenValid = tokenRepository.findByToken(jwt) - .map(t -> !t.isExpired() && !t.isRevoked()) - .orElse(false); - if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) { - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - userDetails, - null, - userDetails.getAuthorities() - ); - authToken.setDetails( - new WebAuthenticationDetailsSource().buildDetails(request) - ); - SecurityContextHolder.getContext().setAuthentication(authToken); - } - } - filterChain.doFilter(request, response); - } } diff --git a/src/main/java/com/alibou/security/config/JwtService.java b/src/main/java/com/alibou/security/config/JwtService.java index 9c1ed46..457a184 100644 --- a/src/main/java/com/alibou/security/config/JwtService.java +++ b/src/main/java/com/alibou/security/config/JwtService.java @@ -5,6 +5,7 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; + import java.security.Key; import java.util.Date; import java.util.HashMap; @@ -18,78 +19,71 @@ @Service public class JwtService { - @Value("${application.security.jwt.secret-key}") - private String secretKey; - @Value("${application.security.jwt.expiration}") - private long jwtExpiration; - @Value("${application.security.jwt.refresh-token.expiration}") - private long refreshExpiration; - - public String extractUsername(String token) { - return extractClaim(token, Claims::getSubject); - } - - public T extractClaim(String token, Function claimsResolver) { - final Claims claims = extractAllClaims(token); - return claimsResolver.apply(claims); - } - - public String generateToken(UserDetails userDetails) { - return generateToken(new HashMap<>(), userDetails); - } - - public String generateToken( - Map extraClaims, - UserDetails userDetails - ) { - return buildToken(extraClaims, userDetails, jwtExpiration); - } - - public String generateRefreshToken( - UserDetails userDetails - ) { - return buildToken(new HashMap<>(), userDetails, refreshExpiration); - } - - private String buildToken( - Map extraClaims, - UserDetails userDetails, - long expiration - ) { - return Jwts - .builder() - .setClaims(extraClaims) - .setSubject(userDetails.getUsername()) - .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + expiration)) - .signWith(getSignInKey(), SignatureAlgorithm.HS256) - .compact(); - } - - public boolean isTokenValid(String token, UserDetails userDetails) { - final String username = extractUsername(token); - return (username.equals(userDetails.getUsername())) && !isTokenExpired(token); - } - - private boolean isTokenExpired(String token) { - return extractExpiration(token).before(new Date()); - } - - private Date extractExpiration(String token) { - return extractClaim(token, Claims::getExpiration); - } - - private Claims extractAllClaims(String token) { - return Jwts - .parserBuilder() - .setSigningKey(getSignInKey()) - .build() - .parseClaimsJws(token) - .getBody(); - } - - private Key getSignInKey() { - byte[] keyBytes = Decoders.BASE64.decode(secretKey); - return Keys.hmacShaKeyFor(keyBytes); - } + @Value("${application.security.jwt.secret-key}") + private String secretKey; + + @Value("${application.security.jwt.expiration}") + private long jwtExpiration; + + @Value("${application.security.jwt.refresh-token.expiration}") + private long refreshExpiration; + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + public String generateToken(UserDetails userDetails) { + return generateToken(new HashMap<>(), userDetails); + } + + public String generateToken(Map extraClaims, UserDetails userDetails) { + return buildToken(extraClaims, userDetails, jwtExpiration); + } + + public String generateRefreshToken(UserDetails userDetails) { + return buildToken(new HashMap<>(), userDetails, refreshExpiration); + } + + private String buildToken(Map extraClaims, UserDetails userDetails, long expiration) { + return Jwts + .builder() + .setClaims(extraClaims) + .setSubject(userDetails.getUsername()) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public boolean isTokenValid(String token, UserDetails userDetails) { + final String username = extractUsername(token); + return (username.equals(userDetails.getUsername())) && !isTokenExpired(token); + } + + private boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + private Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + private Claims extractAllClaims(String token) { + return Jwts + .parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } + + private Key getSigningKey() { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + return Keys.hmacShaKeyFor(keyBytes); + } } diff --git a/src/main/java/com/alibou/security/config/LogoutService.java b/src/main/java/com/alibou/security/config/LogoutService.java index 0784565..ca7462c 100644 --- a/src/main/java/com/alibou/security/config/LogoutService.java +++ b/src/main/java/com/alibou/security/config/LogoutService.java @@ -13,27 +13,23 @@ @RequiredArgsConstructor public class LogoutService implements LogoutHandler { - private final TokenRepository tokenRepository; + private final TokenRepository tokenRepository; - @Override - public void logout( - HttpServletRequest request, - HttpServletResponse response, - Authentication authentication - ) { - final String authHeader = request.getHeader("Authorization"); - final String jwt; - if (authHeader == null ||!authHeader.startsWith("Bearer ")) { - return; + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + final String authHeader = request.getHeader("Authorization"); + final String jwt; + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return; + } + jwt = authHeader.substring(7); + var storedToken = tokenRepository.findByToken(jwt) + .orElse(null); + if (storedToken != null) { + storedToken.setExpired(true); + storedToken.setRevoked(true); + tokenRepository.save(storedToken); + SecurityContextHolder.clearContext(); + } } - jwt = authHeader.substring(7); - var storedToken = tokenRepository.findByToken(jwt) - .orElse(null); - if (storedToken != null) { - storedToken.setExpired(true); - storedToken.setRevoked(true); - tokenRepository.save(storedToken); - SecurityContextHolder.clearContext(); - } - } } diff --git a/src/main/java/com/alibou/security/config/SecurityConfiguration.java b/src/main/java/com/alibou/security/config/SecurityConfiguration.java index e4aefe6..f0a1268 100644 --- a/src/main/java/com/alibou/security/config/SecurityConfiguration.java +++ b/src/main/java/com/alibou/security/config/SecurityConfiguration.java @@ -35,46 +35,33 @@ @EnableMethodSecurity public class SecurityConfiguration { - private static final String[] WHITE_LIST_URL = {"/api/v1/auth/**", - "/v2/api-docs", - "/v3/api-docs", - "/v3/api-docs/**", - "/swagger-resources", - "/swagger-resources/**", - "/configuration/ui", - "/configuration/security", - "/swagger-ui/**", - "/webjars/**", - "/swagger-ui.html"}; + private static final String[] WHITE_LIST_URL = { + "/api/v1/auth/**", + }; + private final JwtAuthenticationFilter jwtAuthFilter; - private final AuthenticationProvider authenticationProvider; private final LogoutHandler logoutHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http + return http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(req -> - req.requestMatchers(WHITE_LIST_URL) - .permitAll() + req.requestMatchers(WHITE_LIST_URL).permitAll() .requestMatchers("/api/v1/management/**").hasAnyRole(ADMIN.name(), MANAGER.name()) .requestMatchers(GET, "/api/v1/management/**").hasAnyAuthority(ADMIN_READ.name(), MANAGER_READ.name()) .requestMatchers(POST, "/api/v1/management/**").hasAnyAuthority(ADMIN_CREATE.name(), MANAGER_CREATE.name()) .requestMatchers(PUT, "/api/v1/management/**").hasAnyAuthority(ADMIN_UPDATE.name(), MANAGER_UPDATE.name()) .requestMatchers(DELETE, "/api/v1/management/**").hasAnyAuthority(ADMIN_DELETE.name(), MANAGER_DELETE.name()) - .anyRequest() - .authenticated() + .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(STATELESS)) - .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .logout(logout -> logout.logoutUrl("/api/v1/auth/logout") .addLogoutHandler(logoutHandler) .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext()) ) - ; - - return http.build(); + .build(); } } diff --git a/src/main/java/com/alibou/security/demo/AdminController.java b/src/main/java/com/alibou/security/demo/AdminController.java index 18ede65..f6aa3bf 100644 --- a/src/main/java/com/alibou/security/demo/AdminController.java +++ b/src/main/java/com/alibou/security/demo/AdminController.java @@ -19,18 +19,21 @@ public class AdminController { public String get() { return "GET:: admin controller"; } + @PostMapping @PreAuthorize("hasAuthority('admin:create')") @Hidden public String post() { return "POST:: admin controller"; } + @PutMapping @PreAuthorize("hasAuthority('admin:update')") @Hidden public String put() { return "PUT:: admin controller"; } + @DeleteMapping @PreAuthorize("hasAuthority('admin:delete')") @Hidden diff --git a/src/main/java/com/alibou/security/demo/DemoController.java b/src/main/java/com/alibou/security/demo/DemoController.java index ee2c380..84ac9d4 100644 --- a/src/main/java/com/alibou/security/demo/DemoController.java +++ b/src/main/java/com/alibou/security/demo/DemoController.java @@ -11,9 +11,8 @@ @Hidden public class DemoController { - @GetMapping - public ResponseEntity sayHello() { - return ResponseEntity.ok("Hello from secured endpoint"); - } - + @GetMapping + public ResponseEntity sayHello() { + return ResponseEntity.ok("Hello from secured endpoint"); + } } diff --git a/src/main/java/com/alibou/security/demo/ManagementController.java b/src/main/java/com/alibou/security/demo/ManagementController.java index a214a9b..de9b613 100644 --- a/src/main/java/com/alibou/security/demo/ManagementController.java +++ b/src/main/java/com/alibou/security/demo/ManagementController.java @@ -29,20 +29,22 @@ public class ManagementController { responseCode = "403" ) } - ) @GetMapping public String get() { return "GET:: management controller"; } + @PostMapping public String post() { return "POST:: management controller"; } + @PutMapping public String put() { return "PUT:: management controller"; } + @DeleteMapping public String delete() { return "DELETE:: management controller"; diff --git a/src/main/java/com/alibou/security/token/Token.java b/src/main/java/com/alibou/security/token/Token.java index 71f3571..31896df 100644 --- a/src/main/java/com/alibou/security/token/Token.java +++ b/src/main/java/com/alibou/security/token/Token.java @@ -22,21 +22,21 @@ @Entity public class Token { - @Id - @GeneratedValue - public Integer id; + @Id + @GeneratedValue + public Integer id; - @Column(unique = true) - public String token; + @Column(unique = true) + public String token; - @Enumerated(EnumType.STRING) - public TokenType tokenType = TokenType.BEARER; + @Enumerated(EnumType.STRING) + public TokenType tokenType = TokenType.BEARER; - public boolean revoked; + public boolean revoked; - public boolean expired; + public boolean expired; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - public User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + public User user; } diff --git a/src/main/java/com/alibou/security/token/TokenRepository.java b/src/main/java/com/alibou/security/token/TokenRepository.java index 48235d8..b5e91ec 100644 --- a/src/main/java/com/alibou/security/token/TokenRepository.java +++ b/src/main/java/com/alibou/security/token/TokenRepository.java @@ -2,17 +2,18 @@ import java.util.List; import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; public interface TokenRepository extends JpaRepository { - @Query(value = """ - select t from Token t inner join User u\s - on t.user.id = u.id\s - where u.id = :id and (t.expired = false or t.revoked = false)\s - """) - List findAllValidTokenByUser(Integer id); + @Query(value = """ + select t from Token t inner join User u\s + on t.user.id = u.id\s + where u.id = :id and (t.expired = false or t.revoked = false) + """) + List findAllValidTokenByUser(Integer id); - Optional findByToken(String token); + Optional findByToken(String token); } diff --git a/src/main/java/com/alibou/security/token/TokenType.java b/src/main/java/com/alibou/security/token/TokenType.java index 82a8cff..3513703 100644 --- a/src/main/java/com/alibou/security/token/TokenType.java +++ b/src/main/java/com/alibou/security/token/TokenType.java @@ -1,5 +1,5 @@ package com.alibou.security.token; public enum TokenType { - BEARER + BEARER } diff --git a/src/main/java/com/alibou/security/user/Permission.java b/src/main/java/com/alibou/security/user/Permission.java index 16ae8b4..230a6c1 100644 --- a/src/main/java/com/alibou/security/user/Permission.java +++ b/src/main/java/com/alibou/security/user/Permission.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +@Getter @RequiredArgsConstructor public enum Permission { @@ -13,10 +14,7 @@ public enum Permission { MANAGER_READ("management:read"), MANAGER_UPDATE("management:update"), MANAGER_CREATE("management:create"), - MANAGER_DELETE("management:delete") + MANAGER_DELETE("management:delete"); - ; - - @Getter private final String permission; } diff --git a/src/main/java/com/alibou/security/user/Role.java b/src/main/java/com/alibou/security/user/Role.java index 0ff9bd1..4c6ae21 100644 --- a/src/main/java/com/alibou/security/user/Role.java +++ b/src/main/java/com/alibou/security/user/Role.java @@ -18,42 +18,40 @@ import static com.alibou.security.user.Permission.MANAGER_READ; import static com.alibou.security.user.Permission.MANAGER_UPDATE; +@Getter @RequiredArgsConstructor public enum Role { - USER(Collections.emptySet()), - ADMIN( - Set.of( - ADMIN_READ, - ADMIN_UPDATE, - ADMIN_DELETE, - ADMIN_CREATE, - MANAGER_READ, - MANAGER_UPDATE, - MANAGER_DELETE, - MANAGER_CREATE - ) - ), - MANAGER( - Set.of( - MANAGER_READ, - MANAGER_UPDATE, - MANAGER_DELETE, - MANAGER_CREATE - ) - ) - - ; - - @Getter - private final Set permissions; - - public List getAuthorities() { - var authorities = getPermissions() - .stream() - .map(permission -> new SimpleGrantedAuthority(permission.getPermission())) - .collect(Collectors.toList()); - authorities.add(new SimpleGrantedAuthority("ROLE_" + this.name())); - return authorities; - } + USER(Collections.emptySet()), + ADMIN( + Set.of( + ADMIN_READ, + ADMIN_UPDATE, + ADMIN_DELETE, + ADMIN_CREATE, + MANAGER_READ, + MANAGER_UPDATE, + MANAGER_DELETE, + MANAGER_CREATE + ) + ), + MANAGER( + Set.of( + MANAGER_READ, + MANAGER_UPDATE, + MANAGER_DELETE, + MANAGER_CREATE + ) + ); + + private final Set permissions; + + public List getAuthorities() { + var authorities = getPermissions() + .stream() + .map(permission -> new SimpleGrantedAuthority(permission.getPermission())) + .collect(Collectors.toList()); + authorities.add(new SimpleGrantedAuthority("ROLE_" + this.name())); + return authorities; + } } diff --git a/src/main/java/com/alibou/security/user/User.java b/src/main/java/com/alibou/security/user/User.java index bc4e086..2dc068b 100644 --- a/src/main/java/com/alibou/security/user/User.java +++ b/src/main/java/com/alibou/security/user/User.java @@ -8,8 +8,10 @@ import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; + import java.util.Collection; import java.util.List; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -26,52 +28,32 @@ @Table(name = "_user") public class User implements UserDetails { - @Id - @GeneratedValue - private Integer id; - private String firstname; - private String lastname; - private String email; - private String password; - - @Enumerated(EnumType.STRING) - private Role role; - - @OneToMany(mappedBy = "user") - private List tokens; - - @Override - public Collection getAuthorities() { - return role.getAuthorities(); - } - - @Override - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return email; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } + @Id + @GeneratedValue + private Integer id; + private String firstname; + private String lastname; + private String email; + private String password; + + @Enumerated(EnumType.STRING) + private Role role; + + @OneToMany(mappedBy = "user") + private List tokens; + + @Override + public Collection getAuthorities() { + return role.getAuthorities(); + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return email; + } } diff --git a/src/main/java/com/alibou/security/user/UserController.java b/src/main/java/com/alibou/security/user/UserController.java index 415be48..0484154 100644 --- a/src/main/java/com/alibou/security/user/UserController.java +++ b/src/main/java/com/alibou/security/user/UserController.java @@ -17,10 +17,7 @@ public class UserController { private final UserService service; @PatchMapping - public ResponseEntity changePassword( - @RequestBody ChangePasswordRequest request, - Principal connectedUser - ) { + public ResponseEntity changePassword(@RequestBody ChangePasswordRequest request, Principal connectedUser) { service.changePassword(request, connectedUser); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/alibou/security/user/UserRepository.java b/src/main/java/com/alibou/security/user/UserRepository.java index a979ad6..7868c30 100644 --- a/src/main/java/com/alibou/security/user/UserRepository.java +++ b/src/main/java/com/alibou/security/user/UserRepository.java @@ -1,10 +1,10 @@ package com.alibou.security.user; import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); - + Optional findByEmail(String email); } diff --git a/src/main/java/com/alibou/security/user/UserService.java b/src/main/java/com/alibou/security/user/UserService.java index a17181d..84d4876 100644 --- a/src/main/java/com/alibou/security/user/UserService.java +++ b/src/main/java/com/alibou/security/user/UserService.java @@ -13,6 +13,7 @@ public class UserService { private final PasswordEncoder passwordEncoder; private final UserRepository repository; + public void changePassword(ChangePasswordRequest request, Principal connectedUser) { var user = (User) ((UsernamePasswordAuthenticationToken) connectedUser).getPrincipal(); @@ -21,6 +22,7 @@ public void changePassword(ChangePasswordRequest request, Principal connectedUse if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) { throw new IllegalStateException("Wrong password"); } + // check if the two new passwords are the same if (!request.getNewPassword().equals(request.getConfirmationPassword())) { throw new IllegalStateException("Password are not the same"); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 71b71d1..d65e97b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,8 +1,8 @@ spring: datasource: url: jdbc:postgresql://localhost:5432/jwt_security - username: username - password: password + username: postgres + password: postgres driver-class-name: org.postgresql.Driver jpa: hibernate: diff --git a/src/test/java/com/alibou/security/SecurityApplicationTests.java b/src/test/java/com/alibou/security/SecurityApplicationTests.java index 6e2729f..570c8bc 100644 --- a/src/test/java/com/alibou/security/SecurityApplicationTests.java +++ b/src/test/java/com/alibou/security/SecurityApplicationTests.java @@ -6,8 +6,8 @@ @SpringBootTest class SecurityApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } }