/* * Copyright 2008 Web Cohesion * * 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.oauth.provider.token; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.apache.commons.codec.binary.Base64; import java.util.*; import java.security.SecureRandom; /** * Base implementation for token services that uses random values to generate tokens. Only the persistence mechanism * is left unimplemented. * * This base implementation creates tokens that have an expiration. For request tokens, the default validity is * 10 minutes. For access tokens, the default validity is 12 hours. * * @author Ryan Heaton */ public abstract class RandomValueProviderTokenServices implements OAuthProviderTokenServices, InitializingBean, OAuthTokenLifecycleRegistry { private Random random; private int requestTokenValiditySeconds = 60 * 10; //default 10 minutes. private int accessTokenValiditySeconds = 60 * 60 * 12; //default 12 hours. private int tokenSecretLengthBytes = 80; private final Collection<OAuthTokenLifecycleListener> lifecycleListeners = new HashSet<OAuthTokenLifecycleListener>(); /** * Read a token from persistence. * * @param token The token to read. * @return The token, or null if the token doesn't exist. */ protected abstract OAuthProviderTokenImpl readToken(String token); /** * Store a token from persistence. * * @param tokenValue The token value. * @param token The token to store. */ protected abstract void storeToken(String tokenValue, OAuthProviderTokenImpl token); /** * Remove a token from persistence. * * @param tokenValue The token to remove. * @return The token that was removed. */ protected abstract OAuthProviderTokenImpl removeToken(String tokenValue); /** * Initialze these token services. If no random generator is set, one will be created. * */ public void afterPropertiesSet() throws Exception { if (random == null) { random = new SecureRandom(); } } public OAuthProviderToken getToken(String token) throws AuthenticationException { OAuthProviderTokenImpl tokenImpl = readToken(token); if (tokenImpl == null) { throw new InvalidOAuthTokenException("Invalid token: " + token); } else if (isExpired(tokenImpl)) { removeToken(token); onTokenRemoved(tokenImpl); throw new ExpiredOAuthTokenException("Expired token."); } return tokenImpl; } /** * Whether the auth token is expired. * * @param authToken The auth token to check for expiration. * @return Whether the auth token is expired. */ protected boolean isExpired(OAuthProviderTokenImpl authToken) { if (authToken.isAccessToken()) { if ((authToken.getTimestamp() + (getAccessTokenValiditySeconds() * 1000L)) < System.currentTimeMillis()) { return true; } } else { if ((authToken.getTimestamp() + (getRequestTokenValiditySeconds() * 1000L)) < System.currentTimeMillis()) { return true; } } return false; } public OAuthProviderToken createUnauthorizedRequestToken(String consumerKey, String callbackUrl) throws AuthenticationException { String tokenValue = UUID.randomUUID().toString(); byte[] secretBytes = new byte[getTokenSecretLengthBytes()]; getRandom().nextBytes(secretBytes); String secret = new String(Base64.encodeBase64(secretBytes)); OAuthProviderTokenImpl token = new OAuthProviderTokenImpl(); token.setAccessToken(false); token.setConsumerKey(consumerKey); token.setCallbackUrl(callbackUrl); token.setUserAuthentication(null); token.setSecret(secret); token.setValue(tokenValue); token.setTimestamp(System.currentTimeMillis()); onTokenCreated(token); storeToken(tokenValue, token); return token; } public void authorizeRequestToken(String requestToken, String verifier, Authentication authentication) throws AuthenticationException { OAuthProviderTokenImpl tokenImpl = readToken(requestToken); if (tokenImpl == null) { throw new InvalidOAuthTokenException("Invalid token: " + requestToken); } else if (isExpired(tokenImpl)) { removeToken(requestToken); onTokenRemoved(tokenImpl); throw new ExpiredOAuthTokenException("Expired token."); } else if (tokenImpl.isAccessToken()) { throw new InvalidOAuthTokenException("Request to authorize an access token."); } tokenImpl.setUserAuthentication(authentication); tokenImpl.setTimestamp(System.currentTimeMillis());//reset the expiration. tokenImpl.setVerifier(verifier); storeToken(requestToken, tokenImpl); } public OAuthAccessProviderToken createAccessToken(String requestToken) throws AuthenticationException { OAuthProviderTokenImpl tokenImpl = readToken(requestToken); if (tokenImpl == null) { throw new InvalidOAuthTokenException("Invalid token: " + requestToken); } else if (isExpired(tokenImpl)) { removeToken(requestToken); onTokenRemoved(tokenImpl); throw new ExpiredOAuthTokenException("Expired token."); } else if (tokenImpl.isAccessToken()) { throw new InvalidOAuthTokenException("Not a request token."); } else if (tokenImpl.getUserAuthentication() == null) { throw new InvalidOAuthTokenException("Request token has not been authorized."); } OAuthProviderTokenImpl requestTokenImpl = removeToken(requestToken); if (requestTokenImpl != null) { onTokenRemoved(requestTokenImpl); } String tokenValue = UUID.randomUUID().toString(); byte[] secretBytes = new byte[getTokenSecretLengthBytes()]; getRandom().nextBytes(secretBytes); String secret = new String(Base64.encodeBase64(secretBytes)); OAuthProviderTokenImpl token = new OAuthProviderTokenImpl(); token.setAccessToken(true); token.setConsumerKey(tokenImpl.getConsumerKey()); token.setUserAuthentication(tokenImpl.getUserAuthentication()); token.setSecret(secret); token.setValue(tokenValue); token.setTimestamp(System.currentTimeMillis()); onTokenCreated(token); storeToken(tokenValue, token); return token; } /** * Logic for handling event firing of a removed token. * * @param token The token that was removed (possibly null). */ protected void onTokenRemoved(OAuthProviderTokenImpl token) { for (OAuthTokenLifecycleListener listener : getLifecycleListeners()) { listener.tokenExpired(token); } } /** * Logic for handling event firing of a created token. * * @param token The token that was created. */ protected void onTokenCreated(OAuthProviderTokenImpl token) { for (OAuthTokenLifecycleListener listener : getLifecycleListeners()) { listener.tokenCreated(token); } } /** * The length of the token secret in bytes, before being base64-encoded. * * @return The length of the token secret in bytes. */ public int getTokenSecretLengthBytes() { return tokenSecretLengthBytes; } /** * The length of the token secret in bytes, before being base64-encoded. * * @param tokenSecretLengthBytes The length of the token secret in bytes, before being base64-encoded. */ public void setTokenSecretLengthBytes(int tokenSecretLengthBytes) { this.tokenSecretLengthBytes = tokenSecretLengthBytes; } /** * The random value generator used to create token secrets. * * @return The random value generator used to create token secrets. */ public Random getRandom() { return random; } /** * The random value generator used to create token secrets. * * @param random The random value generator used to create token secrets. */ public void setRandom(Random random) { this.random = random; } /** * The validity (in seconds) of the unauthenticated request token. * * @return The validity (in seconds) of the unauthenticated request token. */ public int getRequestTokenValiditySeconds() { return requestTokenValiditySeconds; } /** * The validity (in seconds) of the unauthenticated request token. * * @param requestTokenValiditySeconds The validity (in seconds) of the unauthenticated request token. */ public void setRequestTokenValiditySeconds(int requestTokenValiditySeconds) { this.requestTokenValiditySeconds = requestTokenValiditySeconds; } /** * The validity (in seconds) of the access token. * * @return The validity (in seconds) of the access token. */ public int getAccessTokenValiditySeconds() { return accessTokenValiditySeconds; } /** * The validity (in seconds) of the access token. * * @param accessTokenValiditySeconds The validity (in seconds) of the access token. */ public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) { this.accessTokenValiditySeconds = accessTokenValiditySeconds; } /** * The collection of lifecycle listeners for these services. * * @return The collection of lifecycle listeners for these services. */ public Collection<OAuthTokenLifecycleListener> getLifecycleListeners() { return lifecycleListeners; } /** * Register lifecycle listener(s) with these token services. * * @param lifecycleListeners The listeners. */ @Autowired ( required = false ) public void register(OAuthTokenLifecycleListener... lifecycleListeners) { if (lifecycleListeners != null) { this.lifecycleListeners.addAll(Arrays.asList(lifecycleListeners)); } } }