Last active
November 7, 2017 13:51
-
-
Save aaronanderson/deda33e2bbd7bd3189fff55b7c3e0a28 to your computer and use it in GitHub Desktop.
Sample code for OAuth2 password authentication with only JAX-RS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@ApplicationScoped | |
public class OIDCClient { | |
private final AtomicReference<String> refreshRef = new AtomicReference<>(); | |
private final ReadWriteLock tokenLock = new ReentrantReadWriteLock(); | |
private OIDCToken oidcToken; | |
private String refreshToken; | |
@Inject | |
@ConfigurationValue("oidc.idpURL") | |
private String idpURL; | |
@PostConstruct | |
public void init() { | |
client = buildClient(); | |
} | |
public String getCurrentRefreshToken() throws IOException { | |
if (refreshToken == null) { | |
try { | |
String newRefreshToken = getRefreshToken(); | |
if (refreshRef.compareAndSet(null, newRefreshToken)) { | |
refreshToken = newRefreshToken; | |
} | |
} catch (IOException e) { | |
logger.log(Level.SEVERE, "Workspace Security Setup error", e); | |
} | |
} | |
return refreshToken; | |
} | |
public String getRefreshToken() throws IOException { | |
/*EntityManager em = null; | |
try { | |
em = getEntityManagerFactory().createEntityManager(); | |
RefreshToken token = em.find(RefreshToken.class, 1l); | |
if (token != null) { | |
return token.getToken(); | |
} | |
} catch (Exception e) { | |
logger.log(Level.SEVERE, " token error", e); | |
} finally { | |
if (em != null && em.isOpen()) { | |
em.close(); | |
} | |
}*/ | |
return null; | |
} | |
public void setRefreshToken(String token) throws IOException { | |
/*EntityManager em = null; | |
try { | |
em = getEntityManagerFactory().createEntityManager(); | |
em.getTransaction().begin(); | |
RefreshToken rToken = new RefreshToken().withId(1l).withToken(token); | |
em.merge(rToken); | |
em.getTransaction().commit(); | |
} catch (Exception e) { | |
logger.log(Level.SEVERE, " token error", e); | |
em.getTransaction().rollback(); | |
} finally { | |
if (em != null && em.isOpen()) { | |
em.close(); | |
} | |
}*/ | |
} | |
public String getAccessToken() { | |
if (refreshToken == null) { | |
try { | |
refreshToken = getCurrentRefreshToken(); | |
} catch (IOException e) { | |
logger.log(Level.SEVERE, "auth setup error", e); | |
} | |
} | |
if ((oidcToken == null || LocalDateTime.now().isAfter(oidcToken.timeoutTime.minusSeconds(30))) && refreshToken != null) { | |
tokenLock.writeLock().lock(); | |
try { | |
if ((oidcToken == null || LocalDateTime.now().isAfter(oidcToken.timeoutTime.minusSeconds(30))) && refreshToken != null) { | |
oidcToken = retrieveAccessTokenSimple().orElse(null); | |
} | |
} finally { | |
tokenLock.writeLock().unlock(); | |
} | |
} | |
tokenLock.readLock().lock(); | |
try { | |
return oidcToken != null ? oidcToken.token : null; | |
} finally { | |
tokenLock.readLock().unlock(); | |
} | |
} | |
// OAuth redirect is used now instead of native authentication. | |
public boolean testAuthenticate(String account, String password) { | |
Optional<JsonObject> result = oidcAuthenticate(account, password, "openid"); | |
return result.isPresent() && result.get().containsKey("access_token"); | |
} | |
public Optional<OIDCToken> retrieveAccessTokenSimple() { | |
Client oauthClient = ClientBuilder.newBuilder().build(); | |
try { | |
oauthClient.register(new OIDCAuthenticator(OIDCAuthenticator.Mode.Basic, () -> String.format("%s:%s", clientId, clientSecret))); | |
Form form = new Form(); | |
form.param("grant_type", "refresh_token"); | |
form.param("refresh_token", getCurrentRefreshToken()); | |
form.param("scope", "openid profile email groups offline_access"); | |
Response response = oauthClient.target(idpURL).path("/oauth2/v1/token").request().post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); | |
if (response.getStatus() == Response.Status.OK.getStatusCode()) { | |
JsonObject payload = response.readEntity(JsonObject.class); | |
String token = payload.getString("access_token"); | |
long expiry = payload.getJsonNumber("expires_in").longValue(); | |
return Optional.of(new OIDCToken(token, LocalDateTime.now().plusSeconds(expiry))); | |
} else { | |
logger.log(Level.SEVERE, String.format("Invalid OIDC response %d %s", response.getStatus(), response.readEntity(String.class))); | |
} | |
} catch (Throwable t) { | |
logger.log(Level.SEVERE, "OIDC ", t); | |
} finally { | |
oauthClient.close(); | |
} | |
return Optional.empty(); | |
} | |
public boolean retrieveTokens(String account, String password) { | |
Optional<JsonObject> result = oidcAuthenticate(account, password, "openid profile email groups offline_access"); | |
if (result.isPresent() && result.get().containsKey("refresh_token")) { | |
refreshToken = result.get().getString("refresh_token"); | |
try { | |
setRefreshToken(refreshToken); | |
} catch (Throwable t) { | |
logger.log(Level.SEVERE, "", t); | |
} | |
if (result.get().containsKey("access_token")) { | |
tokenLock.writeLock().lock(); | |
try { | |
String token = result.get().getString("access_token"); | |
long expiry = result.get().getJsonNumber("expires_in").longValue(); | |
oidcToken = new OIDCToken(token, LocalDateTime.now().plusSeconds(expiry)); | |
} finally { | |
tokenLock.writeLock().unlock(); | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
public Optional<JsonObject> oidcAuthenticate(String account, String password, String scope) { | |
Client oauthClient = ClientBuilder.newBuilder().build(); | |
try { | |
oauthClient.register(new OIDCAuthenticator(OIDCAuthenticator.Mode.Basic, () -> String.format("%s:%s", clientId, clientSecret))); | |
Form form = new Form(); | |
form.param("grant_type", "password"); | |
form.param("username", account); | |
form.param("password", password); | |
form.param("scope", scope); | |
Response response = oauthClient.target(idpURL).path("/oauth2/v1/token").request().post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); | |
if (response.getStatus() == Response.Status.OK.getStatusCode()) { | |
return Optional.of(response.readEntity(JsonObject.class)); | |
} else { | |
logger.log(Level.SEVERE, String.format("Invalid OIDC response %d %s", response.getStatus(), response.readEntity(String.class))); | |
} | |
} catch (Throwable t) { | |
logger.log(Level.SEVERE, "OIDC ", t); | |
} finally { | |
oauthClient.close(); | |
} | |
return Optional.empty(); | |
} | |
@Provider | |
public static class OIDCAuthenticator implements ClientRequestFilter { | |
private final Mode mode; | |
private final Supplier<String> tokenGenerator; | |
public OIDCAuthenticator(Mode mode, Supplier<String> tokenGenerator) { | |
this.mode = mode; | |
this.tokenGenerator = tokenGenerator; | |
} | |
public void filter(ClientRequestContext requestContext) throws IOException { | |
try { | |
String token = tokenGenerator.get(); | |
if (token == null || token.isEmpty()) { | |
throw new IOException("Empty auth token"); | |
} | |
final String authorization = mode + " " + (mode == Mode.Basic ? Base64.getEncoder().encodeToString(token.getBytes()) : token); | |
MultivaluedMap<String, Object> headers = requestContext.getHeaders(); | |
headers.add("Authorization", authorization); | |
} catch (IllegalArgumentException ex) { | |
throw new IOException(ex); | |
} | |
} | |
public static enum Mode { | |
Basic, Bearer; | |
} | |
} | |
public static class OIDCToken { | |
final String token; | |
final LocalDateTime timeoutTime; | |
public OIDCToken(String token, LocalDateTime timeoutTime) { | |
this.token = token; | |
this.timeoutTime = timeoutTime; | |
} | |
public String getToken() { | |
return token; | |
} | |
} | |
public boolean isDevStage() { | |
return false;//return ProjectStage.Development.equals(getProjectStage()); | |
} | |
public boolean oauthRequired() { | |
if (!isDevStage()) { | |
return true; | |
} | |
} | |
private Client buildClient() { | |
ResteasyClientBuilder builder = new ResteasyClientBuilder(); | |
if (isDevStage()) { | |
builder.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY); | |
builder.disableTrustManager(); | |
} else { | |
try { | |
SSLContext sslContext = getSSLContext(); | |
builder.sslContext(sslContext); | |
} catch (Throwable t) { | |
logger.log(Level.SEVERE, "JAX-RS client TLS setup error ", t); | |
return null; | |
} | |
} | |
Client client = builder.connectionPoolSize(200).build(); | |
// Client client = ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier(hostnameVerifier).build(); | |
// do not enable OIDC authentication for the development environment | |
if (oauthRequired()) { | |
OIDCAuthenticator auth = new OIDCAuthenticator(OIDCAuthenticator.Mode.Bearer, this::getAccessToken); | |
return client.register(auth); | |
} | |
return client; | |
} | |
private static SSLContext sslContext; | |
public static synchronized SSLContext getSSLContext() throws Exception { | |
if (sslContext == null) { | |
InputStream fis = Thread.currentThread().getContextClassLoader().getResourceAsStream("oauthclient.jks");// "swarm.cer" | |
// CertificateFactory cf = CertificateFactory.getInstance("X.509"); | |
// Certificate cert = cf.generateCertificate(fis); | |
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
// keyStore.load(null, null); | |
keyStore.load(fis, "XXXXXXX".toCharArray()); | |
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | |
// keyStore.setCertificateEntry("OAUTHCert", cert); | |
tmf.init(keyStore); | |
sslContext = SSLContext.getInstance("TLS"); | |
sslContext.init(null, tmf.getTrustManagers(), null); | |
} | |
return sslContext; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment