org.apereo.portal.soffit.service.AbstractJwtService.java Source code

Java tutorial

Introduction

Here is the source code for org.apereo.portal.soffit.service.AbstractJwtService.java

Source

/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you 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 the following location:
 *
 *   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 org.apereo.portal.soffit.service;

import java.util.Date;
import java.util.UUID;

import javax.annotation.PostConstruct;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.apereo.portal.soffit.ITokenizable;
import org.jasypt.util.text.BasicTextEncryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

/**
 * Base class for services that produce JASON Web Tokens.
 *
 * @since 5.0
 * @author drewwills
 */
public class AbstractJwtService {

    public static final String JWT_ISSUER = "Soffit";

    public static final String SIGNATURE_KEY_PROPERTY = "org.apereo.portal.soffit.jwt.signatureKey";
    public static final String DEFAULT_SIGNATURE_KEY = "CHANGEME";

    public static final String ENCRYPTION_PASSWORD_PROPERTY = "org.apereo.portal.soffit.jwt.encryptionPassword";
    public static final String DEFAULT_ENCRYPTION_PASSWORD = "CHANGEME";

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Value("${" + SIGNATURE_KEY_PROPERTY + ":" + DEFAULT_SIGNATURE_KEY + "}")
    private String signatureKey;

    @Value("${" + ENCRYPTION_PASSWORD_PROPERTY + ":" + DEFAULT_ENCRYPTION_PASSWORD + "}")
    private String encryptionPassword;

    /*
     * NOTE:  There is also a StrongTextEncryptor, but it requires each deployment
     * to download and install the "Java Cryptography Extension (JCE) Unlimited
     * Strength Jurisdiction Policy Files," which sounds like a tremendous PITA.
     * The BasicTextEncryptor supports "normal-strength encryption of texts,"
     * which should be satisfactory for our needs.
     */
    final BasicTextEncryptor textEncryptor = new BasicTextEncryptor();

    @PostConstruct
    public void init() {

        // Signature Key
        if (StringUtils.isBlank(signatureKey)) {
            logger.error("The value of required property {} is blank", SIGNATURE_KEY_PROPERTY);
            throw new IllegalStateException("Missing property " + SIGNATURE_KEY_PROPERTY);
        } else if (DEFAULT_SIGNATURE_KEY.equals(signatureKey)) {
            logger.warn("Property {} is using the deafult value;  please change it", SIGNATURE_KEY_PROPERTY);
        }

        // Encryption Passowrd
        if (StringUtils.isBlank(encryptionPassword)) {
            logger.error("The value of required property {} is blank", ENCRYPTION_PASSWORD_PROPERTY);
            throw new IllegalStateException("Missing property " + ENCRYPTION_PASSWORD_PROPERTY);
        } else if (DEFAULT_ENCRYPTION_PASSWORD.equals(encryptionPassword)) {
            logger.warn("Property {} is using the deafult value;  please change it", ENCRYPTION_PASSWORD_PROPERTY);
        }
        textEncryptor.setPassword(encryptionPassword);

    }

    protected Claims createClaims(Class<? extends ITokenizable> clazz, String username, Date expires) {

        // Registered claims
        final Claims rslt = Jwts.claims().setIssuer(JWT_ISSUER).setSubject(username).setExpiration(expires)
                .setIssuedAt(new Date()).setId(UUID.randomUUID().toString());

        // Deserialization class
        rslt.put(JwtClaims.CLASS.getName(), clazz.getName());

        return rslt;

    }

    protected String generateEncryptedToken(Claims claims) {

        final String jwt = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, signatureKey)
                .compact();

        // Encryption
        final String rslt = textEncryptor.encrypt(jwt);

        return rslt;

    }

    protected Jws<Claims> parseEncrypteToken(String encryptedToken, Class<? extends ITokenizable> clazz) {

        // Decryption
        final String jwt = textEncryptor.decrypt(encryptedToken);

        final Jws<Claims> rslt = Jwts.parser().setSigningKey(signatureKey).parseClaimsJws(jwt);

        // Token expired?
        final Date expires = rslt.getBody().getExpiration();
        if (expires.before(new Date())) {
            final String msg = "The specified token is expired:  " + rslt;
            throw new SecurityException(msg);
        }

        // Sanity check
        final String s = (String) rslt.getBody().get(JwtClaims.CLASS.getName());
        if (!clazz.getName().equals(s)) {
            // Opportunity for future versioning of the data model... needs work
            String msg = "Token class mismatch;  expected '" + clazz.getName() + "' but was '" + s + "'";
            throw new RuntimeException(msg);
        }

        return rslt;

    }

}