Hello Matthias.

Sorry but I don't have a public repo with an example.
I had some issues in the past when using more recent versions of Aries JAX
RS due to conflicts with the CXF version I was using in another service, so
I kept using version 1.0.4.
In my latest projects, where I needed more customization, I ditched Aries
JAX RS and used CXF directly. Needed more code and had to use
JAXRSServerFactoryBean to create the server. Also lost the possibility to
use service injection :-(

The code for JWT is mostly taken from jose4j examples:

@Component(service = JwtService.class, //
>         immediate = true, //
>         scope = ServiceScope.SINGLETON)
> public class JwtService {
>
>     private static final Logger LOGGER =
> LoggerFactory.getLogger(JwtService.class);
>
>     // time when the token will expire (minutes)
>     private static final float TOKEN_TTL = 30;
>
>     public static final String ISSUER = "ACME";
>
>     public static final String AUDIENCE = "universe";
>
>     public static final String CLAIM_NAME = "name";
>
>     public static final String CLAIM_PERMISSIONS = "permissions";
>
>     private RsaJsonWebKey rsaJsonWebKey;
>
>     private JwtConsumer jwtConsumer;
>
>     @Activate
>     public void activate() throws JoseException {
>
>         initWebKey();
>         initJWTconsumer();
>     }
>
>     private void initWebKey() throws JoseException {
>
>         rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
>         rsaJsonWebKey.setKeyId("keyId");
>
>     }
>
>     private void initJWTconsumer() {
>
>         jwtConsumer = new JwtConsumerBuilder()
>                 .setRequireExpirationTime() // the JWT must have an
> expiration
>                                             // time
>                 .setAllowedClockSkewInSeconds(30) // allow some leeway in
>                                                   // validating time based
>                                                   // claims to account for
> clock
>                                                   // skew
>                 .setRequireSubject() // the JWT must have a subject claim
>                 .setExpectedIssuer(ISSUER) // whom the JWT needs to have
> been
>                                            // issued by
>                 .setExpectedAudience(AUDIENCE) // to whom the JWT is
> intended
>                                                // for
>                 .setVerificationKey(rsaJsonWebKey.getKey()) // verify the
>                                                             // signature
> with
>                                                             // the public
> key
>                 .setJwsAlgorithmConstraints( // only allow the expected
>                                              // signature algorithm(s) in
> the
>                                              // given context
>                         new AlgorithmConstraints(ConstraintType.WHITELIST,
> // which
>
>  // is
>
>  // only
>
>  // RS256
>
>  // here
>                                 AlgorithmIdentifiers.RSA_USING_SHA256))
>                 .build(); // create the JwtConsumer instance
>
>     }
>
>     public String createToken(SubjectDetails subjectDetails) throws
> AuthenticationException {
>
>         // Create the Claims, which will be the content of the JWT
>         JwtClaims claims = new JwtClaims();
>         claims.setIssuer(ISSUER); // who creates the token and signs it
>         claims.setAudience(AUDIENCE); // to whom the token is intended to
> be
>                                       // sent
>         claims.setExpirationTimeMinutesInTheFuture(TOKEN_TTL);
>         claims.setGeneratedJwtId(); // a unique identifier for the token
>         claims.setIssuedAtToNow(); // when the token was issued/created
> (now)
>         claims.setNotBeforeMinutesInThePast(2);
>         claims.setSubject(subjectDetails.getId());
>         claims.setStringClaim(CLAIM_NAME, subjectDetails.getName());
>         claims.setStringListClaim(CLAIM_PERMISSIONS,
> subjectDetails.getPermissionsList());
>
>         // A JWT is a JWS and/or a JWE with JSON claims as the payload.
>         // In this example it is a JWS so we create a JsonWebSignature
> object.
>         JsonWebSignature jws = new JsonWebSignature();
>
>         // The payload of the JWS is JSON content of the JWT Claims
>         jws.setPayload(claims.toJson());
>
>         // The JWT is signed using the private key
>         jws.setKey(rsaJsonWebKey.getPrivateKey());
>
>         jws.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId());
>
>         // Set the signature algorithm on the JWT/JWS that will integrity
>         // protect the claims
>         jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
>
>         try {
>             return jws.getCompactSerialization();
>         }
>         catch (JoseException ex) {
>             throw new AuthenticationException("Token creation failed", ex);
>         }
>     }
>
>     public SubjectDetails validateToken(String token) throws
> AuthenticationException {
>
>         try {
>             JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
>             String subjectName = jwtClaims.getStringClaimValue(CLAIM_NAME);
>             List<String> permissions =
> jwtClaims.getStringListClaimValue(CLAIM_PERMISSIONS);
>             Set<Permission> permissionsSet =
> Permission.fromList(permissions);
>             permissionsSet.add(Permission.AUTHENTICATED); // Implicit
> permission
>             return new SubjectDetails(jwtClaims.getSubject(), subjectName,
> permissionsSet);
>         }
>         catch (InvalidJwtException ex) {
>             logException("InvalidJwtException", ex);
>             throw new AuthenticationException(ex.getMessage());
>
>         }
>         catch (MalformedClaimException ex) {
>             logException("MalformedClaimException", ex);
>             throw new AuthenticationException(ex.getMessage());
>         }
>
>     }
>
>     protected static void logException(String msg, Exception ex) {
>
>         LOGGER.debug(msg, ex);
>     }
> }
>



João Assunção

Email: [email protected]
Mobile: +351 916968984
Phone: +351 211933149
Web: www.exploitsys.com




On Mon, Jun 27, 2022 at 9:33 AM Matthias Leinweber <
[email protected]> wrote:

> Hello,
>
> I am using karaf 4.3.7 and yes I saw the examples in karaf. The version
> used is 1.0.6 which is pretty old. I tried to update to 1.0.10 but i dont
> see what is going wrong. There is the Application, Servlet, and Whiteboard.
> But the servlet is not accessible.
> With version 2.0.1 is cxf-core required. That would also be fine.
> Unfortunately it does not work without a url prefix which would require a
> change on the client side.
>
> @ João thank you very much for your help. Do you also have a repo link to
> a working example; where does the JWTService come from?
>
> Thank
> br,
> Matthias
>
> Am Do., 23. Juni 2022 um 11:02 Uhr schrieb João Assunção <
> [email protected]>:
>
>> Hello Matthias,
>>
>> Regarding authentication I normally use a ContainerRequestFilter.
>> Follows an example of a filter where authentication is done using a JWT
>> token.
>> It uses JwtService that is responsible for creating and validating
>> tokens. It uses jose4j.
>>
>>
>> @Secured
>>> @Provider
>>> @Priority(Priorities.AUTHENTICATION)
>>> @Component(scope = ServiceScope.PROTOTYPE, //
>>>         service = { ContainerRequestFilter.class }, //
>>>         property = {
>>>                 JaxrsWhiteboardConstants.JAX_RS_EXTENSION + "=" + true,
>>> //
>>>                 JaxrsWhiteboardConstants.JAX_RS_NAME + "=" +
>>> "TokenAuthenticationFilter", //
>>>                 Constants.JAX_RS_APPLICATION_SELECT, //
>>>         })
>>> public class TokenAuthenticationFilter implements ContainerRequestFilter
>>> {
>>>
>>>     private static final Logger LOGGER =
>>> LoggerFactory.getLogger(TokenAuthenticationFilter.class);
>>>
>>>     private static final String AUTHENTICATION_SCHEME = "Bearer";
>>>
>>>     private static final String AUTHENTICATION_SCHEME_PREFIX =
>>> AUTHENTICATION_SCHEME + " ";
>>>
>>>     @Reference(cardinality = ReferenceCardinality.MANDATORY)
>>>     JwtService jwtService;
>>>
>>>     @Context
>>>     private ResourceInfo resourceInfo;
>>>
>>>     @Override
>>>     public void filter(ContainerRequestContext requestContext) throws
>>> IOException {
>>>
>>>         // Get the Authorization header from the request
>>>         String authorizationHeader =
>>> requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
>>>         LOGGER.debug("authorizationHeader {}", authorizationHeader);
>>>         // Validate the Authorization header
>>>         if (!isTokenBasedAuthentication(authorizationHeader)) {
>>>             LOGGER.info("Missing auth token. Aborting");
>>>             abortWithUnauthorized(requestContext);
>>>             return;
>>>         }
>>>         // Extract the token from the Authorization header
>>>         final String token =
>>> authorizationHeader.substring(AUTHENTICATION_SCHEME_PREFIX.length());
>>>         try {
>>>             // Validate the token. An exception will be thrown if invalid
>>>             SubjectDetails subjectDetails =
>>> jwtService.validateToken(token);
>>>             LOGGER.debug("Token for subject {} accepted",
>>> subjectDetails.getId());
>>>             initSecurityContext(requestContext, subjectDetails);
>>>             Permission requiredPermission =
>>> getPermissionForResource(resourceInfo);
>>>             if (!subjectDetails.hasPermission(requiredPermission)) {
>>>                 LOGGER.debug("subject {} lack permission {}",
>>> subjectDetails.getId(), requiredPermission);
>>>                 abortWithForbidden(requestContext);
>>>             }
>>>         }
>>>         catch (AuthenticationException e) {
>>>             LOGGER.trace("Ignored", e);
>>>             LOGGER.info("Token validation failed. Aborting");
>>>             abortWithUnauthorized(requestContext);
>>>         }
>>>     }
>>>
>>>     /**
>>>      * @param requestContext
>>>      * @param subjectDetails
>>>      */
>>>     private void initSecurityContext(ContainerRequestContext
>>> requestContext, SubjectDetails subjectDetails) {
>>>
>>>         final SecurityContext currentSecurityContext =
>>> requestContext.getSecurityContext();
>>>         boolean secure = currentSecurityContext.isSecure();
>>>         requestContext.setSecurityContext(new
>>> InternalSecurityContext(subjectDetails, secure));
>>>
>>>     }
>>>
>>>     private boolean isTokenBasedAuthentication(String
>>> authorizationHeader) {
>>>
>>>         // Check if the Authorization header is valid
>>>         // It must not be null and must be prefixed with "Bearer" plus a
>>>         // whitespace
>>>         // The authentication scheme comparison must be case-insensitive
>>>         return StringUtils.startsWithIgnoreCase(authorizationHeader,
>>> AUTHENTICATION_SCHEME_PREFIX);
>>>     }
>>>
>>>     private void abortWithUnauthorized(ContainerRequestContext
>>> requestContext) {
>>>
>>>         // Abort the filter chain with a 401 status code response
>>>         // The WWW-Authenticate header is sent along with the response
>>>         requestContext.abortWith(
>>>                 Response.status(Response.Status.UNAUTHORIZED)
>>>                         .header(HttpHeaders.WWW_AUTHENTICATE,
>>>                                 AUTHENTICATION_SCHEME + " realm=\"" +
>>> Constants.APP_NAME + "\"")
>>>                         .build());
>>>     }
>>>
>>>     private void abortWithForbidden(ContainerRequestContext
>>> requestContext) {
>>>
>>>
>>> requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
>>>     }
>>>
>>>     static Permission getPermissionForResource(ResourceInfo
>>> resourceInfo) {
>>>
>>>         Permission permission =
>>> extractPermission(resourceInfo.getResourceMethod());
>>>         if (permission != null) {
>>>             return permission;
>>>         }
>>>         permission = extractPermission(resourceInfo.getResourceClass());
>>>         return permission;
>>>
>>>     }
>>>
>>>     // Extract Permission from the annotated element
>>>     static Permission extractPermission(AnnotatedElement
>>> annotatedElement) {
>>>
>>>         if (annotatedElement == null) {
>>>             return null;
>>>         }
>>>         Secured secured = annotatedElement.getAnnotation(Secured.class);
>>>         return secured == null ? null : secured.value();
>>>     }
>>>
>>>     private static class InternalSecurityContext implements
>>> SecurityContext {
>>>
>>>         private final boolean secure;
>>>
>>>         private final SubjectDetails subjectDetails;
>>>
>>>         InternalSecurityContext(SubjectDetails subjectDetails, boolean
>>> secure) {
>>>
>>>             this.subjectDetails = subjectDetails;
>>>             this.secure = secure;
>>>         }
>>>
>>>         @Override
>>>         public Principal getUserPrincipal() {
>>>
>>>             return subjectDetails;
>>>         }
>>>
>>>         @Override
>>>         public boolean isUserInRole(String role) {
>>>
>>>             // We are not using role base authorization
>>>             return true;
>>>         }
>>>
>>>         @Override
>>>         public boolean isSecure() {
>>>
>>>             return secure;
>>>         }
>>>
>>>         @Override
>>>         public String getAuthenticationScheme() {
>>>
>>>             return AUTHENTICATION_SCHEME;
>>>         }
>>>
>>>     }
>>>
>>>     /**
>>>      * For test purposes
>>>      *
>>>      * @param jwtService
>>>      * @return
>>>      */
>>>     public static TokenAuthenticationFilter
>>> createTestInstance(JwtService jwtService) {
>>>
>>>         TokenAuthenticationFilter filter = new
>>> TokenAuthenticationFilter();
>>>         filter.jwtService = jwtService;
>>>         return filter;
>>>     }
>>> }
>>>
>>
>> The Secured annotation is used to mark the methods that need to be
>> protected and the required permission.
>>
>> @NameBinding
>>> @Retention(RetentionPolicy.RUNTIME)
>>> @Target({ ElementType.TYPE, ElementType.METHOD })
>>> public @interface Secured {
>>>
>>>     Permission value() default Permission.AUTHENTICATED;
>>> }
>>>
>>
>> João Assunção
>>
>> Email: [email protected]
>> Mobile: +351 916968984
>> Phone: +351 211933149
>> Web: www.exploitsys.com
>>
>>
>>
>>
>> On Wed, Jun 22, 2022 at 8:54 PM Matthias Leinweber <
>> [email protected]> wrote:
>>
>>> Hello Karaf user,
>>>
>>> i try to get aries-jax-rs-whiteboard in a version > 1.0.6 running
>>> without success. 2.0.1 with installed cxf is working fine. But in
>>> 1.0.7-1.0.10. http:list does not show a servlet and my jaxrs whiteboard
>>> resources are not working.
>>>
>>> Any hints which component needs to be installed? Reading BNDrun +
>>> Components DSL is pretty confusing.
>>>
>>> Btw, does anyone have a good example for authentication? Shiro seems to
>>> be a bit overkill.
>>>
>>> br,
>>> Matthias
>>>
>>
>
>
>

Reply via email to