Added GitHub provider. Simplified FaceBook provider
This commit is contained in:
parent
2ad873a87f
commit
8b5fbe92d6
14 changed files with 290 additions and 249 deletions
|
@ -10,6 +10,7 @@
|
||||||
<!ENTITY EAP6Adapter SYSTEM "modules/eap6-adapter.xml">
|
<!ENTITY EAP6Adapter SYSTEM "modules/eap6-adapter.xml">
|
||||||
<!ENTITY SocialConfig SYSTEM "modules/social-config.xml">
|
<!ENTITY SocialConfig SYSTEM "modules/social-config.xml">
|
||||||
<!ENTITY SocialFacebook SYSTEM "modules/social-facebook.xml">
|
<!ENTITY SocialFacebook SYSTEM "modules/social-facebook.xml">
|
||||||
|
<!ENTITY SocialGitHub SYSTEM "modules/social-github.xml">
|
||||||
<!ENTITY SocialGoogle SYSTEM "modules/social-google.xml">
|
<!ENTITY SocialGoogle SYSTEM "modules/social-google.xml">
|
||||||
<!ENTITY SocialTwitter SYSTEM "modules/social-twitter.xml">
|
<!ENTITY SocialTwitter SYSTEM "modules/social-twitter.xml">
|
||||||
<!ENTITY SocialProviderSPI SYSTEM "modules/social-spi.xml">
|
<!ENTITY SocialProviderSPI SYSTEM "modules/social-spi.xml">
|
||||||
|
@ -73,6 +74,7 @@
|
||||||
</para>
|
</para>
|
||||||
&SocialConfig;
|
&SocialConfig;
|
||||||
&SocialFacebook;
|
&SocialFacebook;
|
||||||
|
&SocialGitHub;
|
||||||
&SocialGoogle;
|
&SocialGoogle;
|
||||||
&SocialTwitter;
|
&SocialTwitter;
|
||||||
&SocialProviderSPI;
|
&SocialProviderSPI;
|
||||||
|
|
27
docbook/reference/en/en-US/modules/social-github.xml
Normal file
27
docbook/reference/en/en-US/modules/social-github.xml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<section id="social-github">
|
||||||
|
<title>GitHub</title>
|
||||||
|
<para>
|
||||||
|
To enable login with Google you first have to create an application in
|
||||||
|
<ulink url="https://github.com/settings/applications">GitHub Settings</ulink>. Then you need to copy
|
||||||
|
the client id and secret into the Keycloak Admin Console.
|
||||||
|
</para>
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Log in to <ulink url="https://github.com/settings/applications">GitHub Settings</ulink>. Click the
|
||||||
|
<literal>Register new application</literal> button. Use any value for <literal>Application name</literal>,
|
||||||
|
<literal>Homepage URL</literal> and <literal>Application Description</literal> you want. In <literal>Authorization callback URL</literal>
|
||||||
|
enter the <link linkend="social-callbackurl">social callback url</link> for your realm. Click the
|
||||||
|
<literal>Register application</literal> button.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Copy <literal>Client ID</literal> and <literal>Client secret</literal> from the
|
||||||
|
<ulink url="https://github.com/settings/applications">GitHub Settings</ulink> into the settings
|
||||||
|
page in the Keycloak Admin Console as the <literal>Key</literal> and <literal>Secret</literal>. Then click
|
||||||
|
<literal>Save</literal> in the Keycloak Admin Console to enable login with Google.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
</section>
|
|
@ -106,10 +106,10 @@ public class SocialResource {
|
||||||
RequestDetails requestData = getRequestDetails(queryParams);
|
RequestDetails requestData = getRequestDetails(queryParams);
|
||||||
SocialProvider provider = SocialLoader.load(requestData.getProviderId());
|
SocialProvider provider = SocialLoader.load(requestData.getProviderId());
|
||||||
|
|
||||||
String realmId = requestData.getClientAttribute("realmId");
|
String realmName = requestData.getClientAttribute("realm");
|
||||||
|
|
||||||
RealmManager realmManager = new RealmManager(session);
|
RealmManager realmManager = new RealmManager(session);
|
||||||
RealmModel realm = realmManager.getRealm(realmId);
|
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||||
|
|
||||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||||
|
|
||||||
|
@ -186,12 +186,12 @@ public class SocialResource {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{realm}/login")
|
@Path("{realm}/login")
|
||||||
public Response redirectToProviderAuth(@PathParam("realm") final String realmId,
|
public Response redirectToProviderAuth(@PathParam("realm") final String realmName,
|
||||||
@QueryParam("provider_id") final String providerId, @QueryParam("client_id") final String clientId,
|
@QueryParam("provider_id") final String providerId, @QueryParam("client_id") final String clientId,
|
||||||
@QueryParam("scope") final String scope, @QueryParam("state") final String state,
|
@QueryParam("scope") final String scope, @QueryParam("state") final String state,
|
||||||
@QueryParam("redirect_uri") String redirectUri) {
|
@QueryParam("redirect_uri") String redirectUri) {
|
||||||
RealmManager realmManager = new RealmManager(session);
|
RealmManager realmManager = new RealmManager(session);
|
||||||
RealmModel realm = realmManager.getRealm(realmId);
|
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||||
|
|
||||||
SocialProvider provider = SocialLoader.load(providerId);
|
SocialProvider provider = SocialLoader.load(providerId);
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
|
@ -223,7 +223,7 @@ public class SocialResource {
|
||||||
AuthRequest authRequest = provider.getAuthUrl(config);
|
AuthRequest authRequest = provider.getAuthUrl(config);
|
||||||
|
|
||||||
RequestDetails socialRequest = RequestDetails.create(providerId)
|
RequestDetails socialRequest = RequestDetails.create(providerId)
|
||||||
.putSocialAttributes(authRequest.getAttributes()).putClientAttribute("realmId", realmId)
|
.putSocialAttributes(authRequest.getAttributes()).putClientAttribute("realm", realmName)
|
||||||
.putClientAttribute("clientId", clientId).putClientAttribute("scope", scope)
|
.putClientAttribute("clientId", clientId).putClientAttribute("scope", scope)
|
||||||
.putClientAttribute("state", state).putClientAttribute("redirectUri", redirectUri).build();
|
.putClientAttribute("state", state).putClientAttribute("redirectUri", redirectUri).build();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package org.keycloak.social;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.keycloak.social.utils.SimpleHttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractOAuth2Provider implements SocialProvider {
|
||||||
|
|
||||||
|
private static final String AUTHORIZATION_CODE = "authorization_code";
|
||||||
|
private static final String ACCESS_TOKEN = "access_token";
|
||||||
|
private static final String CLIENT_ID = "client_id";
|
||||||
|
private static final String CLIENT_SECRET = "client_secret";
|
||||||
|
private static final String CODE = "code";
|
||||||
|
private static final String GRANT_TYPE = "grant_type";
|
||||||
|
private static final String REDIRECT_URI = "redirect_uri";
|
||||||
|
private static final String RESPONSE_TYPE = "response_type";
|
||||||
|
private static final String SCOPE = "scope";
|
||||||
|
private static final String STATE = "state";
|
||||||
|
|
||||||
|
private static final String TOKEN_REGEX = "access_token=([^&]+)";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getId();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
protected abstract String getScope();
|
||||||
|
|
||||||
|
protected abstract String getAuthUrl();
|
||||||
|
|
||||||
|
protected abstract String getTokenUrl();
|
||||||
|
|
||||||
|
protected abstract SocialUser getProfile(String accessToken) throws SocialProviderException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
|
||||||
|
String state = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
return AuthRequest.create(state, getAuthUrl()).setQueryParam(CLIENT_ID, config.getKey())
|
||||||
|
.setQueryParam(RESPONSE_TYPE, CODE).setQueryParam(SCOPE, getScope())
|
||||||
|
.setQueryParam(REDIRECT_URI, config.getCallbackUrl()).setQueryParam(STATE, state).setAttribute(STATE, state).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
||||||
|
try {
|
||||||
|
String code = callback.getQueryParam(CODE);
|
||||||
|
|
||||||
|
if (!callback.getQueryParam(STATE).equals(callback.getAttribute(STATE))) {
|
||||||
|
throw new SocialProviderException("Invalid state");
|
||||||
|
}
|
||||||
|
|
||||||
|
String response = SimpleHttp.doPost(getTokenUrl()).param(CODE, code).param(CLIENT_ID, config.getKey())
|
||||||
|
.param(CLIENT_SECRET, config.getSecret())
|
||||||
|
.param(REDIRECT_URI, config.getCallbackUrl())
|
||||||
|
.param(GRANT_TYPE, AUTHORIZATION_CODE).asString();
|
||||||
|
|
||||||
|
String accessToken;
|
||||||
|
|
||||||
|
if (response.startsWith("{")) {
|
||||||
|
accessToken = new JSONObject(response).getString(ACCESS_TOKEN);
|
||||||
|
} else {
|
||||||
|
Matcher matcher = Pattern.compile(TOKEN_REGEX).matcher(response);
|
||||||
|
if (matcher.find()) {
|
||||||
|
accessToken = matcher.group(1);
|
||||||
|
} else {
|
||||||
|
throw new SocialProviderException("Invalid response, could not find token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getProfile(accessToken);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SocialProviderException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequestIdParamName() {
|
||||||
|
return STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,17 +18,6 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-social-core</artifactId>
|
<artifactId>keycloak-social-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.codehaus.jackson</groupId>
|
|
||||||
<artifactId>jackson-core-asl</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -1,149 +1,81 @@
|
||||||
package org.keycloak.social.facebook;
|
package org.keycloak.social.facebook;
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
import org.json.JSONObject;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
import org.keycloak.social.AbstractOAuth2Provider;
|
||||||
import org.keycloak.social.AuthCallback;
|
|
||||||
import org.keycloak.social.AuthRequest;
|
import org.keycloak.social.AuthRequest;
|
||||||
import org.keycloak.social.SocialProvider;
|
|
||||||
import org.keycloak.social.SocialProviderConfig;
|
import org.keycloak.social.SocialProviderConfig;
|
||||||
import org.keycloak.social.SocialProviderException;
|
import org.keycloak.social.SocialProviderException;
|
||||||
import org.keycloak.social.SocialUser;
|
import org.keycloak.social.SocialUser;
|
||||||
|
import org.keycloak.social.utils.SimpleHttp;
|
||||||
import javax.ws.rs.client.Entity;
|
|
||||||
import javax.ws.rs.core.Form;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Social provider for Facebook
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
*/
|
||||||
public class FacebookProvider implements SocialProvider {
|
public class FacebookProvider extends AbstractOAuth2Provider {
|
||||||
|
|
||||||
private static final String AUTHENTICATION_ENDPOINT_URL = "https://graph.facebook.com/oauth/authorize";
|
private static final String ID = "facebook";
|
||||||
|
private static final String NAME = "Facebook";
|
||||||
|
|
||||||
private static final String ACCESS_TOKEN_ENDPOINT_URL = "https://graph.facebook.com/oauth/access_token";
|
private static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize";
|
||||||
|
private static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token";
|
||||||
private static final String PROFILE_ENDPOINT_URL = "https://graph.facebook.com/me";
|
private static final String PROFILE_URL = "https://graph.facebook.com/me";
|
||||||
|
|
||||||
private static final String DEFAULT_RESPONSE_TYPE = "code";
|
|
||||||
|
|
||||||
private static final String DEFAULT_SCOPE = "email";
|
private static final String DEFAULT_SCOPE = "email";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "facebook";
|
return ID;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
|
|
||||||
String state = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
String redirectUri = config.getCallbackUrl();
|
|
||||||
redirectUri = redirectUri.replace("//localhost", "//127.0.0.1");
|
|
||||||
|
|
||||||
return AuthRequest.create(state, AUTHENTICATION_ENDPOINT_URL).setQueryParam("client_id", config.getKey())
|
|
||||||
.setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
|
|
||||||
.setQueryParam("redirect_uri", redirectUri).setQueryParam("state", state).setAttribute("state", state).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRequestIdParamName() {
|
|
||||||
return "state";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Facebook";
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
protected String getScope() {
|
||||||
String code = callback.getQueryParam(DEFAULT_RESPONSE_TYPE);
|
return DEFAULT_SCOPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAuthUrl() {
|
||||||
|
return AUTH_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTokenUrl() {
|
||||||
|
return TOKEN_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SocialUser getProfile(String accessToken) throws SocialProviderException {
|
||||||
try {
|
try {
|
||||||
if (!callback.getQueryParam("state").equals(callback.getAttribute("state"))) {
|
JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
|
||||||
throw new SocialProviderException("Invalid state");
|
|
||||||
|
SocialUser user = new SocialUser(profile.getString("id"));
|
||||||
|
|
||||||
|
user.setUsername(profile.getString("username"));
|
||||||
|
if (user.getUsername() == null || user.getUsername().length() == 0) {
|
||||||
|
user.setUsername(profile.getString("id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
ResteasyClient client = new ResteasyClientBuilder()
|
user.setFirstName(profile.optString("first_name"));
|
||||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
|
user.setLastName(profile.optString("last_name"));
|
||||||
|
user.setEmail(profile.optString("email"));
|
||||||
|
|
||||||
String accessToken = loadAccessToken(code, config, client);
|
return user;
|
||||||
|
|
||||||
FacebookUser facebookUser = loadUser(accessToken, client);
|
|
||||||
|
|
||||||
SocialUser socialUser = new SocialUser(facebookUser.getId());
|
|
||||||
socialUser.setUsername(facebookUser.getUsername());
|
|
||||||
|
|
||||||
// This could happen with Facebook testing users
|
|
||||||
if (facebookUser.getUsername() == null || facebookUser.getUsername().length() == 0) {
|
|
||||||
socialUser.setUsername(facebookUser.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
socialUser.setEmail(facebookUser.getEmail());
|
|
||||||
socialUser.setLastName(facebookUser.getLastName());
|
|
||||||
socialUser.setFirstName(facebookUser.getFirstName());
|
|
||||||
|
|
||||||
return socialUser;
|
|
||||||
} catch (SocialProviderException spe) {
|
|
||||||
throw spe;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new SocialProviderException(e);
|
throw new SocialProviderException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String loadAccessToken(String code, SocialProviderConfig config, ResteasyClient client) throws SocialProviderException {
|
@Override
|
||||||
Form form = new Form();
|
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
|
||||||
form.param("grant_type", "authorization_code")
|
if (config.getCallbackUrl().contains("//localhost")) {
|
||||||
.param("code", code)
|
String callbackUrl = config.getCallbackUrl().replace("//localhost", "//127.0.0.1");
|
||||||
.param("client_id", config.getKey())
|
config = new SocialProviderConfig(config.getKey(), config.getSecret(), callbackUrl);
|
||||||
.param("client_secret", config.getSecret())
|
|
||||||
.param("redirect_uri", config.getCallbackUrl());
|
|
||||||
|
|
||||||
Response response = client.target(ACCESS_TOKEN_ENDPOINT_URL).request().post(Entity.form(form));
|
|
||||||
|
|
||||||
if (response.getStatus() != 200) {
|
|
||||||
String errorTokenResponse = response.readEntity(String.class);
|
|
||||||
throw new SocialProviderException("Access token request to Facebook failed. Status: " + response.getStatus() + ", response: " + errorTokenResponse);
|
|
||||||
}
|
}
|
||||||
|
return super.getAuthUrl(config);
|
||||||
String accessTokenResponse = response.readEntity(String.class);
|
|
||||||
return parseParameter(accessTokenResponse, "access_token");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected FacebookUser loadUser(String accessToken, ResteasyClient client) throws SocialProviderException {
|
|
||||||
URI userDetailsUri = UriBuilder.fromUri(PROFILE_ENDPOINT_URL)
|
|
||||||
.queryParam("access_token", accessToken)
|
|
||||||
.queryParam("fields", "id,name,username,first_name,last_name,email")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Response response = client.target(userDetailsUri).request()
|
|
||||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
|
|
||||||
.get();
|
|
||||||
if (response.getStatus() != 200) {
|
|
||||||
String errorTokenResponse = response.readEntity(String.class);
|
|
||||||
throw new SocialProviderException("Request to Facebook for obtaining user failed. Status: " + response.getStatus() + ", response: " + errorTokenResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.readEntity(FacebookUser.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses value of given parameter from input string like "my_param=abcd&another_param=xyz"
|
|
||||||
private String parseParameter(String input, String paramName) {
|
|
||||||
int start = input.indexOf(paramName + "=");
|
|
||||||
if (start != -1) {
|
|
||||||
input = input.substring(start + paramName.length() + 1);
|
|
||||||
int end = input.indexOf("&");
|
|
||||||
return end==-1 ? input : input.substring(0, end);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Parameter " + paramName + " not available in response " + input);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
package org.keycloak.social.facebook;
|
|
||||||
|
|
||||||
import org.codehaus.jackson.annotate.JsonProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap info about user from Facebook
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class FacebookUser {
|
|
||||||
|
|
||||||
@JsonProperty("id")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@JsonProperty("first_name")
|
|
||||||
private String firstName;
|
|
||||||
|
|
||||||
@JsonProperty("last_name")
|
|
||||||
private String lastName;
|
|
||||||
|
|
||||||
@JsonProperty("username")
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@JsonProperty("name")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@JsonProperty("email")
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFirstName() {
|
|
||||||
return firstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFirstName(String firstName) {
|
|
||||||
this.firstName = firstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastName() {
|
|
||||||
return lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastName(String lastName) {
|
|
||||||
this.lastName = lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsername(String username) {
|
|
||||||
this.username = username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
}
|
|
23
social/github/pom.xml
Executable file
23
social/github/pom.xml
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-social-parent</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.0-alpha-2-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>keycloak-social-github</artifactId>
|
||||||
|
<name>Keycloak Social GitHub</name>
|
||||||
|
<description/>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-social-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
67
social/github/src/main/java/org/keycloak/social/github/GitHubProvider.java
Executable file
67
social/github/src/main/java/org/keycloak/social/github/GitHubProvider.java
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
package org.keycloak.social.github;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.keycloak.social.AbstractOAuth2Provider;
|
||||||
|
import org.keycloak.social.AuthRequest;
|
||||||
|
import org.keycloak.social.SocialProviderConfig;
|
||||||
|
import org.keycloak.social.SocialProviderException;
|
||||||
|
import org.keycloak.social.SocialUser;
|
||||||
|
import org.keycloak.social.utils.SimpleHttp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class GitHubProvider extends AbstractOAuth2Provider {
|
||||||
|
|
||||||
|
private static final String ID = "github";
|
||||||
|
private static final String NAME = "GitHub";
|
||||||
|
|
||||||
|
private static final String AUTH_URL = "https://github.com/login/oauth/authorize";
|
||||||
|
private static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
|
||||||
|
private static final String PROFILE_URL = "https://api.github.com/user";
|
||||||
|
|
||||||
|
private static final String DEFAULT_SCOPE = "user:email";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getScope() {
|
||||||
|
return DEFAULT_SCOPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAuthUrl() {
|
||||||
|
return AUTH_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTokenUrl() {
|
||||||
|
return TOKEN_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SocialUser getProfile(String accessToken) throws SocialProviderException {
|
||||||
|
try {
|
||||||
|
JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
|
||||||
|
|
||||||
|
SocialUser user = new SocialUser(profile.get("id").toString());
|
||||||
|
|
||||||
|
user.setUsername(profile.getString("login"));
|
||||||
|
user.setFirstName(profile.optString("name"));
|
||||||
|
user.setEmail(profile.optString("email"));
|
||||||
|
|
||||||
|
return user;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SocialProviderException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.social.github.GitHubProvider
|
|
@ -19,5 +19,4 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -22,67 +22,54 @@
|
||||||
package org.keycloak.social.google;
|
package org.keycloak.social.google;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.keycloak.social.AuthCallback;
|
import org.keycloak.social.AbstractOAuth2Provider;
|
||||||
import org.keycloak.social.AuthRequest;
|
|
||||||
import org.keycloak.social.utils.SimpleHttp;
|
import org.keycloak.social.utils.SimpleHttp;
|
||||||
import org.keycloak.social.SocialProvider;
|
|
||||||
import org.keycloak.social.SocialProviderConfig;
|
|
||||||
import org.keycloak.social.SocialProviderException;
|
import org.keycloak.social.SocialProviderException;
|
||||||
import org.keycloak.social.SocialUser;
|
import org.keycloak.social.SocialUser;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class GoogleProvider implements SocialProvider {
|
public class GoogleProvider extends AbstractOAuth2Provider {
|
||||||
|
|
||||||
private static final String DEFAULT_RESPONSE_TYPE = "code";
|
private static final String ID = "google";
|
||||||
|
private static final String NAME = "Google";
|
||||||
|
|
||||||
private static final String AUTH_PATH = "https://accounts.google.com/o/oauth2/auth";
|
private static final String AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
|
||||||
|
private static final String TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
|
||||||
private static final String TOKEN_PATH = "https://accounts.google.com/o/oauth2/token";
|
private static final String PROFILE_URL = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
|
||||||
|
|
||||||
private static final String PROFILE_PATH = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
|
|
||||||
|
|
||||||
private static final String DEFAULT_SCOPE = "openid profile email";
|
private static final String DEFAULT_SCOPE = "openid profile email";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "google";
|
return ID;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
|
|
||||||
String state = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
return AuthRequest.create(state, AUTH_PATH).setQueryParam("client_id", config.getKey())
|
|
||||||
.setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
|
|
||||||
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Google";
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
protected String getScope() {
|
||||||
String code = callback.getQueryParam(DEFAULT_RESPONSE_TYPE);
|
return DEFAULT_SCOPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAuthUrl() {
|
||||||
|
return AUTH_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTokenUrl() {
|
||||||
|
return TOKEN_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SocialUser getProfile(String accessToken) throws SocialProviderException {
|
||||||
try {
|
try {
|
||||||
if (!callback.getQueryParam("state").equals(callback.getAttribute("state"))) {
|
JSONObject profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
|
||||||
throw new SocialProviderException("Invalid state");
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONObject token = SimpleHttp.doPost(TOKEN_PATH).param("code", code).param("client_id", config.getKey())
|
|
||||||
.param("client_secret", config.getSecret())
|
|
||||||
.param("redirect_uri", config.getCallbackUrl())
|
|
||||||
.param("grant_type", "authorization_code").asJson();
|
|
||||||
|
|
||||||
String accessToken = token.getString("access_token");
|
|
||||||
|
|
||||||
JSONObject profile = SimpleHttp.doGet(PROFILE_PATH).header("Authorization", "Bearer " + accessToken).asJson();
|
|
||||||
|
|
||||||
SocialUser user = new SocialUser(profile.getString("sub"));
|
SocialUser user = new SocialUser(profile.getString("sub"));
|
||||||
|
|
||||||
|
@ -98,9 +85,4 @@ public class GoogleProvider implements SocialProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRequestIdParamName() {
|
|
||||||
return "state";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
|
<module>github</module>
|
||||||
<module>google</module>
|
<module>google</module>
|
||||||
<module>twitter</module>
|
<module>twitter</module>
|
||||||
<module>facebook</module>
|
<module>facebook</module>
|
||||||
|
|
|
@ -73,6 +73,11 @@
|
||||||
<artifactId>keycloak-social-core</artifactId>
|
<artifactId>keycloak-social-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-social-github</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-social-google</artifactId>
|
<artifactId>keycloak-social-google</artifactId>
|
||||||
|
|
Loading…
Reference in a new issue