All Downloads are FREE. Search and download functionalities are using the official Maven repository.

leap.oauth2.webapp.token.jwt.JwtTokenVerifier Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 the original author or authors.
 *
 * 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 leap.oauth2.webapp.token.jwt;

import leap.core.AppConfigException;
import leap.core.annotation.Inject;
import leap.core.security.token.TokenVerifyException;
import leap.core.security.token.jwt.JWT;
import leap.core.security.token.jwt.JwtVerifier;
import leap.core.security.token.jwt.RsaVerifier;
import leap.lang.New;
import leap.lang.Strings;
import leap.lang.annotation.Nullable;
import leap.lang.codec.Base64;
import leap.lang.http.client.HttpClient;
import leap.lang.http.client.HttpResponse;
import leap.lang.logging.Log;
import leap.lang.logging.LogFactory;
import leap.lang.security.RSA;
import leap.oauth2.webapp.OAuth2Config;
import leap.oauth2.webapp.OAuth2InternalServerException;
import leap.oauth2.webapp.token.SimpleTokenInfo;
import leap.oauth2.webapp.token.Token;
import leap.oauth2.webapp.token.TokenInfo;
import leap.oauth2.webapp.token.TokenVerifier;
import leap.web.security.SecurityConfig;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.lang.JoseException;

import java.security.interfaces.RSAPublicKey;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.jose4j.jwk.JsonWebKey.*;

public class JwtTokenVerifier implements TokenVerifier {

    private static final Log log = LogFactory.get(JwtTokenVerifier.class);

    protected @Inject SecurityConfig sc;
    protected @Inject OAuth2Config   config;
    protected @Inject HttpClient     httpClient;
    
    protected @Inject @Nullable JwksSelector selector;

    private volatile JwtVerifier verifier;

    @Override
    public TokenInfo verifyToken(Token token) throws TokenVerifyException {
        if (null == verifier) {
            refreshJwtVerifier(token.getToken());
        }
        return verify(verifier, token.getToken());
    }

    protected void refreshJwtVerifier(String token) {
        if (!Strings.isEmpty(config.getJwksUrl())) {
            refreshJwtVerifierFromJwks(token, config.getJwksUrl());
        } else {
            if (Strings.isEmpty(config.getPublicKeyUrl())) {
                throw new AppConfigException("publicKeyUrl must be configured");
            }

            log.info("Fetching public key from server, url '{}' ...", config.getPublicKeyUrl());
            HttpResponse response = httpClient.request(config.getPublicKeyUrl()).get();
            if (!response.isOk()) {
                throw new OAuth2InternalServerException("Error fetching public key from server, status " + response.getStatus() + "");
            }

            String encoded = response.getString();
            RSAPublicKey publicKey = RSA.decodePublicKey(encoded);
            verifier = new RsaVerifier(publicKey);
        }
    }

    protected void refreshJwtVerifierFromJwks(String token, String jwksUrl) {
        log.info("Fetching jwks from url '{}' ...", jwksUrl);
        HttpResponse response = httpClient.request(jwksUrl).get();
        if (!response.isOk()) {
            throw new OAuth2InternalServerException("Error fetching jwks from server, status " + response.getStatus() + "");
        }

        try {
            JsonWebKey jwk = null;
            final List jwks = new JsonWebKeySet(response.getString()).getJsonWebKeys();
            if (jwks.size() != 1) {
                if (Strings.isNotEmpty(config.getJwksKeyId())){
                    Optional optional = jwks.stream().filter(jsonWebKey -> Strings.equals(config.getJwksKeyId(),jsonWebKey.getKeyId())).findAny();
                    if (optional.isPresent()){
                        jwk = optional.get();
                    }
                }
                if (null == jwk){
                    if (null != selector){
                        Optional optional = jwks.stream().filter(jsonWebKey -> selector.test(jwkToMap(jsonWebKey))).findAny();
                        if (optional.isPresent()){
                            jwk = optional.get();
                        }
                    }
                }
                if (null == jwk){
                    throw new OAuth2InternalServerException("server response multi jwks, but jwt verifier can not select ensure one.");
                }
            }else {
                jwk = jwks.get(0);
            }
            
            if (!(jwk.getKey() instanceof RSAPublicKey)) {
                throw new OAuth2InternalServerException("Non RSA public key '" + jwk.getKey() + "' not supported");
            }

            verifier = new RsaVerifier((RSAPublicKey) jwk.getKey());
        } catch (JoseException e) {
            throw new OAuth2InternalServerException("Invalid jwks : " + e.getMessage());
        }
    }

    protected Map jwkToMap(JsonWebKey jwk){
        Map map = New.hashMap();
        map.put(KEY_TYPE_PARAMETER, jwk.getKeyType());
        map.put(USE_PARAMETER, jwk.getUse());
        map.put(KEY_ID_PARAMETER, jwk.getKeyId());
        map.put(ALGORITHM_PARAMETER, jwk.getAlgorithm());
        map.put(KEY_OPERATIONS, jwk.getKeyOps());
        map.put("key", Base64.encode(jwk.getKey().getEncoded()));
        return map;
    }
    
    protected TokenInfo verify(JwtVerifier verifier, String token) throws TokenVerifyException {
        Map jwtDetail;

        try {
            jwtDetail = verifier.verify(token);
        } catch (TokenVerifyException e) {
            refreshJwtVerifier(token);
            jwtDetail = verifier.verify(token);
        }

        return createTokenInfo(jwtDetail);
    }

    protected TokenInfo createTokenInfo(Map jwtDetail) {
        SimpleTokenInfo tokenInfo = new SimpleTokenInfo();

        String userId = (String) jwtDetail.get(JWT.CLAIM_SUBJECT);
        tokenInfo.setUserId(userId);
        tokenInfo.setScope((String) jwtDetail.get("scope"));
        tokenInfo.setClientId((String) jwtDetail.get("client_id"));

        /* todo:
        if(Strings.isEmpty(tokenInfo.getClientId())) {
            tokenInfo.setClientId((String)jwtDetail.get(JWT.CLAIM_AUDIENCE));
        }
        */

        tokenInfo.setCreated(System.currentTimeMillis());
        Object exp = jwtDetail.get(JWT.CLAIM_EXPIRATION_TIME);
        if (null != exp && exp instanceof Number) {
            long expirationTimeSecond = ((Number) exp).longValue();
            long nowTimeInSecond = System.currentTimeMillis() / 1000L;
            tokenInfo.setExpiresIn((int) (expirationTimeSecond - nowTimeInSecond));
        }
        tokenInfo.setClaims(jwtDetail);
        return tokenInfo;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy