dk.dma.msinm.user.security.JWTService.java Source code

Java tutorial

Introduction

Here is the source code for dk.dma.msinm.user.security.JWTService.java

Source

/* Copyright (c) 2011 Danish Maritime Authority
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */
package dk.dma.msinm.user.security;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import dk.dma.msinm.common.settings.annotation.Setting;
import dk.dma.msinm.user.User;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;

import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.security.auth.login.CredentialExpiredException;
import java.util.*;

/**
 * Service for handling Json Web Tokens (JWT).
 */
@Singleton
@Lock(LockType.READ)
public class JWTService {

    private static ThreadLocal<String> THREAD_TEMP_JWT_PWD_TOKEN = new ThreadLocal<>();

    @Inject
    Logger log;

    @Inject
    @Setting(value = "jwtTimeoutMinutes", defaultValue = "600") // 10 hours
    Long jwtTimeoutMinutes;

    @Inject
    @Setting(value = "jwtReauthMinutes", defaultValue = "180") // 3 hours
    Long jwtReauthMinutes;

    @Inject
    @Setting(value = "jwtHmacSharedKey", defaultValue = "sdjkfhs-SALKJD-933409_JksdjfkA")
    String hmacSharedKey;

    /**
     * Creates a signed JWT for the given user
     * @param issuer the issuer
     * @param user the user
     * @return the encrypted JWT
     */
    public JWTToken createSignedJWT(String issuer, User user) throws Exception {

        // compose the JWT reserved claim names
        JWTClaimsSet jwtClaims = new JWTClaimsSet();
        jwtClaims.setSubject(user.getEmail());
        jwtClaims.setIssuer(issuer);
        jwtClaims.setAudience(Arrays.asList(issuer));
        jwtClaims.setExpirationTime(new Date(new Date().getTime() + 1000 * 60 * jwtTimeoutMinutes));
        jwtClaims.setIssueTime(new Date());
        jwtClaims.setJWTID(UUID.randomUUID().toString());

        // Create JWS header with HS256 algorithm
        JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
        header.setContentType("text/plain");

        // Create JWS object
        JWSObject jwsObject = new JWSObject(header, new Payload(jwtClaims.toJSONObject()));

        // Create HMAC signer
        JWSSigner signer = new MACSigner(hmacSharedKey.getBytes());

        // Sign the JWT
        jwsObject.sign(signer);

        // Fill out the resulting JWT token
        JWTToken token = new JWTToken();
        token.setToken(jwsObject.serialize());
        token.setEmail(user.getEmail());
        token.setName(user.getName());
        List<String> roles = new ArrayList<>();
        user.getRoles().forEach(r -> roles.add(r.getName()));
        token.setRoles(roles.toArray(new String[roles.size()]));
        return token;
    }

    /**
     * Parses a JWT authorization header. Throws an exception if the JWT cannot be verified
     *
     * @param token the JWT token
     * @return the parsed JWT
     */
    public ParsedJWTInfo parseSignedJWT(String token) throws Exception {
        // Parse back and check signature
        SignedJWT signedJWT = SignedJWT.parse(token);

        JWSVerifier verifier = new MACVerifier(hmacSharedKey.getBytes());

        boolean verifiedSignature = signedJWT.verify(verifier);

        if (!verifiedSignature) {
            throw new CredentialExpiredException("JWT token expired");
        }

        ReadOnlyJWTClaimsSet claims = signedJWT.getJWTClaimsSet();

        // Collect the interesting information in a ParsedJWTInfo and return it
        ParsedJWTInfo jwtInfo = new ParsedJWTInfo();
        jwtInfo.setSubject(claims.getSubject());
        jwtInfo.setExpirationTime(claims.getExpirationTime());
        jwtInfo.setIssueTime(claims.getIssueTime());
        return jwtInfo;
    }

    /**
     * Checks to see if it is time to issue a new JWT token
     * @param jwtInfo the current JWT token
     * @return if it is time to issue a new JWT token
     */
    public boolean reauthJWT(ParsedJWTInfo jwtInfo) {
        double minutesOld = (new Date().getTime() - jwtInfo.getIssueTime().getTime()) / 1000.0 / 60.0;
        return minutesOld > jwtReauthMinutes.doubleValue();
    }

    /**
     * Generates a temporary password token. This is generated in the SecurityServletFilter
     * and used in the JbossLoginModule in the same synchronous request.
     * The token is associated with the current thread.
     *
     * @param prefix a prefix
     * @return the temporary password token
     */
    public String issueTempJwtPwdToken(String prefix) {
        String pwd = createTempJwtPwdToken(prefix);
        THREAD_TEMP_JWT_PWD_TOKEN.set(pwd);
        return pwd;
    }

    /**
     * Generates a temporary password token.
     * @param prefix a prefix
     * @return the temporary password token
     */
    public String createTempJwtPwdToken(String prefix) {
        String pwd = StringUtils.defaultString(prefix) + UUID.randomUUID().toString();
        return pwd;
    }

    /**
     * Verifies that the given password is the one associated with the current thread.
     * At the same time, it removes the password from the current thread, so, it can
     * only be called onece.
     * @param pwd the password to check
     * @return if the password matches the one of the current thread
     */
    public boolean verifyTempJwtPwdToken(String pwd) {
        String threadPwd = THREAD_TEMP_JWT_PWD_TOKEN.get();
        THREAD_TEMP_JWT_PWD_TOKEN.remove();
        return pwd.equals(threadPwd);
    }

    /**
     * Selected information from a parsed JWT token
     */
    public static class ParsedJWTInfo {
        String subject;
        Date expirationTime;
        Date issueTime;

        public String getSubject() {
            return subject;
        }

        public void setSubject(String subject) {
            this.subject = subject;
        }

        public Date getExpirationTime() {
            return expirationTime;
        }

        public void setExpirationTime(Date expirationTime) {
            this.expirationTime = expirationTime;
        }

        public Date getIssueTime() {
            return issueTime;
        }

        public void setIssueTime(Date issueTime) {
            this.issueTime = issueTime;
        }
    }
}