Mercurial > hg > digilib
view servlet/src/main/java/digilib/auth/OpenIdAuthnOps.java @ 1507:8c7f1ef5a67f
added auth token in cookie. cookie name configurable as "auth-token-cookie".
author | robcast |
---|---|
date | Thu, 28 Apr 2016 19:40:47 +0200 |
parents | a693f487d860 |
children | e7e38e1f68df |
line wrap: on
line source
package digilib.auth; /* * #%L * Authentication class implementation using IP addresses and Servlet user information * * Digital Image Library servlet components * * %% * Copyright (C) 2016 MPIWG Berlin * %% * This program 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 program 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 General Lesser Public License for more details. * * You should have received a copy of the GNU General Lesser Public * License along with this program. If not, see * <http://www.gnu.org/licenses/lgpl-3.0.html>. * #L% * Author: Robert Casties (robcast@berlios.de) */ import java.io.File; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.MalformedClaimException; import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.jwt.consumer.JwtContext; import org.jose4j.lang.JoseException; import digilib.conf.DigilibConfiguration; import digilib.conf.DigilibRequest; import digilib.conf.DigilibServletRequest; import digilib.util.XMLMapListLoader; /** * Implements AuthnOps using an OpenId Connect ID token. * * The name of the configuration file is read from the digilib config parameter "auth-file". * <p/> * The tag "digilib-oauth" is read from the configuration file: * <pre> * {@code * <digilib-oauth> * <openid issuer="https://id.some.where" clientid="myclient" roles="someusers" keytype="jwk"> * {"kty":"RSA","e":"AQAB","kid":"rsa1","n":"qjQ5U3wXzamg9R...idGpIiVilMDVBs"} * </openid> * </digilib-oauth> * } * </pre> * * A request with an "id_token" parameter containing a valid token signed with the configured key * including the configured issuer (iss) and clientid (aud) is granted the configured roles. * */ public class OpenIdAuthnOps implements AuthnOps { /** general logger for this class */ protected Logger logger = Logger.getLogger(this.getClass()); protected File configFile; protected JwtConsumer firstPassJwtConsumer; protected Map<String, JwtConsumer> idpJwtConsumers; protected Map<String, List<String>> idpRoles; protected String tokenCookieName; /* (non-Javadoc) * @see digilib.auth.AuthnOps#init(digilib.conf.DigilibConfiguration) */ @Override public void init(DigilibConfiguration dlConfig) throws AuthOpException { configFile = dlConfig.getAsFile("auth-file"); logger.debug("openidauthnops.init (" + configFile + ")"); List<Map<String, String>> idpList; try { // load identity providers XMLMapListLoader idpLoader = new XMLMapListLoader("digilib-oauth", "openid"); idpList = idpLoader.loadUri(configFile.toURI()); } catch (Exception e) { throw new AuthOpException("ERROR loading auth config file: " + e); } if (idpList == null) { throw new AuthOpException("ERROR unable to load auth config file!"); } // create Map of roles by issuer idpRoles = new HashMap<String, List<String>>(); // build a first pass JwtConsumer that doesn't check signatures or do any validation. firstPassJwtConsumer = new JwtConsumerBuilder() .setSkipAllValidators() .setDisableRequireSignature() .setSkipSignatureVerification() .build(); // create Map of configured JwtConsumers by issuer idpJwtConsumers = new HashMap<String, JwtConsumer>(); for (Map<String, String> idpDesc : idpList) { String issuer = idpDesc.get("issuer"); if (issuer == null) { logger.error("Missing issuer in openid tag!"); continue; } String clientid = idpDesc.get("clientid"); if (clientid == null) { logger.error("Missing clientid in openid tag! (issuer: "+issuer+")"); continue; } String rolestr = idpDesc.get("roles"); if (rolestr == null) { logger.error("Missing roles in openid tag! (issuer: "+issuer+")"); continue; } // split roles string into list List<String> roles = Arrays.asList(rolestr.split(",")); String keytype = idpDesc.get("keytype"); if (keytype == null || ! keytype.equals("jwk")) { logger.error("Missing or invalid keytype in openid tag! (issuer: "+issuer+")"); continue; } String keyData = idpDesc.get("_text"); if (keyData == null || keyData.length() == 0) { logger.error("Missing key data in openid tag! (issuer: "+issuer+")"); continue; } try { // create key from JWK data JsonWebKey jwk = JsonWebKey.Factory.newJwk(keyData); // create second pass consumer for validation JwtConsumer secondPassJwtConsumer = new JwtConsumerBuilder() .setExpectedIssuer(issuer) .setVerificationKey(jwk.getKey()) .setRequireExpirationTime() .setAllowedClockSkewInSeconds(300) .setRequireSubject() .setExpectedAudience(clientid) .build(); // save consumer and roles idpJwtConsumers.put(issuer, secondPassJwtConsumer); idpRoles.put(issuer, roles); logger.debug("Registered id provider '"+issuer+"'"); } catch (JoseException e) { logger.error("Invalid key data in openid tag! (issuer: "+issuer+")"); continue; } } // set token cookie name tokenCookieName = dlConfig.getAsString("auth-token-cookie"); } /* (non-Javadoc) * @see digilib.auth.AuthnOps#hasUserRoles() */ @Override public boolean hasUserRoles() { return true; } /* (non-Javadoc) * @see digilib.auth.AuthnOps#getUserRoles(digilib.conf.DigilibRequest) */ @Override public List<String> getUserRoles(DigilibRequest request) throws AuthOpException { /* * try token parameter first */ String id_token = request.getAsString("id_token"); if (id_token == null || id_token.isEmpty()) { /* * try token cookie next */ HttpServletRequest srvReq = ((DigilibServletRequest) request).getServletRequest(); Cookie[] cookies = srvReq.getCookies(); if (cookies != null) { for (Cookie c : cookies) { if (c.getName() == tokenCookieName) { id_token = c.getValue(); break; } } } if (id_token == null || id_token.isEmpty()) { logger.error("Missing id token!"); return null; } } // the first JwtConsumer is just used to parse the JWT into a JwtContext object. try { JwtContext jwtContext = firstPassJwtConsumer.process(id_token); // extract issuer String issuer = jwtContext.getJwtClaims().getIssuer(); // get validating consumer for this issuer JwtConsumer secondPassJwtConsumer = idpJwtConsumers.get(issuer); if (secondPassJwtConsumer == null) { logger.error("Unknown id token issuer: "+issuer); return null; } // validate token secondPassJwtConsumer.processContext(jwtContext); JwtClaims claims = jwtContext.getJwtClaims(); String sub = claims.getSubject(); logger.debug("id_token authenticated user '"+sub+"'"); // get roles List<String> provided = idpRoles.get(issuer); return provided; } catch (InvalidJwtException | MalformedClaimException e) { logger.error("Error validating id token: "+e.getMessage()); return null; } } /* (non-Javadoc) * @see digilib.auth.IpAuthnOps#isUserInRole(digilib.conf.DigilibRequest, java.lang.String) */ @Override public boolean isUserInRole(DigilibRequest request, String role) throws AuthOpException { List<String> provided = getUserRoles(request); if (provided != null && provided.contains(role)) { return true; } return false; } }