Java tutorial
/** * 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; } }