/*
* Copyright 2016 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.apiman.plugins.jwt;
import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.policies.AbstractMappedPolicy;
import io.apiman.gateway.engine.policy.IPolicyChain;
import io.apiman.gateway.engine.policy.IPolicyContext;
import io.apiman.plugins.jwt.beans.ForwardAuthInfo;
import io.apiman.plugins.jwt.beans.JWTPolicyBean;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.InvalidClaimException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.PrematureJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.lang.Objects;
import java.util.Map;
import java.util.Optional;
/**
* Generic JWT/S Policy.
*
* @author Marc Savy {@literal <msavy@redhat.com>}
*/
public class JWTPolicy extends AbstractMappedPolicy<JWTPolicyBean> {
private static final String AUTHORIZATION_KEY = "Authorization"; //$NON-NLS-1$
private static final String ACCESS_TOKEN_QUERY_KEY = "access_token"; //$NON-NLS-1$
private static final String BEARER = "bearer "; //$NON-NLS-1$
private static final PolicyFailureFactory FAILURE_FACTORY = PolicyFailureFactory.getInstance();
@Override
protected Class<JWTPolicyBean> getConfigurationClass() {
return JWTPolicyBean.class;
}
@Override
protected void doApply(ApiRequest request, IPolicyContext context, JWTPolicyBean config, IPolicyChain<ApiRequest> chain) {
String jwt = Optional.ofNullable(request.getHeaders().get(AUTHORIZATION_KEY))
// If seems to be bearer token
.filter(e -> e.toLowerCase().startsWith(BEARER))
// Get out token value
.map(e -> e.substring(BEARER.length(), e.length()))
// Otherwise attempt to get from the access_token query param
.orElse(request.getQueryParams().get(ACCESS_TOKEN_QUERY_KEY));
// If transport security required and is not secure.
if (config.getRequireTransportSecurity() && !request.isTransportSecure()) {
chain.doFailure(FAILURE_FACTORY.noTransportSecurity(context));
return;
}
// If JWT required and none provided
if (config.getRequireJWT() && jwt == null) {
chain.doFailure(FAILURE_FACTORY.noAuthenticationProvided(context));
return;
}
if (jwt != null) {
try {
Map<String, Object> claims = validateJwt(jwt, request, config);
forwardHeaders(request, config, jwt, claims);
stripAuthTokens(request, config);
chain.doApply(request);
} catch (ExpiredJwtException e) {
chain.doFailure(FAILURE_FACTORY.jwtExpired(context, e));
} catch (PrematureJwtException e) {
chain.doFailure(FAILURE_FACTORY.jwtPremature(context, e));
} catch (MalformedJwtException e) {
chain.doFailure(FAILURE_FACTORY.jwtMalformed(context, e));
} catch (SignatureException e) {
chain.doFailure(FAILURE_FACTORY.signatureException(context, e));
} catch (InvalidClaimException e) {
chain.doFailure(FAILURE_FACTORY.invalidClaim(context, e));
} catch (UnsupportedJwtException e) {
chain.doFailure(FAILURE_FACTORY.unsupportedJwt(context, e));
} catch (Exception e) {
chain.doFailure(FAILURE_FACTORY.genericFailure(context, e));
}
} else {
chain.doApply(request);
}
}
private Map<String, Object> validateJwt(String token, ApiRequest request, JWTPolicyBean config)
throws ExpiredJwtException, PrematureJwtException, MalformedJwtException, SignatureException, InvalidClaimException {
JwtParser parser = Jwts.parser()
.setSigningKey(config.getSigningKey())
.setAllowedClockSkewSeconds(config.getAllowedClockSkew());
// Set all claims
config.getRequiredClaims().stream() // TODO add type variable to allow dates, etc
.forEach(requiredClaim -> parser.require(requiredClaim.getClaimName(), requiredClaim.getClaimValue()));
return parser.parse(token, new ConfigCheckingJwtHandler(config));
}
private void stripAuthTokens(ApiRequest request, JWTPolicyBean config) {
if (config.getStripTokens()) {
request.getHeaders().remove(AUTHORIZATION_KEY);
request.getQueryParams().remove(ACCESS_TOKEN_QUERY_KEY);
}
}
private void forwardHeaders(ApiRequest request, JWTPolicyBean config, String rawToken, Map<String, Object> claims) {
for (ForwardAuthInfo entry : config.getForwardAuthInfo()) {
// Add the header if we've been able to look it up, else it'll just be empty.
Object claimValue = ACCESS_TOKEN_QUERY_KEY.equals(entry.getField()) ? rawToken : claims.get(entry.getField());
if (claimValue != null) {
request.getHeaders().put(entry.getHeader(), Objects.nullSafeToString(claimValue));
}
}
}
}