Skip to content
Permalink
Browse files
ensure to support empty header prefix + don't send a response if alre…
…ady commited + adding a readme
  • Loading branch information
rmannibucau committed Apr 22, 2018
1 parent e0d86cb commit c40f0e931fb234f7a3b5d6c2fb2f75c55e3d32b8
Showing 2 changed files with 123 additions and 11 deletions.
@@ -0,0 +1,106 @@
= Geronimo Microprofile JWT Auth Implementation

== Artifacts

=== API

IMPORTANT: you can also use the eclipse bundle.

[source,xml]
----
<parent>
<groupId>org.apache.geronimo</groupId>
<artifactId>geronimo-microprofile-jwt-auth-spec</artifactId>
<version>${jwtauth.version}</version>
</parent>
----

=== Implementation

IMPORTANT: you can also use the eclipse bundle.

[source,xml]
----
<parent>
<groupId>org.apache.geronimo</groupId>
<artifactId>geronimo-jwt-auth-impl</artifactId>
<version>${jwtauth.version}</version>
</parent>
----

== Configuration

IMPORTANT: configuration uses Microprofile Configuration if available
and if not system properties and `META-INF/geronimo/microprofile/jwt-auth.properties`.

|===
| Name | Description | Default
|geronimo.jwt-auth.jwt.header.kid.default|The default `kid` if specified|-
|geronimo.jwt-auth.jwt.header.alg.default|The default `alg` if specified|RS256
|geronimo.jwt-auth.jwt.header.typ.default|The default `typ` if specified|JWT
|geronimo.jwt-auth.jwt.header.typ.validate|Should the typ value be validated (only `JWT` is supported)|true
|geronimo.jwt-auth.filter.mapping.default|When the JAX-RS `Application` doesn't have an `@ApplicationPath` and no servlet registration are found for the application this defines the path to use to handle JWT|/*
|geronimo.jwt-auth.filter.publicUrls|List of URL to ignore|-
|geronimo.jwt-auth.kids.key.mapping|The mapping between the kid and the public key to use|-
|geronimo.jwt-auth.kids.issuer.mapping|The mapping of the issuer expected per kid|-
|geronimo.jwt-auth.issuer.default|The default issuer to use when no mapping is found|-
|geronimo.jwt-auth.header.name|The header name to read the JWT|Authorization
|geronimo.jwt-auth.header.prefix|The header prefix to use|bearer
|geronimo.jwt-auth.header.alg.supported|List of accepted `alg` value|RS256, accepted values: [RS\|HS][256\|384\|512]
|geronimo.jwt-auth.exp.required|Should the validation fail if `exp` is missing|true
|geronimo.jwt-auth.iat.required|Should the validation fail if `iat` is missing|true
|geronimo.jwt-auth.date.tolerance|The tolerance in ms for `exp` and `iat`|60000
|geronimo.jwt-auth.jca.provider|The JCA provider (java security)|- (built-in one)
|geronimo.jwt-auth.groups.mapping|The mapping for the groups|-
|===

Here is a sample `META-INF/geronimo/microprofile/jwt-auth.properties`
(assuming you don't use Microprofile config) using some of these entries:

[source,properties]
----
# for rolesallowed accept group1 and Group1MappedRole for the requirement Group1MappedRole
geronimo.jwt-auth.groups.mapping = \
Group1MappedRole = group1, Group1MappedRole
# the global expected issuer
geronimo.jwt-auth.issuer.default = https://server.example.com
# mapping kid1 to the embedded resource /publicKey.pem
# can be an absolute path too
geronimo.jwt-auth.kids.key.mapping = \
kid1 = /publicKey.pem
----

== OpenWebBeans

For this specification to work on OpenWebBeans you need to configure a few key (until 2.0.4).
For that register a `META-INF/openwebbeans/openwebbeans.properties`:

[source,properties]
----
configuration.ordinal=1001
# OWB default is wrong and we need that
org.apache.webbeans.container.InjectionResolver.fastMatching = false
# only if you use Principal injection instead of JsonWebToken injection
# since 2.0.5
org.apache.webbeans.component.PrincipalBean.proxy = false
org.apache.webbeans.spi.SecurityService = org.superbiz.MySecurityService
----

And here is a sample security service implementation:

[source,java]
----
public class MySecurityService extends SimpleSecurityService {
@Override
public Principal getCurrentPrincipal() {
return ((Supplier<Principal>) CDI.current().select(HttpServletRequest.class).get()
.getAttribute(Principal.class.getName() + ".supplier")).get();
}
}
----

IMPORTANT: in any case it is not recommanded to use CDI `Principal` API, always prefer `JsonWebToken` one.
@@ -20,6 +20,7 @@

import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Stream;

import javax.enterprise.inject.spi.CDI;
@@ -52,7 +53,9 @@ public void init(final FilterConfig filterConfig) throws ServletException {

final GeronimoJwtAuthConfig config = current.select(GeronimoJwtAuthConfig.class).get();
headerName = config.read("header.name", "Authorization");
prefix = config.read("header.prefix", "bearer") + " ";
prefix = Optional.of(config.read("header.prefix", "bearer"))
.filter(s -> !s.isEmpty()).map(s -> s + " ")
.orElse("");
publicUrls = Stream.of(config.read("filter.publicUrls", "").split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
@@ -79,17 +82,20 @@ public void doFilter(final ServletRequest request, final ServletResponse respons
final JwtRequest req = new JwtRequest(service, headerName, prefix, httpServletRequest);
extension.execute(req, () -> chain.doFilter(req, response));
} catch (final Exception e) { // when not used with JAX-RS but directly Servlet
Throwable current = e;
while (current != null) {
if (JwtException.class.isInstance(current)) {
final JwtException ex = JwtException.class.cast(current);
HttpServletResponse.class.cast(response).sendError(ex.getStatus(), ex.getMessage());
return;
final HttpServletResponse httpServletResponse = HttpServletResponse.class.cast(response);
if (!httpServletResponse.isCommitted()) {
Throwable current = e;
while (current != null) {
if (JwtException.class.isInstance(current)) {
final JwtException ex = JwtException.class.cast(current);
httpServletResponse.sendError(ex.getStatus(), ex.getMessage());
return;
}
if (current == current.getCause()) {
break;
}
current = current.getCause();
}
if (current == current.getCause()) {
break;
}
current = current.getCause();
}
throw e;
}

0 comments on commit c40f0e9

Please sign in to comment.