-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
547 additions
and
15 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
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,54 @@ | ||
<mxfile host="app.diagrams.net" modified="2023-09-06T02:17:57.678Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36" etag="kJ8t7kbPnoaa44JijZXw" version="21.7.2" type="device"> | ||
<diagram name="Page-1" id="qUw_CK1pjzlE3Cx89_ZL"> | ||
<mxGraphModel dx="1306" dy="1930" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0"> | ||
<root> | ||
<mxCell id="0" /> | ||
<mxCell id="1" parent="0" /> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#00AA00;fontStyle=1" parent="1" source="iVyjQRI55B28_YTLfMTB-1" target="iVyjQRI55B28_YTLfMTB-14" edge="1"> | ||
<mxGeometry relative="1" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-1" value="<font face="Ubuntu" color="#ffffff"><span style="font-size: 20px;"><span>OAuth<br>Client<br></span></span></font>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;strokeColor=none;fillColor=#F26C9D;fontStyle=1" parent="1" vertex="1"> | ||
<mxGeometry x="320" y="360" width="80" height="80" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-4" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;strokeWidth=3;strokeColor=#F8C300;fontStyle=1" parent="1" vertex="1"> | ||
<mxGeometry x="620" y="350" width="320" height="190" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-5" value="<font face="Ubuntu" color="#ffffff"><span style="font-size: 20px;"><span>Auth<br>Server<br></span></span></font>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;strokeColor=none;fillColor=#E55FFB;fontStyle=1" parent="1" vertex="1"> | ||
<mxGeometry x="740" y="-100" width="80" height="80" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-8" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=1;entryDx=0;entryDy=0;strokeWidth=3;startArrow=classic;startFill=1;strokeColor=#00aa00;endArrow=none;endFill=0;fontStyle=1" parent="1" edge="1" target="iVyjQRI55B28_YTLfMTB-5" source="iVyjQRI55B28_YTLfMTB-1"> | ||
<mxGeometry relative="1" as="geometry"> | ||
<mxPoint x="391.76" y="377.5999999999999" as="sourcePoint" /> | ||
<mxPoint x="761.76" y="-23.839999999999918" as="targetPoint" /> | ||
</mxGeometry> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-9" value="access_token (JWT)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;rotation=313;fontFamily=Ubuntu;fontStyle=1;fontSize=20;" parent="1" vertex="1"> | ||
<mxGeometry x="325" y="165" width="540" height="30" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#00AA00;strokeWidth=3;fontStyle=1" parent="1" source="iVyjQRI55B28_YTLfMTB-14" target="iVyjQRI55B28_YTLfMTB-15" edge="1"> | ||
<mxGeometry relative="1" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-14" value="<font style="font-size: 20px;" face="Ubuntu"><span>ContainerRequestFilter</span></font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#F8C300;strokeColor=#F8C300;fontStyle=1" parent="1" vertex="1"> | ||
<mxGeometry x="660" y="370" width="240" height="60" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-15" value="<font style="font-size: 20px;" face="Ubuntu"><span>Endpoints</span></font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#F8C300;strokeColor=#F8C300;fontStyle=1" parent="1" vertex="1"> | ||
<mxGeometry x="660" y="470" width="240" height="60" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-18" value="access_token" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;rotation=0;fontFamily=Ubuntu;fontStyle=1;fontSize=20;" parent="1" vertex="1"> | ||
<mxGeometry x="250" y="410" width="540" height="30" as="geometry" /> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;curved=1;strokeWidth=3;strokeColor=#00aa00;fontStyle=1" parent="1" source="iVyjQRI55B28_YTLfMTB-14" target="iVyjQRI55B28_YTLfMTB-14" edge="1"> | ||
<mxGeometry relative="1" as="geometry"> | ||
<Array as="points"> | ||
<mxPoint x="720" y="300" /> | ||
<mxPoint x="840" y="300" /> | ||
</Array> | ||
</mxGeometry> | ||
</mxCell> | ||
<mxCell id="iVyjQRI55B28_YTLfMTB-20" value="Validate access_token" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;rotation=0;fontFamily=Ubuntu;fontStyle=1;fontSize=20;" parent="1" vertex="1"> | ||
<mxGeometry x="510" y="260" width="540" height="30" as="geometry" /> | ||
</mxCell> | ||
</root> | ||
</mxGraphModel> | ||
</diagram> | ||
</mxfile> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,219 @@ | ||
--- | ||
sidebar_position: 5 | ||
title: Security | ||
--- | ||
|
||
Core Concepts | ||
------------- | ||
|
||
API authentication is largely a solved problem and generally outside the scope of Jersey WS Template. | ||
|
||
Jersey WS Template does, however, adds a layer of security on its own by validating [OAuth 2 access token] on all | ||
incoming request. Each API request requires a standard **"Authentication": "Bearer <access_token>"** token header: | ||
|
||
![Error loading oauth2-filtering.png](./img/oauth2-filtering.png) | ||
|
||
To define a token validator, simply implement the **AccessTokenValidator** like so: | ||
|
||
```java | ||
public class JwtTokenValidator implements AccessTokenValidator { | ||
|
||
@Override | ||
public boolean validate(@NotNull final String accessToken) { | ||
// use https://github.com/auth0/java-jwt as recommended by https://jwt.io/ | ||
return true; | ||
} | ||
} | ||
``` | ||
|
||
and bind the validator, in [BinderFactory], using | ||
|
||
```java | ||
bind(new JwtTokenValidator()).to(AccessTokenValidator.class); | ||
``` | ||
|
||
For example, to validate a ES384 token using _AccessTokenValidator_, we may implement a _ES384JwtTokenValidator_ | ||
validator below: | ||
|
||
:::note | ||
|
||
Note that the implementation below depends on 2 JWT libraries: | ||
|
||
```xml | ||
<dependency> | ||
<groupId>com.auth0</groupId> | ||
<artifactId>java-jwt</artifactId> | ||
<version>4.4.0</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.auth0</groupId> | ||
<artifactId>jwks-rsa</artifactId> | ||
<version>0.22.1</version> | ||
</dependency> | ||
``` | ||
|
||
::: | ||
|
||
```java | ||
import com.auth0.jwk.InvalidPublicKeyException; | ||
import com.auth0.jwk.Jwk; | ||
import com.auth0.jwk.JwkException; | ||
import com.auth0.jwk.JwkProvider; | ||
import com.auth0.jwk.JwkProviderBuilder; | ||
import com.auth0.jwt.JWT; | ||
import com.auth0.jwt.algorithms.Algorithm; | ||
import com.auth0.jwt.interfaces.Verification; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import jakarta.validation.constraints.NotNull; | ||
import net.jcip.annotations.Immutable; | ||
import net.jcip.annotations.ThreadSafe; | ||
|
||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.security.interfaces.ECPublicKey; | ||
import java.util.Objects; | ||
|
||
/** | ||
* {@link ES384JwtTokenValidator} validates an <a href="https://jwt.io/">JWT</a> token in ES384 JWS form. | ||
* | ||
* It validates the access token by verifying the integrity of the header and payload to ensure that they have not been | ||
* altered by using token's signature section. | ||
*/ | ||
@Immutable | ||
@ThreadSafe | ||
public class ES384JwtTokenValidator implements AccessTokenValidator { | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(ES384JwtTokenValidator.class); | ||
|
||
private final String jwksUrl; | ||
|
||
/** | ||
* Constructs a new {@link ES384JwtTokenValidator} that verfies the signed JWT token with the JWK keys stored at a | ||
* specified URL. | ||
* | ||
* @param jwksUrl The provided JWKS URL that, on GET, returns a json object such as | ||
* <pre> | ||
* {@code | ||
* { | ||
* "keys": [ | ||
* { | ||
* "kty": "EC", | ||
* "use": "sig", | ||
* "kid": "eTERknhur9q8gisdaf_dfrqrgdfsg", | ||
* "alg": "ES384", | ||
* "crv": "P-384", | ||
* "x": "sdfrgHGYF...", | ||
* "y": "sdfuUIG&8..." | ||
* } | ||
* ] | ||
* } | ||
* } | ||
* </pre> | ||
* | ||
* @throws NullPointerException if {@code jwksUrl} is {@code null} | ||
*/ | ||
public ES384JwtTokenValidator(final String jwksUrl) { | ||
this.jwksUrl = Objects.requireNonNull(jwksUrl); | ||
} | ||
|
||
@Override | ||
public boolean validate(@NotNull final String accessToken) { | ||
final JwkProvider jwkProvider = getJwkProvider(getJwksUrl()); | ||
final Jwk jwk = getJwk(jwkProvider, accessToken); | ||
final Algorithm algorithm = getVerificationAlgorithm(jwk); | ||
final Verification verifier = JWT.require(algorithm); | ||
|
||
verifier.build().verify(accessToken); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Returns a one-time built instance of {@link JwkProvider} to save performance. | ||
* | ||
* @param jwksUrl The JWKS URL that, on GET, returns a json object such as | ||
* <pre> | ||
* {@code | ||
* { | ||
* "keys": [ | ||
* { | ||
* "kty": "EC", | ||
* "use": "sig", | ||
* "kid": "eTERknhur9q8gisdaf_dfrqrgdfsg", | ||
* "alg": "ES384", | ||
* "crv": "P-384", | ||
* "x": "sdfrgHGYF...", | ||
* "y": "sdfuUIG&8..." | ||
* } | ||
* ] | ||
* } | ||
* } | ||
* </pre> | ||
* | ||
* @return a new instance | ||
* | ||
* @throws IllegalStateException if the {@code jwksUrl} is an invalid URL | ||
*/ | ||
@NotNull | ||
private static JwkProvider getJwkProvider(@NotNull final String jwksUrl) { | ||
try { | ||
return new JwkProviderBuilder(new URL(jwksUrl)).build(); | ||
} catch (final MalformedURLException exception) { | ||
final String message = String.format("Invalid JWKS URL: '%s'", jwksUrl); | ||
LOG.error(message, exception); | ||
throw new IllegalStateException(message, exception); | ||
} | ||
} | ||
|
||
/** | ||
* Returns a JWK key set that will be used to verify a given JWT access token. | ||
* | ||
* @param jwkProvider An object that contains the JWK; cannot be {@code null} | ||
* @param accessToken A "key" that indexes the JWK from the {@code jwkProvider}; cannot be {@code null} | ||
* | ||
* @return a JWK key set | ||
* | ||
* @throws IllegalStateException if no JWK set associated with the provided token is found from {@code jwkProvider} | ||
*/ | ||
private static Jwk getJwk(@NotNull final JwkProvider jwkProvider, @NotNull final String accessToken) { | ||
try { | ||
return jwkProvider.get(JWT.decode(accessToken).getKeyId()); | ||
} catch (final JwkException exception) { | ||
final String message = "The key ID in the access token does not match any JWK"; | ||
LOG.error(message, exception); | ||
throw new IllegalStateException(message, exception); | ||
} | ||
} | ||
|
||
/** | ||
* Returns the verifying algorithm associated with a specified JWK. | ||
* | ||
* @param jwk The JWK contains the public key for decrypting the token signature, cannot be {@code null} | ||
* | ||
* @return a decrypting algorithm with public key and without private key enclosed | ||
* | ||
* @throws IllegalStateException if public key cannot be retrieved from the JWK | ||
*/ | ||
private static Algorithm getVerificationAlgorithm(@NotNull final Jwk jwk) { | ||
try { | ||
return Algorithm.ECDSA384((ECPublicKey) jwk.getPublicKey(), null); | ||
} catch (final InvalidPublicKeyException exception) { | ||
final String message = "The public key cannot be build from JWK"; | ||
LOG.error(message, exception); | ||
throw new IllegalStateException(message, exception); | ||
} | ||
} | ||
|
||
@NotNull | ||
private String getJwksUrl() { | ||
return jwksUrl; | ||
} | ||
} | ||
``` | ||
|
||
[BinderFactory]: https://github.com/QubitPi/jersey-ws-template/blob/master/src/main/java/com/qubitpi/ws/jersey/template/application/BinderFactory.java | ||
|
||
[OAuth 2 access token]: https://www.oauth.com/oauth2-servers/access-tokens/ |
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
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
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
Oops, something went wrong.