Added support for Facebook login
This commit is contained in:
parent
25888b2ef4
commit
13ea0ee609
6 changed files with 263 additions and 0 deletions
|
@ -45,6 +45,11 @@
|
||||||
<artifactId>keycloak-social-twitter</artifactId>
|
<artifactId>keycloak-social-twitter</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-social-facebook</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-sdk-html</artifactId>
|
<artifactId>keycloak-sdk-html</artifactId>
|
||||||
|
|
34
social/facebook/pom.xml
Normal file
34
social/facebook/pom.xml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<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-1</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>keycloak-social-facebook</artifactId>
|
||||||
|
<name>Keycloak Social Facebook</name>
|
||||||
|
<description/>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-social-core</artifactId>
|
||||||
|
<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>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,145 @@
|
||||||
|
package org.keycloak.social.facebook;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
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 org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||||
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
|
import org.keycloak.social.AuthCallback;
|
||||||
|
import org.keycloak.social.AuthRequest;
|
||||||
|
import org.keycloak.social.AuthRequestBuilder;
|
||||||
|
import org.keycloak.social.SocialProvider;
|
||||||
|
import org.keycloak.social.SocialProviderConfig;
|
||||||
|
import org.keycloak.social.SocialProviderException;
|
||||||
|
import org.keycloak.social.SocialUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Social provider for Facebook
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class FacebookProvider implements SocialProvider {
|
||||||
|
|
||||||
|
private static final String AUTHENTICATION_ENDPOINT_URL = "https://graph.facebook.com/oauth/authorize";
|
||||||
|
|
||||||
|
private static final String ACCESS_TOKEN_ENDPOINT_URL = "https://graph.facebook.com/oauth/access_token";
|
||||||
|
|
||||||
|
private static final String PROFILE_ENDPOINT_URL = "https://graph.facebook.com/me";
|
||||||
|
|
||||||
|
private static final String DEFAULT_RESPONSE_TYPE = "code";
|
||||||
|
|
||||||
|
private static final String DEFAULT_SCOPE = "email";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "facebook";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
|
||||||
|
String state = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
AuthRequestBuilder b = AuthRequestBuilder.create(state, AUTHENTICATION_ENDPOINT_URL).setQueryParam("client_id", config.getKey())
|
||||||
|
.setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
|
||||||
|
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state);
|
||||||
|
|
||||||
|
b.setAttribute("state", state);
|
||||||
|
|
||||||
|
return b.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequestIdParamName() {
|
||||||
|
return "state";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Facebook";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
||||||
|
String code = callback.getQueryParam(DEFAULT_RESPONSE_TYPE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!callback.getQueryParam("state").equals(callback.getAttribute("state"))) {
|
||||||
|
throw new SocialProviderException("Invalid state");
|
||||||
|
}
|
||||||
|
|
||||||
|
ResteasyClient client = new ResteasyClientBuilder()
|
||||||
|
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
|
||||||
|
|
||||||
|
String accessToken = loadAccessToken(code, config, client);
|
||||||
|
|
||||||
|
FacebookUser facebookUser = loadUser(accessToken, client);
|
||||||
|
|
||||||
|
SocialUser socialUser = new SocialUser(facebookUser.getId());
|
||||||
|
socialUser.setEmail(facebookUser.getEmail());
|
||||||
|
socialUser.setLastName(facebookUser.getLastName());
|
||||||
|
socialUser.setFirstName(facebookUser.getFirstName());
|
||||||
|
|
||||||
|
return socialUser;
|
||||||
|
} catch (SocialProviderException spe) {
|
||||||
|
throw spe;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SocialProviderException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String loadAccessToken(String code, SocialProviderConfig config, ResteasyClient client) throws SocialProviderException {
|
||||||
|
Form form = new Form();
|
||||||
|
form.param("grant_type", "authorization_code")
|
||||||
|
.param("code", code)
|
||||||
|
.param("client_id", config.getKey())
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.social.facebook.FacebookProvider
|
|
@ -17,6 +17,7 @@
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
<module>google</module>
|
<module>google</module>
|
||||||
<module>twitter</module>
|
<module>twitter</module>
|
||||||
|
<module>facebook</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
Loading…
Reference in a new issue