-
Notifications
You must be signed in to change notification settings - Fork 0
Add JWT authentication, RBAC and signup functionality #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
704517b
feat: implement JWT authentication and security configuration
addee1 00d3546
feat: add authentication endpoints (login and signup)
addee1 71e4479
feat: add role-based access control with @PreAuthorize
addee1 0686af0
feat: add SecurityUtils for accessing authenticated user
addee1 65f00fb
chore: adds jsonwebtoken dependency
addee1 d9a45ec
chore: document JWT secret usage for development
addee1 cdfae3b
fix: remove insecure hash endpoint
addee1 6194a7d
fix: improve authentication security and error handling
addee1 374de87
fix: remove sensitive logging and handle invalid JWT safely
addee1 117ae6a
chore: document development-only security configuration
addee1 ebad5bb
fix: add null check for authentication in SecurityUtils
addee1 555bd37
updates javaDoc text
addee1 d7e3c25
removed unused imports
addee1 138c1c0
fix javadoc comment
addee1 78ceb89
fix javadoc comment
addee1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package org.example.alfs.config; | ||
|
|
||
| import org.example.alfs.security.JwtAuthenticationFilter; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| import org.springframework.security.config.http.SessionCreationPolicy; | ||
| import org.springframework.security.web.SecurityFilterChain; | ||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
|
||
| @Configuration | ||
| @EnableMethodSecurity | ||
| public class SecurityConfig { | ||
| private final JwtAuthenticationFilter jwtFilter; | ||
|
|
||
| public SecurityConfig(JwtAuthenticationFilter jwtFilter) {this.jwtFilter = jwtFilter;} | ||
|
|
||
| @Bean | ||
| public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
|
|
||
| http | ||
| // Only for development (H2 console support) | ||
| .securityMatcher("/**") | ||
|
|
||
| .csrf(csrf -> csrf.disable()) | ||
|
|
||
| .headers(headers -> headers.frameOptions(frame -> frame.disable())) | ||
|
|
||
| // stateless jwt | ||
| .sessionManagement(session -> | ||
| session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) | ||
| ) | ||
|
|
||
|
|
||
| // Authorization strategy: | ||
| // - JWT is used for authentication (identifying the user) | ||
| // - User roles are NOT trusted from the JWT | ||
| // - Roles are always loaded from the database | ||
| // This ensures that permission changes take effect immediately | ||
| .authorizeHttpRequests(auth -> auth | ||
| .requestMatchers("/auth/login").permitAll() | ||
| .requestMatchers("/auth/signup").permitAll() | ||
| .requestMatchers("/auth/hash").permitAll() | ||
| .requestMatchers("/h2-console/**").permitAll() | ||
| .anyRequest().authenticated() | ||
| ) | ||
|
|
||
| // disable DEFAULT LOGIN | ||
| .formLogin(form -> form.disable()) | ||
| .httpBasic(basic -> basic.disable()) | ||
|
|
||
|
|
||
| .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); | ||
|
|
||
| return http.build(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
src/main/java/org/example/alfs/controllers/TestController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package org.example.alfs.controllers; | ||
|
|
||
| import org.springframework.security.access.prepost.PreAuthorize; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| // This controller demonstrates role-based access control using @PreAuthorize. | ||
| // Use this as a reference when implementing real endpoints. | ||
| @RestController | ||
| public class TestController { | ||
|
|
||
| @PreAuthorize("hasAnyRole('ADMIN','INVESTIGATOR','REPORTER')") | ||
| @GetMapping("/api/test/all-roles") | ||
| public String allRoles() { | ||
| return "all roles allowed"; | ||
| } | ||
|
|
||
| @PreAuthorize("hasRole('REPORTER')") | ||
| @GetMapping("/api/test/create-ticket") | ||
| public String createTicket() { | ||
| return "reporter can create ticket"; | ||
| } | ||
|
|
||
|
|
||
| @PreAuthorize("hasRole('ADMIN')") | ||
| @GetMapping("/api/test/assign-ticket") | ||
| public String assignTicket() { | ||
| return "admin can assign ticket"; | ||
| } | ||
|
|
||
| @PreAuthorize("hasRole('INVESTIGATOR')") | ||
| @GetMapping("/api/test/update-status") | ||
| public String updateStatus() { | ||
| return "investigator can update status"; | ||
| } | ||
|
|
||
| // need to be signed in - all roles | ||
| @GetMapping("/api/hello") | ||
| public String hello() { | ||
| return "hello secured"; | ||
| } | ||
|
|
||
| // Only ADMIN | ||
| @PreAuthorize("hasRole('ADMIN')") | ||
| @GetMapping("/api/admin/test") | ||
| public String adminOnly() { | ||
| return "only admin"; | ||
| } | ||
|
|
||
| // Only INVESTIGATOR | ||
| @PreAuthorize("hasRole('INVESTIGATOR')") | ||
| @GetMapping("/api/investigator/test") | ||
| public String investigatorOnly() { | ||
| return "only investigator"; | ||
| } | ||
|
|
||
| // Only REPORTER | ||
| @PreAuthorize("hasRole('REPORTER')") | ||
| @GetMapping("/api/reporter/test") | ||
| public String reporterOnly() { | ||
| return "only reporter"; | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
src/main/java/org/example/alfs/dto/auth/SignupRequestDTO.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package org.example.alfs.dto.auth; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
|
|
||
|
|
||
| @Getter | ||
| @Setter | ||
| public class SignupRequestDTO { | ||
|
|
||
| @NotBlank(message = "Username is required") | ||
| private String username; | ||
|
|
||
| @NotBlank(message = "Password is required") | ||
| private String password; | ||
|
|
||
| } |
103 changes: 103 additions & 0 deletions
103
src/main/java/org/example/alfs/security/JwtAuthenticationFilter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| package org.example.alfs.security; | ||
|
|
||
| import jakarta.servlet.FilterChain; | ||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import org.example.alfs.entities.User; | ||
| import org.example.alfs.repositories.UserRepository; | ||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
| import org.springframework.security.core.context.SecurityContextHolder; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.filter.OncePerRequestFilter; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.List; | ||
|
|
||
| @Component | ||
| public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
|
|
||
| private final JwtService jwtService; | ||
| private final UserRepository userRepository; | ||
|
|
||
| public JwtAuthenticationFilter(JwtService jwtService, UserRepository userRepository) { | ||
| this.jwtService = jwtService; | ||
| this.userRepository = userRepository; | ||
| } | ||
|
|
||
| // Skips filter for LOGIN & H2 | ||
| @Override | ||
| protected boolean shouldNotFilter(HttpServletRequest request) { | ||
| String path = request.getRequestURI(); | ||
| return path.startsWith("/auth") || path.startsWith("/h2-console"); | ||
| } | ||
|
|
||
|
|
||
| @Override | ||
| protected void doFilterInternal(HttpServletRequest request, | ||
| HttpServletResponse response, | ||
| FilterChain filterChain) | ||
| throws ServletException, IOException { | ||
|
|
||
| final String authHeader = request.getHeader("Authorization"); | ||
|
|
||
| // if no token, keep going | ||
| if (authHeader == null || !authHeader.startsWith("Bearer ")) { | ||
| filterChain.doFilter(request, response); | ||
| return; | ||
| } | ||
|
|
||
| String jwt = authHeader.substring(7); | ||
| String username; | ||
| try { | ||
| username = jwtService.extractUsername(jwt); | ||
| } catch (Exception e) { | ||
| filterChain.doFilter(request, response); | ||
| return; | ||
| } | ||
|
|
||
| if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { | ||
|
|
||
| User user = userRepository.findByUsername(username).orElse(null); | ||
|
|
||
| if (user == null) { | ||
| filterChain.doFilter(request, response); | ||
| return; | ||
| } | ||
|
|
||
| // IMPORTANT: | ||
| // We do NOT trust the role stored in the JWT. | ||
| // Instead, we always load the user's role from the database. | ||
| // | ||
| // This ensures that if a user's role changes (e.g. ADMIN β REPORTER), | ||
| // the change takes effect immediately, even if the old JWT is still valid. | ||
| org.springframework.security.core.userdetails.UserDetails userDetails = | ||
| new org.springframework.security.core.userdetails.User( | ||
| user.getUsername(), | ||
| user.getPasswordHash(), | ||
| List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())) | ||
| ); | ||
|
|
||
| boolean valid = jwtService.isTokenValid(jwt, user); | ||
|
|
||
| if (valid) { | ||
| UsernamePasswordAuthenticationToken authToken = | ||
| new UsernamePasswordAuthenticationToken( | ||
| userDetails, | ||
| null, | ||
| userDetails.getAuthorities() | ||
| ); | ||
|
|
||
| authToken.setDetails( | ||
| new org.springframework.security.web.authentication.WebAuthenticationDetailsSource() | ||
| .buildDetails(request) | ||
| ); | ||
|
|
||
| SecurityContextHolder.getContext().setAuthentication(authToken); | ||
| } | ||
| } | ||
|
|
||
| filterChain.doFilter(request, response); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§© Analysis chain
π Script executed:
Repository: ithsjava25/project-backend-alfs
Length of output: 116
π Script executed:
Repository: ithsjava25/project-backend-alfs
Length of output: 720
π Script executed:
# Search for profile-specific property files fd application- src/main/resources/Repository: ithsjava25/project-backend-alfs
Length of output: 57
π Script executed:
# Read the full SecurityConfig.java to understand the context cat -n src/main/java/org/example/alfs/config/SecurityConfig.javaRepository: ithsjava25/project-backend-alfs
Length of output: 2876
Implement Spring profiles to prevent H2 console and disabled security headers from reaching production.
The current configuration exposes the H2 console with direct database access and disabled security headers in all environments. No profile-specific configurations exist to conditionally apply these settings only to development:
spring.h2.console.enabled=truein application.properties)/h2-console/**is permitted without authentication (line 46)These settings should only apply in development. Create
application-dev.propertiesandapplication-prod.propertiesto conditionally enable H2 and relax security only whenspring.profiles.active=dev.Additionally, remove unused imports on lines 6 (
HttpMethod) and 7 (Customizer).π€ Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are intentionally keeping this configuration for development only.
This project is not targeting production deployment, and these settings are acceptable for the current scope.
We have added comments in the code to clarify that this is for development purposes only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@addee1, that's a clear and reasonable explanation β thank you for the context. For a development-only project with a stateless JWT-based REST API, disabling CSRF and frame options and enabling the H2 console are all justified, and adding clarifying comments in the code is a good practice.One minor item still worth addressing from the original comment: the unused imports
HttpMethod(Line 6) andCustomizer(Line 7) inSecurityConfig.javacan be removed to keep the file clean, since they are not referenced anywhere in the class.βοΈ Learnings added