update JWT handling for external services
Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
@@ -38,6 +38,8 @@ dependencies {
|
|||||||
implementation 'com.google.cloud:spring-cloud-gcp-starter'
|
implementation 'com.google.cloud:spring-cloud-gcp-starter'
|
||||||
implementation 'com.google.auth:google-auth-library-oauth2-http'
|
implementation 'com.google.auth:google-auth-library-oauth2-http'
|
||||||
implementation 'io.grpc:grpc-netty'
|
implementation 'io.grpc:grpc-netty'
|
||||||
|
implementation 'io.netty:netty-all'
|
||||||
|
implementation 'com.nimbusds:nimbus-jose-jwt:9.40'
|
||||||
testImplementation 'io.projectreactor:reactor-test'
|
testImplementation 'io.projectreactor:reactor-test'
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||||
|
76
src/main/java/dev/mars3142/fhq/edge/AuthGatewayFilter.java
Normal file
76
src/main/java/dev/mars3142/fhq/edge/AuthGatewayFilter.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package dev.mars3142.fhq.edge;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.JWSAlgorithm;
|
||||||
|
import com.nimbusds.jose.jwk.source.JWKSourceBuilder;
|
||||||
|
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
|
||||||
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
|
import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
|
||||||
|
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
||||||
|
import dev.mars3142.fhq.edge.exceptions.AuthUnauthorizedException;
|
||||||
|
import java.net.URI;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import lombok.val;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||||
|
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class AuthGatewayFilter extends AbstractGatewayFilterFactory<AuthGatewayFilter.Config> {
|
||||||
|
|
||||||
|
public AuthGatewayFilter() {
|
||||||
|
super(Config.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GatewayFilter apply(Config config) {
|
||||||
|
return (exchange, chain) -> {
|
||||||
|
val bearer = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||||
|
if (bearer == null) {
|
||||||
|
log.error("No authorization header");
|
||||||
|
throw new AuthUnauthorizedException();
|
||||||
|
}
|
||||||
|
val token = bearer.substring("Bearer ".length());
|
||||||
|
if (token.isEmpty()) {
|
||||||
|
log.error("Empty token");
|
||||||
|
throw new AuthUnauthorizedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val claimsSet = verifyToken(config, token);
|
||||||
|
|
||||||
|
exchange.getRequest()
|
||||||
|
.mutate()
|
||||||
|
.header("x-user-id", claimsSet.getStringClaim("user_id"));
|
||||||
|
return chain.filter(exchange);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error while verifying token", e);
|
||||||
|
throw new AuthUnauthorizedException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JWTClaimsSet verifyToken(Config config, String token) throws Exception {
|
||||||
|
val url = new URI(
|
||||||
|
"https://www.googleapis.com/service_accounts/v1/jwk/securetoken%40system.gserviceaccount.com").toURL();
|
||||||
|
val keySource = JWKSourceBuilder.create(url).build();
|
||||||
|
val algorithm = JWSAlgorithm.RS256;
|
||||||
|
val selector = new JWSVerificationKeySelector<>(algorithm, keySource);
|
||||||
|
val jwtClaimsSet = new JWTClaimsSet.Builder().issuer("https://securetoken.google.com/firmware-hq")
|
||||||
|
.audience(config.audience).build();
|
||||||
|
val claimsVerifier = new DefaultJWTClaimsVerifier<>(config.audience, jwtClaimsSet, null);
|
||||||
|
claimsVerifier.verify(jwtClaimsSet, null);
|
||||||
|
val processor = new DefaultJWTProcessor<>();
|
||||||
|
processor.setJWSKeySelector(selector);
|
||||||
|
processor.setJWTClaimsSetVerifier(claimsVerifier);
|
||||||
|
return processor.process(token, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
public static class Config {
|
||||||
|
|
||||||
|
private String audience;
|
||||||
|
}
|
||||||
|
}
|
@@ -44,14 +44,13 @@ public class GCPGatewayFilter extends AbstractGatewayFilterFactory<GCPGatewayFil
|
|||||||
val tokenCredential =
|
val tokenCredential =
|
||||||
IdTokenCredentials.newBuilder()
|
IdTokenCredentials.newBuilder()
|
||||||
.setIdTokenProvider((IdTokenProvider) credentials)
|
.setIdTokenProvider((IdTokenProvider) credentials)
|
||||||
.setTargetAudience(config.getAudience())
|
.setTargetAudience(config.audience)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return tokenCredential.refreshAccessToken().getTokenValue();
|
return tokenCredential.refreshAccessToken().getTokenValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
|
||||||
public static class Config {
|
public static class Config {
|
||||||
|
|
||||||
private String audience;
|
private String audience;
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.mars3142.fhq.edge.exceptions;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||||
|
public class AuthUnauthorizedException extends RuntimeException {
|
||||||
|
|
||||||
|
}
|
@@ -35,15 +35,23 @@ spring:
|
|||||||
filters:
|
filters:
|
||||||
- name: GCPGatewayFilter
|
- name: GCPGatewayFilter
|
||||||
args:
|
args:
|
||||||
audience: firmware-hq
|
audience: ${TIMEZONE_SERVICE_URI:http://timezone-service}
|
||||||
|
|
||||||
- id: backend-service
|
- id: account-service
|
||||||
uri: ${BACKEND_SERVICE_URI:http://backend-service}
|
uri: ${BACKEND_SERVICE_URI:http://backend-service}
|
||||||
predicates:
|
predicates:
|
||||||
- Path=/v1/account/**
|
- Path=/v1/account/**
|
||||||
- Path=/v1/token/**
|
|
||||||
filters:
|
filters:
|
||||||
#- AuthGatewayFilter
|
|
||||||
- name: GCPGatewayFilter
|
- name: GCPGatewayFilter
|
||||||
args:
|
args:
|
||||||
audience: firmware-hq
|
audience: ${BACKEND_SERVICE_URI:http://backend-service}
|
||||||
|
|
||||||
|
- id: token-service
|
||||||
|
uri: ${BACKEND_SERVICE_URI:http://backend-service}
|
||||||
|
predicates:
|
||||||
|
- Path=/v1/token/**
|
||||||
|
filters:
|
||||||
|
- AuthGatewayFilter
|
||||||
|
- name: GCPGatewayFilter
|
||||||
|
args:
|
||||||
|
audience: ${BACKEND_SERVICE_URI:http://backend-service}
|
||||||
|
Reference in New Issue
Block a user