Skip to content

Commit

Permalink
Token key endpoint caches resource
Browse files Browse the repository at this point in the history
- Always sends etag with key responses
- If "If-None-Match" matches last modified timestamp, returns 304 with no body

[#114026695] https://www.pivotaltracker.com/story/show/114026695

Signed-off-by: Bharath Sekar <bharath.sekar@ge.com>
  • Loading branch information
Hieu Nguyen authored and Bharath Sekar committed Aug 9, 2017
1 parent 3a77868 commit d9b9392
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 145 deletions.
Expand Up @@ -58,6 +58,10 @@ public class KeyInfo {
private String type = "MAC"; private String type = "MAC";
private RSAPublicKey rsaPublicKey; private RSAPublicKey rsaPublicKey;


public static Long getLastModified() {
return IdentityZoneHolder.get().getLastModified().getTime();
}

public static KeyInfo getKey(String keyId) { public static KeyInfo getKey(String keyId) {
return getKeys().get(keyId); return getKeys().get(keyId);
} }
Expand Down
Expand Up @@ -16,18 +16,23 @@
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.oauth.token.VerificationKeyResponse; import org.cloudfoundry.identity.uaa.oauth.token.VerificationKeyResponse;
import org.cloudfoundry.identity.uaa.oauth.token.VerificationKeysListResponse; import org.cloudfoundry.identity.uaa.oauth.token.VerificationKeysListResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;


import java.security.Principal; import java.security.Principal;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.util.Base64; import java.util.Base64;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
Expand All @@ -44,6 +49,36 @@ public class TokenKeyEndpoint {


protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());


@RequestMapping(value = "/token_key", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<VerificationKeyResponse> getKey(Principal principal,
@RequestHeader(value = "If-None-Match", required = false, defaultValue = "NaN") String eTag) {

String lastModified = KeyInfo.getLastModified().toString();
if (unmodifiedResource(eTag, lastModified)) {
return new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}

HttpHeaders header = new HttpHeaders();
header.put("ETag", Collections.singletonList(lastModified));
return new ResponseEntity<>(getKey(principal), header, HttpStatus.OK);
}


@RequestMapping(value = "/token_keys", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<VerificationKeysListResponse> getKeys(Principal principal,
@RequestHeader(value = "If-None-Match", required = false, defaultValue = "NaN") String eTag) {
String lastModified = KeyInfo.getLastModified().toString();
if (unmodifiedResource(eTag, lastModified)) {
return new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}

HttpHeaders header = new HttpHeaders();
header.put("ETag", Collections.singletonList(lastModified));
return new ResponseEntity<>(getKeys(principal), header, HttpStatus.OK);
}

/** /**
* Get the verification key for the token signatures. The principal has to * Get the verification key for the token signatures. The principal has to
* be provided only if the key is secret * be provided only if the key is secret
Expand All @@ -52,8 +87,6 @@ public class TokenKeyEndpoint {
* @param principal the currently authenticated user if there is one * @param principal the currently authenticated user if there is one
* @return the key used to verify tokens * @return the key used to verify tokens
*/ */
@RequestMapping(value = "/token_key", method = RequestMethod.GET)
@ResponseBody
public VerificationKeyResponse getKey(Principal principal) { public VerificationKeyResponse getKey(Principal principal) {
KeyInfo key = KeyInfo.getActiveKey(); KeyInfo key = KeyInfo.getActiveKey();
if (!includeSymmetricalKeys(principal) && !key.isAssymetricKey()) { if (!includeSymmetricalKeys(principal) && !key.isAssymetricKey()) {
Expand Down Expand Up @@ -90,6 +123,10 @@ public static Map<String, Object> getResultMap(KeyInfo key) {
return result; return result;
} }


private boolean unmodifiedResource(String eTag, String lastModified) {
return !eTag.equals("NaN") && lastModified.equals(eTag);
}

/** /**
* Get the verification key for the token signatures wrapped into keys array. * Get the verification key for the token signatures wrapped into keys array.
* Wrapping done for compatibility with some clients expecting this even for single key, like mod_auth_openidc. * Wrapping done for compatibility with some clients expecting this even for single key, like mod_auth_openidc.
Expand All @@ -99,8 +136,6 @@ public static Map<String, Object> getResultMap(KeyInfo key) {
* @param principal the currently authenticated user if there is one * @param principal the currently authenticated user if there is one
* @return the key used to verify tokens, wrapped in keys array * @return the key used to verify tokens, wrapped in keys array
*/ */
@RequestMapping(value = "/token_keys", method = RequestMethod.GET)
@ResponseBody
public VerificationKeysListResponse getKeys(Principal principal) { public VerificationKeysListResponse getKeys(Principal principal) {
boolean includeSymmetric = includeSymmetricalKeys(principal); boolean includeSymmetric = includeSymmetricalKeys(principal);
Map<String, KeyInfo> keys = KeyInfo.getKeys(); Map<String, KeyInfo> keys = KeyInfo.getKeys();
Expand Down

0 comments on commit d9b9392

Please sign in to comment.