From ed95d4339791a1973bf07b3ef7f60215bef066fa Mon Sep 17 00:00:00 2001
From: PAException <35733278+PAException@users.noreply.github.com>
Date: Tue, 19 May 2020 17:45:23 +0200
Subject: [PATCH 1/3] Added spring-auth module (#14)
---
pom.xml | 1 +
spring-auth/pom.xml | 77 ++++++++++++++++++
.../gewia/common/spring/auth/AuthScope.java | 18 +++++
.../common/spring/auth/Authentication.java | 14 ++++
.../spring/auth/IgnoreServiceToken.java | 12 +++
.../spring/auth/SpringAuthentication.java | 23 ++++++
.../auth/SpringAuthenticationWebConfig.java | 42 ++++++++++
.../auth/interceptor/ScopeInterceptor.java | 78 +++++++++++++++++++
.../interceptor/ServiceTokenInterceptor.java | 36 +++++++++
9 files changed, 301 insertions(+)
create mode 100644 spring-auth/pom.xml
create mode 100644 spring-auth/src/main/java/com/gewia/common/spring/auth/AuthScope.java
create mode 100644 spring-auth/src/main/java/com/gewia/common/spring/auth/Authentication.java
create mode 100644 spring-auth/src/main/java/com/gewia/common/spring/auth/IgnoreServiceToken.java
create mode 100644 spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthentication.java
create mode 100644 spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthenticationWebConfig.java
create mode 100644 spring-auth/src/main/java/com/gewia/common/spring/auth/interceptor/ScopeInterceptor.java
create mode 100644 spring-auth/src/main/java/com/gewia/common/spring/auth/interceptor/ServiceTokenInterceptor.java
diff --git a/pom.xml b/pom.xml
index c4a279a..c8ff076 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
scope
auth
util
+ spring-auth
diff --git a/spring-auth/pom.xml b/spring-auth/pom.xml
new file mode 100644
index 0000000..96526e7
--- /dev/null
+++ b/spring-auth/pom.xml
@@ -0,0 +1,77 @@
+
+
+
+ gewia-common
+ com.gewia.common
+ 1.0
+
+ 4.0.0
+ spring-auth
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 2.1.10.RELEASE
+ pom
+ import
+
+
+
+
+
+
+ com.gewia.common
+ auth
+ ${project.parent.version}
+ compile
+
+
+ com.gewia.common
+ scope
+ ${project.parent.version}
+ compile
+
+
+
+ com.auth0
+ java-jwt
+ 3.10.3
+ compile
+
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+ compile
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/AuthScope.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/AuthScope.java
new file mode 100644
index 0000000..8a1116e
--- /dev/null
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/AuthScope.java
@@ -0,0 +1,18 @@
+package com.gewia.common.spring.auth;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Repeatable(Authentication.class)
+public @interface AuthScope {
+
+ String value() default "";
+
+ String scope() default "";
+
+}
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/Authentication.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/Authentication.java
new file mode 100644
index 0000000..cb596e9
--- /dev/null
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/Authentication.java
@@ -0,0 +1,14 @@
+package com.gewia.common.spring.auth;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Authentication {
+
+ AuthScope[] value();
+
+}
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/IgnoreServiceToken.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/IgnoreServiceToken.java
new file mode 100644
index 0000000..3216d4c
--- /dev/null
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/IgnoreServiceToken.java
@@ -0,0 +1,12 @@
+package com.gewia.common.spring.auth;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface IgnoreServiceToken {
+
+}
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthentication.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthentication.java
new file mode 100644
index 0000000..594a060
--- /dev/null
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthentication.java
@@ -0,0 +1,23 @@
+package com.gewia.common.spring.auth;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Getter;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+@ComponentScan("com.gewia.common.spring.auth")
+public abstract class SpringAuthentication implements InitializingBean {
+
+ @Getter(AccessLevel.PACKAGE) private static List interceptors = new ArrayList<>();
+
+ @Override
+ public void afterPropertiesSet() {
+ interceptors = this.addAuthenticationInterceptors(interceptors);
+ }
+
+ abstract public List addAuthenticationInterceptors(List authenticationInterceptors);
+
+}
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthenticationWebConfig.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthenticationWebConfig.java
new file mode 100644
index 0000000..4daa459
--- /dev/null
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthenticationWebConfig.java
@@ -0,0 +1,42 @@
+package com.gewia.common.spring.auth;
+
+import com.auth0.jwt.interfaces.DecodedJWT;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+@Configuration
+@EnableWebMvc
+public class SpringAuthenticationWebConfig implements WebMvcConfigurer, HandlerMethodArgumentResolver {
+
+ @Override
+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
+ return ((HttpServletRequest) webRequest.getNativeRequest()).getAttribute("accessToken");
+ }
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.getParameterType().equals(DecodedJWT.class);
+ }
+
+ @Override
+ public void addArgumentResolvers(List resolvers) {
+ resolvers.add(this);
+ }
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ for (HandlerInterceptorAdapter interceptors : SpringAuthentication.getInterceptors())
+ registry.addInterceptor(interceptors).addPathPatterns("/**/*");
+ }
+
+}
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/interceptor/ScopeInterceptor.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/interceptor/ScopeInterceptor.java
new file mode 100644
index 0000000..e89c751
--- /dev/null
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/interceptor/ScopeInterceptor.java
@@ -0,0 +1,78 @@
+package com.gewia.common.spring.auth.interceptor;
+
+import com.auth0.jwt.interfaces.Claim;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.gewia.common.auth.jwt.JwtUtil;
+import com.gewia.common.spring.auth.AuthScope;
+import com.gewia.common.spring.auth.Authentication;
+import com.gewia.common.util.Pair;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+@AllArgsConstructor
+public class ScopeInterceptor extends HandlerInterceptorAdapter {
+
+ private final JwtUtil jwtUtil;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ HandlerMethod method = (HandlerMethod) handler;
+
+ AuthScope[] authScopes;
+ Authentication auth = method.getMethodAnnotation(Authentication.class);
+ AuthScope methodAuthScope = method.getMethodAnnotation(AuthScope.class);
+ if (auth != null) authScopes = auth.value();
+ else {
+ if (methodAuthScope == null) return true;
+ authScopes = new AuthScope[]{methodAuthScope};
+ }
+
+
+ String jwt = request.getHeader("Authorization");
+ if (jwt == null || jwt.isBlank()) return false;
+
+ Pair result = this.jwtUtil.verify(jwt);
+ switch (result.getRight()) {
+ case EXPIRED:
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ return false;
+ case INVALID:
+ response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
+ return false;
+ case FAILED:
+ response.setStatus(HttpStatus.EXPECTATION_FAILED.value());
+ return false;
+ case UNKNOWN:
+ response.setStatus(HttpStatus.FORBIDDEN.value());
+ return false;
+ default:
+ response.setStatus(HttpStatus.OK.value());
+ }
+
+ Claim claim = result.getLeft().getClaim("scopes");
+ List userScopes = claim.asList(String.class);
+ for (AuthScope authScope : authScopes) {
+ String scope = authScope.scope();
+ if (scope.isBlank()) scope = authScope.value();
+ if (!scope.isBlank()) {
+ boolean isPresent = false;
+ for (String userScope : userScopes)
+ if (userScope.equalsIgnoreCase(scope)) {
+ isPresent = true;
+ break;
+ }
+ if (!isPresent) return false;
+ }
+ }
+
+ request.setAttribute("accessToken", result.getLeft());
+
+ return true;
+ }
+
+}
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/interceptor/ServiceTokenInterceptor.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/interceptor/ServiceTokenInterceptor.java
new file mode 100644
index 0000000..42212a4
--- /dev/null
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/interceptor/ServiceTokenInterceptor.java
@@ -0,0 +1,36 @@
+package com.gewia.common.spring.auth.interceptor;
+
+import com.gewia.common.spring.auth.IgnoreServiceToken;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+@AllArgsConstructor
+public class ServiceTokenInterceptor extends HandlerInterceptorAdapter {
+
+ private final String serviceToken;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ response.setStatus(HttpStatus.FORBIDDEN.value());
+
+ HandlerMethod method = (HandlerMethod) handler;
+ if (method.hasMethodAnnotation(IgnoreServiceToken.class) ||
+ method.getMethod().getDeclaringClass().getAnnotation(IgnoreServiceToken.class) != null) {
+ response.setStatus(HttpStatus.OK.value());
+ return true;
+ }
+
+ String serviceToken = request.getHeader("X-ServiceToken");
+
+ if (serviceToken == null) return false;
+ if (!this.serviceToken.equals(serviceToken)) return false;
+
+ response.setStatus(HttpStatus.OK.value());
+ return true;
+ }
+
+}
From 87e295429c46dcdd5fc0f1b1d112df42a7d401aa Mon Sep 17 00:00:00 2001
From: PAException
Date: Mon, 11 May 2020 19:40:21 +0200
Subject: [PATCH 2/3] Created a class to map JWTScopes (#18)
---
auth/pom.xml | 5 ++
.../com/gewia/common/auth/jwt/JwtScopes.java | 39 ++++++++++++
.../gewia/common/auth/jwt/JwtScopesTest.java | 60 +++++++++++++++++++
.../java/com/gewia/common/util/Executor.java | 8 +++
4 files changed, 112 insertions(+)
create mode 100644 auth/src/main/java/com/gewia/common/auth/jwt/JwtScopes.java
create mode 100644 auth/src/test/java/com/gewia/common/auth/jwt/JwtScopesTest.java
create mode 100644 util/src/main/java/com/gewia/common/util/Executor.java
diff --git a/auth/pom.xml b/auth/pom.xml
index 087976b..5e66cfb 100644
--- a/auth/pom.xml
+++ b/auth/pom.xml
@@ -18,6 +18,11 @@
${project.parent.version}
compile
+
+ com.gewia.common
+ scope
+ ${project.parent.version}
+
com.auth0
diff --git a/auth/src/main/java/com/gewia/common/auth/jwt/JwtScopes.java b/auth/src/main/java/com/gewia/common/auth/jwt/JwtScopes.java
new file mode 100644
index 0000000..3a5bfcb
--- /dev/null
+++ b/auth/src/main/java/com/gewia/common/auth/jwt/JwtScopes.java
@@ -0,0 +1,39 @@
+package com.gewia.common.auth.jwt;
+
+import com.gewia.common.util.Executor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JwtScopes {
+
+ @Getter private List scopes = new ArrayList<>();
+ private boolean containing = false;
+
+ public JwtScopes(List scopes) {
+ this.scopes = Collections.unmodifiableList(scopes);
+ }
+
+ public JwtScopes includes(String scope, Executor executor) {
+ if (this.scopes.contains(scope)) {
+ containing = true;
+ if (executor != null) executor.action();
+ } else containing = false;
+
+ return this;
+ }
+
+ public void orElse(Executor executor) {
+ if (!containing && executor != null) executor.action();
+ containing = false;
+ }
+
+ public boolean getResult() {
+ return containing;
+ }
+
+}
diff --git a/auth/src/test/java/com/gewia/common/auth/jwt/JwtScopesTest.java b/auth/src/test/java/com/gewia/common/auth/jwt/JwtScopesTest.java
new file mode 100644
index 0000000..c0c2c44
--- /dev/null
+++ b/auth/src/test/java/com/gewia/common/auth/jwt/JwtScopesTest.java
@@ -0,0 +1,60 @@
+package com.gewia.common.auth.jwt;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JwtScopesTest {
+
+ @Test
+ public void testScopesUnmodifiable() {
+ JwtScopes jwtScopes = this.getExampleJwtScopes();
+
+ Assert.assertThrows(UnsupportedOperationException.class, () -> jwtScopes.getScopes().add("another.example.scope"));
+ }
+
+ @Test
+ public void testResultOperation() {
+ JwtScopes jwtScopes = this.getExampleJwtScopes();
+
+ Assert.assertTrue(jwtScopes.includes("microservice.topic.mode.limitation.extra", null).getResult());
+ Assert.assertFalse(jwtScopes.includes("another.exmaple.scope", null).getResult());
+ }
+
+ @Test
+ public void testOrElseOperation() {
+ JwtScopes jwtScopes = this.getExampleJwtScopes();
+
+ AtomicInteger i = new AtomicInteger();
+ jwtScopes.includes("another.example.scope", i::getAndIncrement).orElse(i::getAndDecrement);
+ Assert.assertEquals(-1, i.get());
+ }
+
+ @Test
+ public void testIncludeOperation() {
+ JwtScopes jwtScopes = this.getExampleJwtScopes();
+
+ AtomicInteger i = new AtomicInteger();
+ jwtScopes.includes("microservice.topic.mode.limitation.extra", i::getAndIncrement).orElse(i::getAndDecrement);
+ Assert.assertEquals(1, i.get());
+ }
+
+ @Test
+ public void testInitializing() {
+ List exampleScopes = new ArrayList<>();
+ exampleScopes.add("microservice.topic.mode.limitation.extra");
+ JwtScopes jwtScopes = new JwtScopes(exampleScopes);
+
+ Assert.assertEquals(exampleScopes, jwtScopes.getScopes());
+ }
+
+ private JwtScopes getExampleJwtScopes() {
+ List exampleScopes = new ArrayList<>();
+ exampleScopes.add("microservice.topic.mode.limitation.extra");
+
+ return new JwtScopes(exampleScopes);
+ }
+
+}
diff --git a/util/src/main/java/com/gewia/common/util/Executor.java b/util/src/main/java/com/gewia/common/util/Executor.java
new file mode 100644
index 0000000..06ad4e2
--- /dev/null
+++ b/util/src/main/java/com/gewia/common/util/Executor.java
@@ -0,0 +1,8 @@
+package com.gewia.common.util;
+
+@FunctionalInterface
+public interface Executor {
+
+ void action();
+
+}
From 84f23317c9ec1ca4a67c33e3133e0b9384969be8 Mon Sep 17 00:00:00 2001
From: PAException <35733278+PAException@users.noreply.github.com>
Date: Sat, 23 May 2020 15:54:21 +0200
Subject: [PATCH 3/3] Added Jwt as parameter of methods
---
.../main/java/com/gewia/common/auth/jwt/Jwt.java | 14 ++++++++++++++
.../java/com/gewia/common/auth/jwt/JwtScopes.java | 4 ++++
.../common/spring/auth/SpringAuthentication.java | 4 ++--
.../spring/auth/SpringAuthenticationWebConfig.java | 12 +++++++++---
4 files changed, 29 insertions(+), 5 deletions(-)
create mode 100644 auth/src/main/java/com/gewia/common/auth/jwt/Jwt.java
diff --git a/auth/src/main/java/com/gewia/common/auth/jwt/Jwt.java b/auth/src/main/java/com/gewia/common/auth/jwt/Jwt.java
new file mode 100644
index 0000000..fd4f8f3
--- /dev/null
+++ b/auth/src/main/java/com/gewia/common/auth/jwt/Jwt.java
@@ -0,0 +1,14 @@
+package com.gewia.common.auth.jwt;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import java.util.UUID;
+
+@Getter
+@AllArgsConstructor
+public class Jwt {
+
+ private final UUID userId;
+ private final JwtScopes userScopes;
+
+}
diff --git a/auth/src/main/java/com/gewia/common/auth/jwt/JwtScopes.java b/auth/src/main/java/com/gewia/common/auth/jwt/JwtScopes.java
index 3a5bfcb..160bacd 100644
--- a/auth/src/main/java/com/gewia/common/auth/jwt/JwtScopes.java
+++ b/auth/src/main/java/com/gewia/common/auth/jwt/JwtScopes.java
@@ -36,4 +36,8 @@ public boolean getResult() {
return containing;
}
+ public boolean hasScope(String scope) {
+ return this.scopes.contains(scope);
+ }
+
}
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthentication.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthentication.java
index 594a060..219c068 100644
--- a/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthentication.java
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthentication.java
@@ -15,9 +15,9 @@ public abstract class SpringAuthentication implements InitializingBean {
@Override
public void afterPropertiesSet() {
- interceptors = this.addAuthenticationInterceptors(interceptors);
+ this.addAuthenticationInterceptors(interceptors);
}
- abstract public List addAuthenticationInterceptors(List authenticationInterceptors);
+ abstract public void addAuthenticationInterceptors(List authenticationInterceptors);
}
diff --git a/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthenticationWebConfig.java b/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthenticationWebConfig.java
index 4daa459..afb427b 100644
--- a/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthenticationWebConfig.java
+++ b/spring-auth/src/main/java/com/gewia/common/spring/auth/SpringAuthenticationWebConfig.java
@@ -1,8 +1,11 @@
package com.gewia.common.spring.auth;
-import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.List;
+import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.gewia.common.auth.jwt.Jwt;
+import com.gewia.common.auth.jwt.JwtScopes;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
@@ -20,12 +23,15 @@ public class SpringAuthenticationWebConfig implements WebMvcConfigurer, HandlerM
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
- return ((HttpServletRequest) webRequest.getNativeRequest()).getAttribute("accessToken");
+ DecodedJWT decodedJWT = (DecodedJWT) ((HttpServletRequest) webRequest.getNativeRequest()).getAttribute("accessToken");
+
+ return new Jwt(UUID.fromString(decodedJWT.getClaim("userId").asString()),
+ new JwtScopes(decodedJWT.getClaim("scopes").asList(String.class)));
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
- return parameter.getParameterType().equals(DecodedJWT.class);
+ return parameter.getParameterType().equals(Jwt.class);
}
@Override