Skip to content
This repository was archived by the owner on Nov 23, 2025. It is now read-only.

Commit ff65981

Browse files
committed
feat: Implement JWT authentication and public service type access in the admin service
1 parent 25ad199 commit ff65981

7 files changed

Lines changed: 176 additions & 6 deletions

File tree

admin-service/pom.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
<groupId>org.springframework.boot</groupId>
4747
<artifactId>spring-boot-starter-security</artifactId>
4848
</dependency>
49+
<dependency>
50+
<groupId>org.springframework.boot</groupId>
51+
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
52+
</dependency>
4953
<dependency>
5054
<groupId>org.springframework.boot</groupId>
5155
<artifactId>spring-boot-starter-validation</artifactId>
@@ -54,6 +58,29 @@
5458
<groupId>org.springframework.boot</groupId>
5559
<artifactId>spring-boot-starter-web</artifactId>
5660
</dependency>
61+
<dependency>
62+
<groupId>org.springframework.boot</groupId>
63+
<artifactId>spring-boot-starter-webflux</artifactId>
64+
</dependency>
65+
66+
<!-- JWT dependencies -->
67+
<dependency>
68+
<groupId>io.jsonwebtoken</groupId>
69+
<artifactId>jjwt-api</artifactId>
70+
<version>0.12.6</version>
71+
</dependency>
72+
<dependency>
73+
<groupId>io.jsonwebtoken</groupId>
74+
<artifactId>jjwt-impl</artifactId>
75+
<version>0.12.6</version>
76+
<scope>runtime</scope>
77+
</dependency>
78+
<dependency>
79+
<groupId>io.jsonwebtoken</groupId>
80+
<artifactId>jjwt-jackson</artifactId>
81+
<version>0.12.6</version>
82+
<scope>runtime</scope>
83+
</dependency>
5784

5885
<dependency>
5986
<groupId>org.springframework.boot</groupId>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.techtorque.admin_service.config;
2+
3+
import io.jsonwebtoken.Claims;
4+
import io.jsonwebtoken.Jwts;
5+
import io.jsonwebtoken.security.Keys;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
8+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
9+
import org.springframework.security.core.context.SecurityContextHolder;
10+
import org.springframework.stereotype.Component;
11+
import org.springframework.web.filter.OncePerRequestFilter;
12+
13+
import jakarta.servlet.FilterChain;
14+
import jakarta.servlet.ServletException;
15+
import jakarta.servlet.http.HttpServletRequest;
16+
import jakarta.servlet.http.HttpServletResponse;
17+
import javax.crypto.SecretKey;
18+
import java.io.IOException;
19+
import java.nio.charset.StandardCharsets;
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.stream.Collectors;
24+
25+
@Component
26+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
27+
28+
@Value("${jwt.secret:mysecretkey}")
29+
private String jwtSecret;
30+
31+
@Override
32+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
33+
throws ServletException, IOException {
34+
35+
String authHeader = request.getHeader("Authorization");
36+
37+
if (authHeader != null && authHeader.startsWith("Bearer ")) {
38+
String token = authHeader.substring(7);
39+
40+
try {
41+
SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
42+
43+
Claims claims = Jwts.parser()
44+
.verifyWith(key)
45+
.build()
46+
.parseSignedClaims(token)
47+
.getPayload();
48+
49+
String username = claims.getSubject();
50+
@SuppressWarnings("unchecked")
51+
List<String> roles = (List<String>) claims.get("roles");
52+
53+
if (username != null && roles != null) {
54+
List<SimpleGrantedAuthority> authorities = roles.stream()
55+
.map(role -> {
56+
String roleUpper = role.trim().toUpperCase();
57+
// Treat SUPER_ADMIN as ADMIN for authorization purposes
58+
if ("SUPER_ADMIN".equals(roleUpper)) {
59+
// Add both SUPER_ADMIN and ADMIN roles
60+
return Arrays.asList(
61+
new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"),
62+
new SimpleGrantedAuthority("ROLE_ADMIN")
63+
);
64+
}
65+
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + roleUpper));
66+
})
67+
.flatMap(List::stream)
68+
.collect(Collectors.toList());
69+
70+
UsernamePasswordAuthenticationToken authentication =
71+
new UsernamePasswordAuthenticationToken(username, null, authorities);
72+
73+
SecurityContextHolder.getContext().setAuthentication(authentication);
74+
}
75+
} catch (Exception e) {
76+
logger.warn("JWT token validation failed: " + e.getMessage());
77+
}
78+
}
79+
80+
filterChain.doFilter(request, response);
81+
}
82+
}

admin-service/src/main/java/com/techtorque/admin_service/config/SecurityConfig.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.techtorque.admin_service.config;
22

3+
import lombok.RequiredArgsConstructor;
34
import org.springframework.context.annotation.Bean;
45
import org.springframework.context.annotation.Configuration;
56
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@@ -12,8 +13,11 @@
1213
@Configuration
1314
@EnableWebSecurity
1415
@EnableMethodSecurity(prePostEnabled = true)
16+
@RequiredArgsConstructor
1517
public class SecurityConfig {
1618

19+
private final JwtAuthenticationFilter jwtAuthenticationFilter;
20+
1721
// A more comprehensive whitelist for Swagger/OpenAPI, based on the auth-service config.
1822
private static final String[] SWAGGER_WHITELIST = {
1923
"/v3/api-docs/**",
@@ -24,6 +28,11 @@ public class SecurityConfig {
2428
"/api-docs/**"
2529
};
2630

31+
// Public endpoints accessible by all authenticated users (including CUSTOMER role)
32+
private static final String[] PUBLIC_ENDPOINTS = {
33+
"/public/**"
34+
};
35+
2736
@Bean
2837
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
2938
http
@@ -42,12 +51,18 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
4251
// Permit all requests to the Swagger UI and API docs paths
4352
.requestMatchers(SWAGGER_WHITELIST).permitAll()
4453

54+
// Allow authenticated users to access public endpoints
55+
.requestMatchers(PUBLIC_ENDPOINTS).authenticated()
56+
4557
// All other requests must be authenticated
4658
.anyRequest().authenticated()
4759
)
4860

49-
// Add our custom filter to read headers from the Gateway
50-
.addFilterBefore(new GatewayHeaderFilter(), UsernamePasswordAuthenticationFilter.class);
61+
// Add JWT filter first (for direct service-to-service calls with JWT)
62+
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
63+
64+
// Add our custom filter to read headers from the Gateway (for gateway-routed calls)
65+
.addFilterAfter(new GatewayHeaderFilter(), JwtAuthenticationFilter.class);
5166

5267
return http.build();
5368
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.techtorque.admin_service.controller;
2+
3+
import com.techtorque.admin_service.dto.response.ServiceTypeResponse;
4+
import com.techtorque.admin_service.service.AdminServiceConfigService;
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
7+
import lombok.RequiredArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.*;
11+
12+
import java.util.List;
13+
14+
/**
15+
* Public controller for service types - accessible by all authenticated users and other microservices
16+
* This allows customers and other services to read active service types without admin privileges
17+
*/
18+
@RestController
19+
@RequestMapping("/public/service-types")
20+
@RequiredArgsConstructor
21+
@Slf4j
22+
@Tag(name = "Public Service Types", description = "Public API for accessing service types")
23+
public class PublicServiceTypeController {
24+
25+
private final AdminServiceConfigService adminServiceConfigService;
26+
27+
@GetMapping
28+
@Operation(summary = "Get all active service types", description = "Retrieve all active service types available to customers")
29+
public ResponseEntity<List<ServiceTypeResponse>> getActiveServiceTypes() {
30+
log.info("Public request: Fetching all active service types");
31+
List<ServiceTypeResponse> serviceTypes = adminServiceConfigService.getAllServiceTypes(true); // activeOnly = true
32+
log.info("Retrieved {} active service types for public access", serviceTypes.size());
33+
return ResponseEntity.ok(serviceTypes);
34+
}
35+
36+
@GetMapping("/{id}")
37+
@Operation(summary = "Get service type by ID", description = "Retrieve a specific service type by its ID")
38+
public ResponseEntity<ServiceTypeResponse> getServiceTypeById(@PathVariable String id) {
39+
log.info("Public request: Fetching service type with ID: {}", id);
40+
ServiceTypeResponse serviceType = adminServiceConfigService.getServiceTypeById(id);
41+
return ResponseEntity.ok(serviceType);
42+
}
43+
}

admin-service/src/main/java/com/techtorque/admin_service/dto/response/ServiceTypeResponse.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public class ServiceTypeResponse {
2020
private String name;
2121
private String description;
2222
private String category;
23-
private BigDecimal price;
24-
private Integer durationMinutes;
23+
private BigDecimal basePriceLKR; // Changed from 'price' to match frontend
24+
private Integer estimatedDurationMinutes; // Changed from 'durationMinutes' to match frontend
2525
private Boolean active;
2626
private Boolean requiresApproval;
2727
private Integer dailyCapacity;

admin-service/src/main/java/com/techtorque/admin_service/service/impl/AdminServiceConfigServiceImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ private ServiceTypeResponse convertToResponse(ServiceType serviceType) {
134134
.name(serviceType.getName())
135135
.description(serviceType.getDescription())
136136
.category(serviceType.getCategory())
137-
.price(serviceType.getPrice())
138-
.durationMinutes(serviceType.getDefaultDurationMinutes())
137+
.basePriceLKR(serviceType.getPrice())
138+
.estimatedDurationMinutes(serviceType.getDefaultDurationMinutes())
139139
.active(serviceType.getActive())
140140
.requiresApproval(serviceType.getRequiresApproval())
141141
.dailyCapacity(serviceType.getDailyCapacity())

admin-service/src/main/resources/application.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ spring.jpa.properties.hibernate.format_sql=true
1717
# Development/Production Profile
1818
spring.profiles.active=${SPRING_PROFILE:dev}
1919

20+
# JWT Configuration (must match Auth Service secret)
21+
jwt.secret=${JWT_SECRET:YourSuperSecretKeyForJWTGoesHereAndItMustBeVeryLongForSecurityPurposes}
22+
2023
# OpenAPI access URL
2124
# http://localhost:8087/swagger-ui/index.html
2225

0 commit comments

Comments
 (0)