001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2021, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.dpop.verifiers; 019 020 021import java.util.Date; 022import java.util.Map; 023import java.util.Timer; 024import java.util.TimerTask; 025import java.util.concurrent.ConcurrentHashMap; 026 027import net.jcip.annotations.ThreadSafe; 028 029import com.nimbusds.oauth2.sdk.id.JWTID; 030import com.nimbusds.oauth2.sdk.util.singleuse.AlreadyUsedException; 031import com.nimbusds.oauth2.sdk.util.singleuse.SingleUseChecker; 032 033 034/** 035 * DPoP proof JWT single use checker. Caches a hash of the checked DPoP JWT 036 * "jti" (JWT ID) claims for a given DPoP issuer. The checker should be 037 * {@link #shutdown() shut down} when no longer in use. 038 */ 039@ThreadSafe 040@Deprecated 041public class DefaultDPoPSingleUseChecker implements SingleUseChecker<Map.Entry<DPoPIssuer, JWTID>> { 042 043 private final Timer timer; 044 045 private final ConcurrentHashMap<String,Long> cachedJTIs = new ConcurrentHashMap<>(); 046 047 048 /** 049 * Creates a new DPoP proof JWT single use checker. 050 * 051 * @param lifetimeSeconds The lifetime of cached DPoP proof "jti" 052 * (JWT ID) claims, in seconds. 053 * @param purgeIntervalSeconds The interval in seconds for purging the 054 * cached "jti" (JWT ID) claims of checked 055 * DPoP proofs. 056 */ 057 public DefaultDPoPSingleUseChecker(final long lifetimeSeconds, 058 final long purgeIntervalSeconds) { 059 060 timer = new Timer("dpop-single-use-jti-cache-purge-task", true); 061 062 timer.schedule( 063 new TimerTask() { 064 @Override 065 public void run() { 066 final long nowMS = new Date().getTime(); 067 final long expHorizon = nowMS - lifetimeSeconds * 1000; 068 for (Map.Entry<String, Long> en: cachedJTIs.entrySet()) { 069 if (en.getValue() < expHorizon) { 070 cachedJTIs.remove(en.getKey()); 071 } 072 } 073 } 074 }, 075 purgeIntervalSeconds * 1000, 076 purgeIntervalSeconds * 1000); 077 } 078 079 080 @Override 081 public void markAsUsed(final Map.Entry<DPoPIssuer, JWTID> object) 082 throws AlreadyUsedException { 083 084 String key = object.getKey().getValue() + ":" + InMemoryDPoPSingleUseChecker.computeSHA256(object.getValue()); 085 086 long nowMS = new Date().getTime(); 087 088 if (cachedJTIs.putIfAbsent(key, nowMS) != null) { 089 throw new AlreadyUsedException("Detected jti replay"); 090 } 091 } 092 093 094 /** 095 * Returns the number of cached items. 096 * 097 * @return The cached items, zero if none. 098 */ 099 public int getCacheSize() { 100 101 return cachedJTIs.size(); 102 } 103 104 105 /** 106 * Shuts down this checker and frees any associated resources. 107 */ 108 public void shutdown() { 109 110 timer.cancel(); 111 } 112}