Skip to content

Commit d65cfa1

Browse files
committed
Some minor adjustments to JwtAuthenticationFilter and stuff. Added fields to my GraphQL Controller for debugging
1 parent 306eeab commit d65cfa1

4 files changed

Lines changed: 78 additions & 27 deletions

File tree

‎springqpro-backend/src/main/java/com/springqprobackend/springqpro/config/SecurityConfig.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import org.springframework.context.annotation.Configuration;
66
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
77
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
89
import org.springframework.security.config.http.SessionCreationPolicy;
910
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
1011
import org.springframework.security.crypto.password.PasswordEncoder;
1112
import org.springframework.security.web.SecurityFilterChain;
1213
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
1314

15+
@EnableWebSecurity
1416
@EnableMethodSecurity
1517
@Configuration
1618
public class SecurityConfig {

‎springqpro-backend/src/main/java/com/springqprobackend/springqpro/graphql/TaskGraphQLController.java‎

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import com.springqprobackend.springqpro.domain.TaskEntity;
44
import com.springqprobackend.springqpro.enums.TaskStatus;
55
import com.springqprobackend.springqpro.enums.TaskType;
6+
import com.springqprobackend.springqpro.service.ProcessingService;
67
import com.springqprobackend.springqpro.service.TaskService;
78
import com.springqprobackend.springqpro.graphql.controllerRecords.CreateTaskInput;
89
import com.springqprobackend.springqpro.graphql.controllerRecords.UpdateTaskInput;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
912
import org.springframework.graphql.data.method.annotation.SchemaMapping;
1013
import org.springframework.security.access.prepost.PreAuthorize;
14+
import org.springframework.security.core.Authentication;
1115
import org.springframework.stereotype.Controller;
1216
import org.springframework.graphql.data.method.annotation.Argument;
1317
import org.springframework.graphql.data.method.annotation.MutationMapping;
@@ -23,20 +27,10 @@
2327
- The schema defines how the client *sees* the data.
2428
- Spring will automatically wire the /graphql endpoint (no controller mapping is needed).
2529
*/
26-
27-
/* 2025-11-12-DEBUG:
28-
DONE:
29-
- task (query)
30-
- tasks (query)
31-
- createTask (mutation)
32-
TO-DO FROM schema.graphqls:
33-
- updateTask (mutation)
34-
- deleteTask (mutation)
35-
*/
36-
3730
@Controller // IMPORTANT NOTE: GraphQL controllers use @Controller, NOT @RestController (remember this!)
3831
public class TaskGraphQLController {
3932
// Field(s):
33+
private static final Logger logger = LoggerFactory.getLogger(ProcessingService.class);
4034
private final TaskService taskService;
4135
// Constructor(s):
4236
public TaskGraphQLController(TaskService taskService) {
@@ -45,37 +39,46 @@ public TaskGraphQLController(TaskService taskService) {
4539

4640
@QueryMapping // This is GraphQL query resolver.
4741
@PreAuthorize("isAuthenticated()") // 2025-11-24-DEBUG: Securing my GraphQL resolvers for JWT.
48-
public List<TaskEntity> tasks(@Argument TaskStatus status) {
42+
public List<TaskEntity> tasks(@Argument TaskStatus status, Authentication auth) {
43+
logger.info("INFO: GraphQL tasks (by status) Query sent by user:{}", auth.getName());
4944
return taskService.getAllTasks(status);
5045
}
5146
@QueryMapping
5247
@PreAuthorize("isAuthenticated()") // 2025-11-24-DEBUG: Securing my GraphQL resolvers for JWT.
53-
public List<TaskEntity> tasksType(@Argument TaskType type) { return taskService.getAllTasks(type); } // <-- 2025-11-19-DEBUG: ADDITION.
48+
public List<TaskEntity> tasksType(@Argument TaskType type, Authentication auth) {
49+
logger.info("INFO: GraphQL tasks (by type) Query sent by user:{}", auth.getName());
50+
return taskService.getAllTasks(type);
51+
}
5452

5553
@QueryMapping
5654
@PreAuthorize("isAuthenticated()") // 2025-11-24-DEBUG: Securing my GraphQL resolvers for JWT.
57-
public TaskEntity task(@Argument String id) {
55+
public TaskEntity task(@Argument String id, Authentication auth) {
56+
logger.info("INFO: GraphQL task (by id) Query sent by user:{}", auth.getName());
5857
return taskService.getTask(id).orElse(null);
5958
}
6059

6160
@MutationMapping
6261
@PreAuthorize("isAuthenticated()") // 2025-11-24-DEBUG: Securing my GraphQL resolvers for JWT.
63-
public TaskEntity createTask(@Argument("input") CreateTaskInput input) {
62+
public TaskEntity createTask(@Argument("input") CreateTaskInput input, Authentication auth) {
63+
logger.info("INFO: GraphQL createTask Query sent by user:{}", auth.getName());
6464
return taskService.createTask(input.payload(), input.type());
6565
}
6666

6767
@MutationMapping
6868
@Transactional
6969
@PreAuthorize("isAuthenticated()") // 2025-11-24-DEBUG: Securing my GraphQL resolvers for JWT.
70-
public TaskEntity updateTask(@Argument("input") UpdateTaskInput input) {
70+
public TaskEntity updateTask(@Argument("input") UpdateTaskInput input, Authentication auth) {
71+
logger.info("INFO: GraphQL updateTask Query sent by user:{}", auth.getName());
7172
taskService.updateStatus(input.id(), input.status(), input.attempts());
72-
return task(input.id()); // or "return taskService.getTask(id).orElse(null);"
73+
return taskService.getTask(input.id()).orElse(null);
74+
//return task(input.id()) ; // or "return taskService.getTask(id).orElse(null);"
7375
}
7476

7577
@MutationMapping
7678
@Transactional
7779
@PreAuthorize("hasRole('ADMIN')")
78-
public boolean deleteTask(@Argument String id) {
80+
public boolean deleteTask(@Argument String id, Authentication auth) {
81+
logger.info("INFO: GraphQL deleteTask Query sent by user:{}", auth.getName());
7982
return taskService.deleteTask(id);
8083
}
8184

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package com.springqprobackend.springqpro.security;
22

3+
import io.jsonwebtoken.ExpiredJwtException;
4+
import io.jsonwebtoken.JwtException;
35
import jakarta.servlet.FilterChain;
46
import jakarta.servlet.ServletException;
57
import jakarta.servlet.http.HttpServletRequest;
68
import jakarta.servlet.http.HttpServletResponse;
9+
import org.springframework.http.HttpHeaders;
710
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
811
import org.springframework.security.core.context.SecurityContextHolder;
912
import org.springframework.security.core.userdetails.UserDetails;
1013
import org.springframework.security.core.userdetails.UserDetailsService;
1114
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
15+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
1216
import org.springframework.stereotype.Component;
1317
import org.springframework.web.filter.OncePerRequestFilter;
1418

@@ -24,21 +28,36 @@ public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService uds) {
2428
}
2529
@Override
2630
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
27-
String authHeader = request.getHeader("Authorization");
28-
if(authHeader != null && authHeader.startsWith("Bearer ")) {
29-
String token = authHeader.substring(7);
30-
if(jwtUtil.validateToken(token)) {
31-
String email = jwtUtil.extractEmail(token);
31+
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
32+
if(authHeader == null || !authHeader.startsWith("Bearer ")) {
33+
// No JWT – let the request continue unauthenticated
34+
chain.doFilter(request, response);
35+
return;
36+
}
37+
String token = authHeader.substring(7);
38+
try {
39+
String email = jwtUtil.validateAndGetSubject(token);
40+
if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) {
3241
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
33-
UsernamePasswordAuthenticationToken auth =
34-
new UsernamePasswordAuthenticationToken(
42+
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
3543
userDetails,
3644
null,
3745
userDetails.getAuthorities()
38-
);
46+
);
47+
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
3948
SecurityContextHolder.getContext().setAuthentication(auth);
4049
}
50+
} catch (Exception ex) {
51+
// Token invalid/expired -> do NOT authenticate, just continue.
52+
// SecurityContext remains empty -> downstream sees request as anonymous.
53+
SecurityContextHolder.clearContext();
54+
if(ex instanceof ExpiredJwtException) {
55+
logger.warn("JWT expired: {}");
56+
}
57+
if(ex instanceof JwtException) {
58+
logger.warn("Invalid JWT: {}");
59+
}
4160
}
42-
chain.doFilter(request,response);
61+
chain.doFilter(request, response);
4362
}
4463
}

‎springqpro-backend/src/main/java/com/springqprobackend/springqpro/security/JwtUtil.java‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,31 @@ public boolean isExpired(String token) {
6262
return true; // Invalid tokens are expired.
6363
}
6464
}
65+
/* NOTE: This method below can definitely just combine isExpired and extractEmail but better to make it be a "single parse"
66+
so I completely eliminate the possibility of signature tampering, corrupt tokens, and so on. */
67+
/* Stuff that could go wrong apparently according to ChatGPT (if I just did isExpired -> extractEmail):
68+
Invalid signature, Tampered or truncated token, Wrong key, Wrong algorithm, Null token, Missing subject
69+
Expired token, and Other malformed JWT errors */
70+
public String validateAndGetSubject(String token) {
71+
try {
72+
Claims claims = Jwts.parser()
73+
.verifyWith(key)
74+
.build()
75+
.parseSignedClaims(token)
76+
.getPayload();
77+
// Check expiration manually (JJWT 0.12 no longer auto-fails expired tokens)
78+
if (claims.getExpiration() == null || claims.getExpiration().before(new Date())) {
79+
throw new ExpiredJwtException(null, claims, "Token is expired");
80+
}
81+
return claims.getSubject();
82+
} catch (ExpiredJwtException ex) {
83+
throw ex; // bubble up "expired" explicitly
84+
} catch (JwtException ex) {
85+
// SignatureException, MalformedJwtException, UnsupportedJwtException, etc.
86+
throw new JwtException("Invalid JWT token", ex);
87+
} catch (IllegalArgumentException ex) {
88+
throw new JwtException("JWT token is empty or null", ex);
89+
}
90+
}
91+
6592
}

0 commit comments

Comments
 (0)