-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add AuthorizationSecurityFeature, BearerAuthenticator and refactor ex…
…isting code using this
- Loading branch information
Showing
12 changed files
with
587 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
...b-jersey/src/main/java/com/coreoz/plume/jersey/security/AuthorizationSecurityFeature.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package com.coreoz.plume.jersey.security; | ||
|
||
import jakarta.ws.rs.container.ContainerRequestContext; | ||
import jakarta.ws.rs.container.ContainerRequestFilter; | ||
import jakarta.ws.rs.container.DynamicFeature; | ||
import jakarta.ws.rs.container.ResourceInfo; | ||
import jakarta.ws.rs.core.FeatureContext; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.lang.reflect.AnnotatedElement; | ||
|
||
/** | ||
* Provide a general way of implementing a Jersey request authentication filter | ||
* that will be active on a method or a class depending on the presence of an annotation.<br> | ||
* <br> | ||
* The priority will be:<br> | ||
* 1. If the annotation is present on the resource method, then this annotation will be used when calling the {@link AuthorizationVerifier}<br> | ||
* 2. Else if the annotation is present on the resource class, then this annotation will be used when calling the {@link AuthorizationVerifier}<br> | ||
* 3. Else the authentication filter will not be active for the whole resource | ||
* @param <A> The annotation type that will be looked to on the resource to enable the authentication filter | ||
* @see com.coreoz.plume.jersey.security.basic.BasicAuthenticator | ||
* @see com.coreoz.plume.jersey.security.bearer.BearerAuthenticator | ||
* @see com.coreoz.plume.jersey.security.permission.PermissionFeature | ||
*/ | ||
@Slf4j | ||
public class AuthorizationSecurityFeature<A extends Annotation> implements DynamicFeature { | ||
private final Class<A> restrictedAnnotations; | ||
private final AuthorizationVerifier<A> authorizationVerifier; | ||
|
||
/** | ||
* @param restrictedAnnotations The annotation type that will hold the authentication | ||
* @param authorizationVerifier The function that will verify that the request is authorized, else it should throw {@link jakarta.ws.rs.ForbiddenException} or {@link jakarta.ws.rs.ClientErrorException} | ||
*/ | ||
public AuthorizationSecurityFeature(Class<A> restrictedAnnotations, AuthorizationVerifier<A> authorizationVerifier) { | ||
this.restrictedAnnotations = restrictedAnnotations; | ||
this.authorizationVerifier = authorizationVerifier; | ||
} | ||
|
||
@Override | ||
public void configure(ResourceInfo methodResourceInfo, FeatureContext methodResourceContext) { | ||
A methodAnnotation = fetchAnnotation(methodResourceInfo.getResourceMethod()); | ||
if (methodAnnotation != null) { | ||
methodResourceContext.register(new AuthorizationFilter(methodAnnotation)); | ||
} else { | ||
A classAnnotation = fetchAnnotation(methodResourceInfo.getResourceClass()); | ||
if (classAnnotation != null) { | ||
methodResourceContext.register(new AuthorizationFilter(classAnnotation)); | ||
} | ||
} | ||
} | ||
|
||
private A fetchAnnotation(AnnotatedElement annotatedElement) { | ||
return annotatedElement.getAnnotation(restrictedAnnotations); | ||
} | ||
|
||
private class AuthorizationFilter implements ContainerRequestFilter { | ||
private final A authorizationAnnotation; | ||
|
||
public AuthorizationFilter(A authorizationAnnotation) { | ||
this.authorizationAnnotation = authorizationAnnotation; | ||
} | ||
|
||
@Override | ||
public void filter(ContainerRequestContext requestContext) { | ||
authorizationVerifier.verifyAuthentication(authorizationAnnotation, requestContext); | ||
} | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
plume-web-jersey/src/main/java/com/coreoz/plume/jersey/security/AuthorizationVerifier.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.coreoz.plume.jersey.security; | ||
|
||
import jakarta.ws.rs.ClientErrorException; | ||
import jakarta.ws.rs.ForbiddenException; | ||
import jakarta.ws.rs.container.ContainerRequestContext; | ||
|
||
import javax.annotation.Nonnull; | ||
import java.lang.annotation.Annotation; | ||
|
||
/** | ||
* The function that will verify that an incoming request is authorized or not<br> | ||
* <br> | ||
* This function should be used within the {@link AuthorizationSecurityFeature}. | ||
* @param <A> The annotation type used to identify that the verifier should be applied on a resource method/class | ||
*/ | ||
@FunctionalInterface | ||
public interface AuthorizationVerifier<A extends Annotation> { | ||
/** | ||
* Verify that an incoming request is authorized | ||
* | ||
* @param authorizationAnnotation The annotation type used to identify that the verifier should be applied on a resource method/class | ||
* @param requestContext The request context that should be used to verify the authorization | ||
* @throws ForbiddenException If the user cannot access the resource | ||
* @throws ClientErrorException If there is another error about the request authorization | ||
*/ | ||
void verifyAuthentication(@Nonnull A authorizationAnnotation, @Nonnull ContainerRequestContext requestContext) throws ForbiddenException, ClientErrorException; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
plume-web-jersey/src/main/java/com/coreoz/plume/jersey/security/basic/BasicRestricted.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.coreoz.plume.jersey.security.basic; | ||
|
||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.Target; | ||
|
||
import static java.lang.annotation.ElementType.METHOD; | ||
import static java.lang.annotation.ElementType.TYPE; | ||
import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
||
/** | ||
* An annotation that can be used to set up an {@link com.coreoz.plume.jersey.security.AuthorizationSecurityFeature} | ||
* with a {@link BasicAuthenticator} | ||
*/ | ||
@Documented | ||
@Retention(RUNTIME) | ||
@Target({TYPE, METHOD}) | ||
public @interface BasicRestricted { | ||
} |
58 changes: 58 additions & 0 deletions
58
...web-jersey/src/main/java/com/coreoz/plume/jersey/security/bearer/BearerAuthenticator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.coreoz.plume.jersey.security.bearer; | ||
|
||
import com.coreoz.plume.jersey.security.AuthorizationVerifier; | ||
import com.coreoz.plume.jersey.security.basic.BasicRestricted; | ||
import com.google.common.net.HttpHeaders; | ||
import jakarta.ws.rs.ForbiddenException; | ||
import jakarta.ws.rs.container.ContainerRequestContext; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.security.MessageDigest; | ||
|
||
/** | ||
* Provide a generic resource authenticator for API-key based resource authorization | ||
*/ | ||
@Slf4j | ||
public class BearerAuthenticator { | ||
private static final String BEARER_PREFIX = "Bearer "; | ||
|
||
private final String authenticationSecretHeader; | ||
|
||
public BearerAuthenticator(String bearerToken) { | ||
this.authenticationSecretHeader = BEARER_PREFIX + bearerToken; | ||
} | ||
|
||
/** | ||
* Provide an {@link AuthorizationVerifier} from the bearer authenticator to provide annotation based request authorization using {@link com.coreoz.plume.jersey.security.AuthorizationSecurityFeature} | ||
* @param annotation The annotation that will be used to identify resources that must be authorized. For example {@link BasicRestricted} can be used if it is not already used in the project for another authorization system | ||
* @return The basic authenticator corresponding {@link AuthorizationVerifier} | ||
* @param <A> The annotation type | ||
*/ | ||
public <A extends Annotation> AuthorizationVerifier<A> toAuthorizationVerifier(A annotation) { | ||
return (authorizationAnnotation, requestContext) -> verifyAuthentication(requestContext); | ||
} | ||
|
||
public void verifyAuthentication(@NotNull ContainerRequestContext requestContext) { | ||
String bearer = parseBearerHeader(requestContext.getHeaderString(HttpHeaders.AUTHORIZATION)); | ||
|
||
if (bearer == null || !MessageDigest.isEqual(authenticationSecretHeader.getBytes(), bearer.getBytes())) { | ||
throw new ForbiddenException("Invalid bearer header: " + bearer); | ||
} | ||
} | ||
|
||
public static String parseBearerHeader(String authorizationHeader) { | ||
if(authorizationHeader == null) { | ||
logger.debug("Missing Authorization header"); | ||
return null; | ||
} | ||
|
||
if(!authorizationHeader.startsWith(BEARER_PREFIX)) { | ||
logger.debug("Bearer Authorization header must starts with '{}'", BEARER_PREFIX); | ||
return null; | ||
} | ||
|
||
return authorizationHeader; | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
plume-web-jersey/src/main/java/com/coreoz/plume/jersey/security/bearer/BearerRestricted.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.coreoz.plume.jersey.security.bearer; | ||
|
||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.Target; | ||
|
||
import static java.lang.annotation.ElementType.METHOD; | ||
import static java.lang.annotation.ElementType.TYPE; | ||
import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
||
/** | ||
* An annotation that can be used to set up an {@link com.coreoz.plume.jersey.security.AuthorizationSecurityFeature} | ||
* with a {@link BearerAuthenticator} | ||
*/ | ||
@Documented | ||
@Retention(RUNTIME) | ||
@Target({TYPE, METHOD}) | ||
public @interface BearerRestricted { | ||
} |
Oops, something went wrong.