Added a OAuth2 Store to save state between application restarts

This commit is contained in:
Ziver Koc 2021-09-09 17:51:16 +02:00
parent 8ba1441572
commit 8508df42ac
4 changed files with 149 additions and 44 deletions

View file

@ -26,11 +26,10 @@ package zutil.net.http.page.oauth;
import zutil.Hasher;
import zutil.Timer;
import zutil.net.http.page.oauth.OAuth2RegistryStore.OAuth2ClientRegister;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.*;
/**
* 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_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;
transient private OAuth2RegistryStore registryStore;
transient private List<OAuth2TokenRegistrationListener> tokenListeners = new ArrayList<>();
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
// ------------------------------------------------------
@ -70,7 +85,10 @@ public class OAuth2Registry implements Serializable {
*/
public void addWhitelist(String 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
* @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
* @return true if the given code is valid otherwise false.
*/
public boolean isAuthorizationCodeValid(String code) {
ClientRegister reg = getClientRegisterForAuthCode(code);
OAuth2ClientRegister clientRegister = getClientRegisterForAuthCode(code);
if (reg != null) {
return reg.authCodes.containsKey(code) &&
!reg.authCodes.get(code).hasTimedOut();
if (clientRegister != null) {
return clientRegister.authCodes.containsKey(code) &&
!clientRegister.authCodes.get(code).hasTimedOut();
}
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
* @return true if the given token is valid otherwise false.
*/
public boolean isAccessTokenValid(String token) {
ClientRegister reg = getClientRegisterForToken(token);
OAuth2ClientRegister clientRegister = getClientRegisterForToken(token);
if (reg != null) {
return reg.accessTokens.containsKey(token) &&
!reg.accessTokens.get(token).hasTimedOut();
if (clientRegister != null) {
return clientRegister.accessTokens.containsKey(token) &&
!clientRegister.accessTokens.get(token).hasTimedOut();
}
return false;
}
@ -130,10 +148,11 @@ public class OAuth2Registry implements Serializable {
// ------------------------------------------------------
public void revokeAuthorizationCode(String code) {
ClientRegister reg = getClientRegisterForAuthCode(code);
OAuth2ClientRegister clientRegister = getClientRegisterForAuthCode(code);
if (reg != null) {
reg.authCodes.remove(code);
if (clientRegister != null) {
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);
}
protected long registerAuthorizationCode(String clientId, String code, long timeoutMillis) {
ClientRegister reg = getClientRegister(clientId);
OAuth2ClientRegister clientRegister = getClientRegister(clientId);
if (reg != null) {
reg.authCodes.put(code, new Timer(timeoutMillis).start());
if (clientRegister != null) {
clientRegister.authCodes.put(code, new Timer(timeoutMillis).start());
if (registryStore != null) registryStore.storeClientRegister(clientRegister);
return timeoutMillis;
}
return -1;
@ -158,13 +179,19 @@ public class OAuth2Registry implements Serializable {
return registerAccessToken(clientId, token, DEFAULT_TOKEN_TIMEOUT);
}
protected long registerAccessToken(String clientId, String token, long timeoutMillis) {
ClientRegister reg = getClientRegister(clientId);
OAuth2ClientRegister clientRegister = getClientRegister(clientId);
if (reg != null) {
reg.accessTokens.put(token, new Timer(timeoutMillis).start());
if (clientRegister != null) {
long absoluteTimeout = System.currentTimeMillis() + timeoutMillis;
clientRegister.accessTokens.put(token, new Timer(timeoutMillis).start());
if (tokenListener != null)
tokenListener.onTokenRegistration(clientId, token, timeoutMillis);
if (registryStore != null) registryStore.storeClientRegister(clientRegister);
if (tokenListeners != null) {
for (OAuth2TokenRegistrationListener listener : tokenListeners) {
listener.onTokenRegistration(clientId, token, absoluteTimeout);
}
}
return timeoutMillis;
}
return -1;
@ -212,45 +239,46 @@ public class OAuth2Registry implements Serializable {
// Listeners
// ------------------------------------------------------
public void setTokenListener(TokenRegistrationListener listener) {
this.tokenListener = listener;
public void addTokenListener(OAuth2TokenRegistrationListener 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
*
* @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 timeoutMillis the expiration time of the token
* @param token a String token that has been generated and provided to the client
* @param timeoutMillis the expiration epoc time of the token
*/
void onTokenRegistration(String clientId, String token, long timeoutMillis);
}
// ------------------------------------------------------
// Client register logic
// ------------------------------------------------------
private ClientRegister getClientRegister(String clientId) {
if (!requireWhitelist && !clientRegisters.containsKey(clientId))
clientRegisters.put(clientId, new ClientRegister());
private OAuth2ClientRegister getClientRegister(String clientId) {
if (!requireWhitelist && !clientRegisters.containsKey(clientId)) {
OAuth2ClientRegister clientRegister = new OAuth2ClientRegister(clientId);
clientRegisters.put(clientId, clientRegister);
if (registryStore != null) registryStore.storeClientRegister(clientRegister);
}
return clientRegisters.get(clientId);
}
private ClientRegister getClientRegisterForAuthCode(String code) {
private OAuth2ClientRegister getClientRegisterForAuthCode(String code) {
String clientId = getClientIdForAuthenticationCode(code);
return (clientId == null ? null : clientRegisters.get(clientId));
}
private ClientRegister getClientRegisterForToken(String token) {
private OAuth2ClientRegister getClientRegisterForToken(String token) {
String clientId = getClientIdForAccessToken(token);
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<>();
}
}

View 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);
}
}
}

View file

@ -25,6 +25,8 @@
package zutil.parser.json;
import zutil.ClassUtil;
import zutil.io.StringInputStream;
import zutil.io.StringOutputStream;
import zutil.log.LogUtil;
import zutil.parser.Base64Decoder;
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
* class will be instantiated and assigned data from the received JSON.

View file

@ -79,6 +79,7 @@ public class JSONObjectOutputStream extends OutputStream implements ObjectOutput
return null;
}
public synchronized void writeObject(Object obj) throws IOException{
try {
out.write(getDataNode(obj));