Added a OAuth2 Store to save state between application restarts
This commit is contained in:
parent
8ba1441572
commit
8508df42ac
4 changed files with 149 additions and 44 deletions
|
|
@ -26,11 +26,10 @@ package zutil.net.http.page.oauth;
|
||||||
|
|
||||||
import zutil.Hasher;
|
import zutil.Hasher;
|
||||||
import zutil.Timer;
|
import zutil.Timer;
|
||||||
|
import zutil.net.http.page.oauth.OAuth2RegistryStore.OAuth2ClientRegister;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data class containing authentication information for individual
|
* A data class containing authentication information for individual
|
||||||
|
|
@ -40,13 +39,29 @@ public class OAuth2Registry implements Serializable {
|
||||||
private static final long DEFAULT_CODE_TIMEOUT = 10 * 60 * 1000; // 10min
|
private static final long DEFAULT_CODE_TIMEOUT = 10 * 60 * 1000; // 10min
|
||||||
private static final long DEFAULT_TOKEN_TIMEOUT = 24 * 60 * 60 * 1000; // 24h
|
private static final long DEFAULT_TOKEN_TIMEOUT = 24 * 60 * 60 * 1000; // 24h
|
||||||
|
|
||||||
private Map<String, ClientRegister> clientRegisters = new HashMap<>();
|
private Map<String, OAuth2ClientRegister> clientRegisters = new HashMap<>();
|
||||||
private boolean requireWhitelist = true;
|
private boolean requireWhitelist = true;
|
||||||
|
|
||||||
|
transient private OAuth2RegistryStore registryStore;
|
||||||
|
transient private List<OAuth2TokenRegistrationListener> tokenListeners = new ArrayList<>();
|
||||||
transient private Random random = new Random();
|
transient private Random random = new Random();
|
||||||
transient private TokenRegistrationListener tokenListener;
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an in memory only OAuth2 registry.
|
||||||
|
*/
|
||||||
|
public OAuth2Registry() {}
|
||||||
|
/**
|
||||||
|
* Create new OAuth2 registry with an offline backup store.
|
||||||
|
*/
|
||||||
|
public OAuth2Registry(OAuth2RegistryStore store) {
|
||||||
|
this.registryStore = store;
|
||||||
|
|
||||||
|
for (OAuth2ClientRegister register : store.getClientRegistries()) {
|
||||||
|
clientRegisters.put(register.clientId, register);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
// Whitelist methods
|
// Whitelist methods
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
|
|
@ -70,7 +85,10 @@ public class OAuth2Registry implements Serializable {
|
||||||
*/
|
*/
|
||||||
public void addWhitelist(String clientId) {
|
public void addWhitelist(String clientId) {
|
||||||
if (!clientRegisters.containsKey(clientId)) {
|
if (!clientRegisters.containsKey(clientId)) {
|
||||||
clientRegisters.put(clientId, new ClientRegister());
|
OAuth2ClientRegister clientRegister = new OAuth2ClientRegister(clientId);
|
||||||
|
|
||||||
|
clientRegisters.put(clientId, clientRegister);
|
||||||
|
if (registryStore != null) registryStore.storeClientRegister(clientRegister);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +97,7 @@ public class OAuth2Registry implements Serializable {
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a client_id value is a valid value and is in the whitelist fro approved clients.
|
* Validates a client_id value is a valid value and is in the whitelist from approved clients.
|
||||||
*
|
*
|
||||||
* @param clientId the client_id value to validate
|
* @param clientId the client_id value to validate
|
||||||
* @return true if the client_id is allowed to start the OAuth process.
|
* @return true if the client_id is allowed to start the OAuth process.
|
||||||
|
|
@ -94,33 +112,33 @@ public class OAuth2Registry implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that a authorization code has valid format and has been authorized and not elapsed.
|
* Validates that an authorization code has valid format and has been authorized and not elapsed.
|
||||||
*
|
*
|
||||||
* @param code the code that should be validated
|
* @param code the code that should be validated
|
||||||
* @return true if the given code is valid otherwise false.
|
* @return true if the given code is valid otherwise false.
|
||||||
*/
|
*/
|
||||||
public boolean isAuthorizationCodeValid(String code) {
|
public boolean isAuthorizationCodeValid(String code) {
|
||||||
ClientRegister reg = getClientRegisterForAuthCode(code);
|
OAuth2ClientRegister clientRegister = getClientRegisterForAuthCode(code);
|
||||||
|
|
||||||
if (reg != null) {
|
if (clientRegister != null) {
|
||||||
return reg.authCodes.containsKey(code) &&
|
return clientRegister.authCodes.containsKey(code) &&
|
||||||
!reg.authCodes.get(code).hasTimedOut();
|
!clientRegister.authCodes.get(code).hasTimedOut();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that a access token has valid format and has been authorized and not elapsed.
|
* Validates that an access token has valid format and has been authorized and not elapsed.
|
||||||
*
|
*
|
||||||
* @param token the token that should be validated
|
* @param token the token that should be validated
|
||||||
* @return true if the given token is valid otherwise false.
|
* @return true if the given token is valid otherwise false.
|
||||||
*/
|
*/
|
||||||
public boolean isAccessTokenValid(String token) {
|
public boolean isAccessTokenValid(String token) {
|
||||||
ClientRegister reg = getClientRegisterForToken(token);
|
OAuth2ClientRegister clientRegister = getClientRegisterForToken(token);
|
||||||
|
|
||||||
if (reg != null) {
|
if (clientRegister != null) {
|
||||||
return reg.accessTokens.containsKey(token) &&
|
return clientRegister.accessTokens.containsKey(token) &&
|
||||||
!reg.accessTokens.get(token).hasTimedOut();
|
!clientRegister.accessTokens.get(token).hasTimedOut();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -130,10 +148,11 @@ public class OAuth2Registry implements Serializable {
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
|
|
||||||
public void revokeAuthorizationCode(String code) {
|
public void revokeAuthorizationCode(String code) {
|
||||||
ClientRegister reg = getClientRegisterForAuthCode(code);
|
OAuth2ClientRegister clientRegister = getClientRegisterForAuthCode(code);
|
||||||
|
|
||||||
if (reg != null) {
|
if (clientRegister != null) {
|
||||||
reg.authCodes.remove(code);
|
clientRegister.authCodes.remove(code);
|
||||||
|
if (registryStore != null) registryStore.storeClientRegister(clientRegister);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,10 +164,12 @@ public class OAuth2Registry implements Serializable {
|
||||||
return registerAuthorizationCode(clientId, code, DEFAULT_CODE_TIMEOUT);
|
return registerAuthorizationCode(clientId, code, DEFAULT_CODE_TIMEOUT);
|
||||||
}
|
}
|
||||||
protected long registerAuthorizationCode(String clientId, String code, long timeoutMillis) {
|
protected long registerAuthorizationCode(String clientId, String code, long timeoutMillis) {
|
||||||
ClientRegister reg = getClientRegister(clientId);
|
OAuth2ClientRegister clientRegister = getClientRegister(clientId);
|
||||||
|
|
||||||
if (reg != null) {
|
if (clientRegister != null) {
|
||||||
reg.authCodes.put(code, new Timer(timeoutMillis).start());
|
clientRegister.authCodes.put(code, new Timer(timeoutMillis).start());
|
||||||
|
|
||||||
|
if (registryStore != null) registryStore.storeClientRegister(clientRegister);
|
||||||
return timeoutMillis;
|
return timeoutMillis;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -158,13 +179,19 @@ public class OAuth2Registry implements Serializable {
|
||||||
return registerAccessToken(clientId, token, DEFAULT_TOKEN_TIMEOUT);
|
return registerAccessToken(clientId, token, DEFAULT_TOKEN_TIMEOUT);
|
||||||
}
|
}
|
||||||
protected long registerAccessToken(String clientId, String token, long timeoutMillis) {
|
protected long registerAccessToken(String clientId, String token, long timeoutMillis) {
|
||||||
ClientRegister reg = getClientRegister(clientId);
|
OAuth2ClientRegister clientRegister = getClientRegister(clientId);
|
||||||
|
|
||||||
if (reg != null) {
|
if (clientRegister != null) {
|
||||||
reg.accessTokens.put(token, new Timer(timeoutMillis).start());
|
long absoluteTimeout = System.currentTimeMillis() + timeoutMillis;
|
||||||
|
clientRegister.accessTokens.put(token, new Timer(timeoutMillis).start());
|
||||||
|
|
||||||
if (tokenListener != null)
|
if (registryStore != null) registryStore.storeClientRegister(clientRegister);
|
||||||
tokenListener.onTokenRegistration(clientId, token, timeoutMillis);
|
|
||||||
|
if (tokenListeners != null) {
|
||||||
|
for (OAuth2TokenRegistrationListener listener : tokenListeners) {
|
||||||
|
listener.onTokenRegistration(clientId, token, absoluteTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
return timeoutMillis;
|
return timeoutMillis;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -212,45 +239,46 @@ public class OAuth2Registry implements Serializable {
|
||||||
// Listeners
|
// Listeners
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
|
|
||||||
public void setTokenListener(TokenRegistrationListener listener) {
|
public void addTokenListener(OAuth2TokenRegistrationListener listener) {
|
||||||
this.tokenListener = listener;
|
if (!this.tokenListeners.contains(listener))
|
||||||
|
this.tokenListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface TokenRegistrationListener {
|
public interface OAuth2TokenRegistrationListener {
|
||||||
/**
|
/**
|
||||||
* Method will be called when a new token is successfully registered on a client
|
* Method will be called when a new token is successfully registered on a client
|
||||||
*
|
*
|
||||||
* @param clientId the client ID that got the specified token
|
* @param clientId the client ID that got the specified token
|
||||||
* @param token a String token that has ben generated and provided to the client
|
* @param token a String token that has been generated and provided to the client
|
||||||
* @param timeoutMillis the expiration time of the token
|
* @param timeoutMillis the expiration epoc time of the token
|
||||||
*/
|
*/
|
||||||
void onTokenRegistration(String clientId, String token, long timeoutMillis);
|
void onTokenRegistration(String clientId, String token, long timeoutMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// Client register logic
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
|
|
||||||
private ClientRegister getClientRegister(String clientId) {
|
private OAuth2ClientRegister getClientRegister(String clientId) {
|
||||||
if (!requireWhitelist && !clientRegisters.containsKey(clientId))
|
if (!requireWhitelist && !clientRegisters.containsKey(clientId)) {
|
||||||
clientRegisters.put(clientId, new ClientRegister());
|
OAuth2ClientRegister clientRegister = new OAuth2ClientRegister(clientId);
|
||||||
|
|
||||||
|
clientRegisters.put(clientId, clientRegister);
|
||||||
|
if (registryStore != null) registryStore.storeClientRegister(clientRegister);
|
||||||
|
}
|
||||||
|
|
||||||
return clientRegisters.get(clientId);
|
return clientRegisters.get(clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientRegister getClientRegisterForAuthCode(String code) {
|
private OAuth2ClientRegister getClientRegisterForAuthCode(String code) {
|
||||||
String clientId = getClientIdForAuthenticationCode(code);
|
String clientId = getClientIdForAuthenticationCode(code);
|
||||||
|
|
||||||
return (clientId == null ? null : clientRegisters.get(clientId));
|
return (clientId == null ? null : clientRegisters.get(clientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientRegister getClientRegisterForToken(String token) {
|
private OAuth2ClientRegister getClientRegisterForToken(String token) {
|
||||||
String clientId = getClientIdForAccessToken(token);
|
String clientId = getClientIdForAccessToken(token);
|
||||||
|
|
||||||
return (clientId == null ? null : clientRegisters.get(clientId));
|
return (clientId == null ? null : clientRegisters.get(clientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class ClientRegister implements Serializable {
|
|
||||||
Map<String, Timer> authCodes = new HashMap<>();
|
|
||||||
Map<String, Timer> accessTokens = new HashMap<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
57
src/zutil/net/http/page/oauth/OAuth2RegistryStore.java
Normal file
57
src/zutil/net/http/page/oauth/OAuth2RegistryStore.java
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package zutil.net.http.page.oauth;
|
||||||
|
|
||||||
|
import zutil.Timer;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A storage interface for the {@link OAuth2Registry} class. This interface
|
||||||
|
* is mainly used to save any changed client states and to load client data.
|
||||||
|
*/
|
||||||
|
public interface OAuth2RegistryStore {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all previously stored client registries.
|
||||||
|
*/
|
||||||
|
List<OAuth2ClientRegister> getClientRegistries();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client registry has been updated and needs to be stored for later retrieval.
|
||||||
|
*
|
||||||
|
* @param register the register object that has changed and needs to be stored.
|
||||||
|
*/
|
||||||
|
void storeClientRegister(OAuth2ClientRegister register);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This data class contains OAuth data related to a single client.
|
||||||
|
*/
|
||||||
|
class OAuth2ClientRegister implements Serializable {
|
||||||
|
public String clientId;
|
||||||
|
public HashMap<String, Timer> authCodes = new HashMap<>();
|
||||||
|
public HashMap<String, Timer> accessTokens = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public OAuth2ClientRegister(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
OAuth2ClientRegister that = (OAuth2ClientRegister) o;
|
||||||
|
return Objects.equals(clientId, that.clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
package zutil.parser.json;
|
package zutil.parser.json;
|
||||||
|
|
||||||
import zutil.ClassUtil;
|
import zutil.ClassUtil;
|
||||||
|
import zutil.io.StringInputStream;
|
||||||
|
import zutil.io.StringOutputStream;
|
||||||
import zutil.log.LogUtil;
|
import zutil.log.LogUtil;
|
||||||
import zutil.parser.Base64Decoder;
|
import zutil.parser.Base64Decoder;
|
||||||
import zutil.parser.DataNode;
|
import zutil.parser.DataNode;
|
||||||
|
|
@ -55,6 +57,23 @@ public class JSONObjectInputStream extends InputStream implements ObjectInput, C
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a String containing the JSON representation of the Object
|
||||||
|
*/
|
||||||
|
public static <T> T parse(String json) {
|
||||||
|
try {
|
||||||
|
StringInputStream in = new StringInputStream();
|
||||||
|
JSONObjectInputStream reader = new JSONObjectInputStream(in);
|
||||||
|
T object = reader.readGenericObject();
|
||||||
|
reader.close();
|
||||||
|
return object;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If no metadata is available in the stream then this
|
* If no metadata is available in the stream then this
|
||||||
* class will be instantiated and assigned data from the received JSON.
|
* class will be instantiated and assigned data from the received JSON.
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ public class JSONObjectOutputStream extends OutputStream implements ObjectOutput
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public synchronized void writeObject(Object obj) throws IOException{
|
public synchronized void writeObject(Object obj) throws IOException{
|
||||||
try {
|
try {
|
||||||
out.write(getDataNode(obj));
|
out.write(getDataNode(obj));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue