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
>>>
>>
>
>
>