/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed 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
*
* 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.springframework.security.oauth2.provider.token.store.jwk;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.util.Assert;
import java.util.Collection;
/**
* A {@link TokenStore} implementation that provides support for verifying the
* JSON Web Signature (JWS) for a JSON Web Token (JWT) using a JSON Web Key (JWK).
* <br>
* <br>
*
* This {@link TokenStore} implementation is <b>exclusively</b> meant to be used by a <b>Resource Server</b> as
* it's sole responsibility is to decode a JWT and verify it's signature (JWS) using the corresponding JWK.
* <br>
* <br>
*
* <b>NOTE:</b>
* There are a few operations defined by {@link TokenStore} that are not applicable for a Resource Server.
* In these cases, the method implementation will explicitly throw a
* {@link JwkException} reporting <i>"This operation is not supported"</i>.
* <br>
* <br>
*
* The unsupported operations are as follows:
* <ul>
* <li>{@link #storeAccessToken(OAuth2AccessToken, OAuth2Authentication)}</li>
* <li>{@link #removeAccessToken(OAuth2AccessToken)}</li>
* <li>{@link #storeRefreshToken(OAuth2RefreshToken, OAuth2Authentication)}</li>
* <li>{@link #readRefreshToken(String)}</li>
* <li>{@link #readAuthenticationForRefreshToken(OAuth2RefreshToken)}</li>
* <li>{@link #removeRefreshToken(OAuth2RefreshToken)}</li>
* <li>{@link #removeAccessTokenUsingRefreshToken(OAuth2RefreshToken)}</li>
* <li>{@link #getAccessToken(OAuth2Authentication)}</li>
* <li>{@link #findTokensByClientIdAndUserName(String, String)}</li>
* <li>{@link #findTokensByClientId(String)}</li>
* </ul>
* <br>
*
* This implementation delegates to an internal instance of a {@link JwtTokenStore} which uses a
* specialized extension of {@link JwtAccessTokenConverter}.
* This specialized {@link JwtAccessTokenConverter} is capable of fetching (and caching)
* the JWK Set (a set of JWKs) from the URL supplied to the constructor of this implementation.
* <br>
* <br>
*
* The {@link JwtAccessTokenConverter} will verify the JWS in the following step sequence:
* <br>
* <br>
* <ol>
* <li>Extract the <b>"kid"</b> parameter from the JWT header.</li>
* <li>Find the matching JWK with the corresponding <b>"kid"</b> attribute.</li>
* <li>Obtain the <code>SignatureVerifier</code> associated with the JWK and verify the signature.</li>
* </ol>
* <br>
* <b>NOTE:</b> The algorithms currently supported by this implementation are: RS256, RS384 and RS512.
* <br>
* <br>
*
* @see JwtTokenStore
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key (JWK)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a>
*
* @author Joe Grandja
*/
public final class JwkTokenStore implements TokenStore {
private final JwtTokenStore delegate;
/**
* Creates a new instance using the provided URL as the location for the JWK Set.
*
* @param jwkSetUrl the JWK Set URL
*/
public JwkTokenStore(String jwkSetUrl) {
Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty");
JwkDefinitionSource jwkDefinitionSource = new JwkDefinitionSource(jwkSetUrl);
JwkVerifyingJwtAccessTokenConverter accessTokenConverter =
new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource);
this.delegate = new JwtTokenStore(accessTokenConverter);
}
/**
* Delegates to the internal instance {@link JwtTokenStore#readAuthentication(OAuth2AccessToken)}.
*
* @param token the access token
* @return the {@link OAuth2Authentication} representation of the access token
*/
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
return this.delegate.readAuthentication(token);
}
/**
* Delegates to the internal instance {@link JwtTokenStore#readAuthentication(String)}.
*
* @param tokenValue the access token value
* @return the {@link OAuth2Authentication} representation of the access token
*/
@Override
public OAuth2Authentication readAuthentication(String tokenValue) {
return this.delegate.readAuthentication(tokenValue);
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
throw this.operationNotSupported();
}
/**
* Delegates to the internal instance {@link JwtTokenStore#readAccessToken(String)}.
*
* @param tokenValue the access token value
* @return the {@link OAuth2AccessToken} representation of the access token value
*/
@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
return this.delegate.readAccessToken(tokenValue);
}
/**
* Delegates to the internal instance {@link JwtTokenStore#removeAccessToken(OAuth2AccessToken)}.
*
* @param token the access token
*/
@Override
public void removeAccessToken(OAuth2AccessToken token) {
this.delegate.removeAccessToken(token);
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
throw this.operationNotSupported();
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
throw this.operationNotSupported();
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
throw this.operationNotSupported();
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public void removeRefreshToken(OAuth2RefreshToken token) {
throw this.operationNotSupported();
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
throw this.operationNotSupported();
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
throw this.operationNotSupported();
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
throw this.operationNotSupported();
}
/**
* This operation is not applicable for a Resource Server
* and if called, will throw a {@link JwkException}.
*
* @throws JwkException reporting this operation is not supported
*/
@Override
public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
throw this.operationNotSupported();
}
private JwkException operationNotSupported() {
return new JwkException("This operation is not supported.");
}
}