I am very confused about this architecture. I am not even sure is it possible.
I have more than 10 microservises and a API Gateway. I want to add authentication and authorization to this system. One of this services is authentication-server and it has
an endpoint which is /signin
@PostMapping(value = "/signin")
public UserLoginResponse login(@Valid @RequestBody UserLoginRequest userLoginRequest) {
return authService.login(userLoginRequest);
}
public class UserLoginResponse {
private String accessToken; //accessToken is jwt token and it has ROLE field.
}
public class UserLoginRequest {
private String username;
private String password;
}
Here is the confusing part for me: Right now gateway creates code duplication. When I add to a new endpoint, I need to add almost same controller/service/models to API Gateway.
For example:
Lets say microservice A has /product endpoint, these are (veeery roughly) the classes I should have
// Controller
class ProductController {
@GetMapping("/product/{id}")
public ProductResponse getProduct(@PathVariable String id) {
return productService.getProduct(id)
}
}
// Service
class ProductService {
public getProduct(String id){
return productRepository.get(id);
}
}
// Response DTO
class ProductResponse(){
private String id;
private String name;
}
Our team also has implemented classes in the gateway.
//Controller has authorization with @PreAuthorize annotation.
@RestController
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
class ProductController {
@GetMapping("/product/{id}")
public ProductResponse getProduct(PathVariable String id) {
return productService.getProduct(id)
}
}
// Service
class ProductService {
private final ClientApi productClientApi;
public ProductService(ClientApi productClientApi) {
this.productClientApi = productClientApi;
}
public getProduct(String id){
return productClientApi.getProduct(id);
}
}
//This is feign client. It makes http requests to product-api
@FeignClient(
"product-api",
url = "\${product-api.base-url}",
configuration = [FeignClientConfiguration::class]
)
interface ClientApi(){
@GetMapping( value = {"product/{id}"}, consumes = {"application/json"} )
ProductResponse getProduct(@PathVariable String id);
}
// Response DTO
class ProductResponse(){
private String id;
private String name;
}
- When a request comes to
/productits jwt token controlled here and if it has proper permission, it goes to the service layer, - Service layer makes request to
product-api(which is microservice A) - returns the response
Question: There should be easier way. Every new endpoint in the services costs us code duplication in Gateway. I think I want just routing. Whatever comes to gateway directly should be routed to services and it should still has the authentication/authorization responsibility. I know that I can do that as below with spring-cloud-gateway but I couldn't figure out how can i do that with authentication and authorization. Can anyone explain me that am i thinking wrong ?
spring:
application:
name: "API-GATEWAY"
cloud:
gateway:
routes:
- id: product-service
uri: 'http://product-url:8083'
predicates:
- Path=/product/**