概要
Apache Oltuを使ったOAuth認証のサンプルコードです。
このライブラリの検証目的に作成しました。
環境
- Windows7 (64bit)
- Java 1.8.0_65
- Spring-Boot 1.3.0
- Apache Oltu 1.0.1
参考
- [Apache Oltu] (https://oltu.apache.org/)
- [Working example on OAuth2 + Spring Security + Apache Oltu] (http://yfrankfeng.blogspot.jp/2015/07/working-example-on-oauth2-spring.html)
- [OAuth 2.0 Scopes for Google APIs] (https://developers.google.com/identity/protocols/googlescopes)
- [Using OAuth 2.0 for Web Server Applications] (https://developers.google.com/identity/protocols/OAuth2WebServer)
サンプルコード
pom.xml
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.sbsns.oltu</groupId>
<artifactId>sbsns-oltu-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sbsns-oltu-example</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.client</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
application.yml
# EMBEDDED SERVER CONFIGURATION (ServerProperties)
server:
port: 9000
spring:
# PROFILES
profiles:
active: dev
# THYMELEAF (ThymeleafAutoConfiguration)
thymeleaf:
enabled: true
cache: false
facebook:
app-id: <facebook-app-id>
app-secret: <faceboo-app-secret>
google:
app-id: <google-app-id>
app-secret: <google-app-secret>
Googleのサンプル
Config
package com.example.sbsns.oltu;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GoogleConfig {
@Value("${google.app-id}")
private String appId;
@Value("${google.app-secret}")
private String appSecret;
public String getAppId() {
return appId;
}
public String getAppSecret() {
return appSecret;
}
}
Form
package com.example.sbsns.oltu.web;
public class GoogleOAuthCallbackForm {
private String code;
private String state;
private String error;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
@Override
public String toString() {
return "GoogleAuthCallbackForm [code=" + code + ", state=" + state
+ ", error=" + error + "]";
}
}
Controller
package com.example.sbsns.oltu.web;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthAccessTokenResponse;
import org.apache.oltu.oauth2.client.response.OAuthAuthzResponse;
import org.apache.oltu.oauth2.client.response.OAuthResourceResponse;
import org.apache.oltu.oauth2.common.OAuthProviderType;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.example.sbsns.oltu.GoogleConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
@Controller
@RequestMapping(value = "/google")
public class GoogleOAuthController {
private static Logger logger = LoggerFactory.getLogger(GoogleOAuthController.class);
@Autowired
private GoogleConfig config;
private static final String REDIRECT_URI = "http://localhost:9000/google/callback";
private static final String API_USER_INFO_ENDPOINT = "https://www.googleapis.com/oauth2/v1/userinfo";
private static final String API_REVOKE_ENDPOINT = "https://accounts.google.com/o/oauth2/revoke";
//private static final String API_GOOGLE_PLUS_ENDPOINT = "https://www.googleapis.com/plus/v1/people/me";
@RequestMapping(value = "/signin", method = RequestMethod.GET)
public void signin(
HttpServletRequest request,
HttpServletResponse response,
Model model) throws IOException, OAuthSystemException {
logger.info("signin");
String state = "abc123xyz456";
OAuthClientRequest clientRequest = OAuthClientRequest
.authorizationProvider(OAuthProviderType.GOOGLE)
.setClientId(config.getAppId())
.setRedirectURI(REDIRECT_URI)
.setResponseType("code")
.setState(state)
.setScope("https://www.googleapis.com/auth/userinfo.email")
.buildQueryMessage();
String redirectURL = clientRequest.getLocationUri();
logger.info("locationURL:{}", redirectURL);
response.sendRedirect(clientRequest.getLocationUri());
}
@RequestMapping(value = "/callback", method = RequestMethod.GET)
public String callback(
GoogleOAuthCallbackForm form,
HttpServletRequest request,
HttpServletResponse response,
Model model) throws OAuthProblemException, OAuthSystemException {
logger.info("callback");
logger.info("form:{}", form);
String code;
String state;
try {
OAuthAuthzResponse oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
code = oar.getCode();
state = oar.getState();
logger.info("code:{}", code);
logger.info("state:{}", state);
} catch (OAuthProblemException e) {
logger.error("OAuth response error", e);
return "redirect:/";
}
OAuthClientRequest oauthRequest = OAuthClientRequest
.tokenProvider(OAuthProviderType.GOOGLE)
.setClientId(config.getAppId())
.setClientSecret(config.getAppSecret())
.setRedirectURI(REDIRECT_URI)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setCode(code)
.buildBodyMessage();
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
final OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(oauthRequest, "POST");
String accessToken = oAuthResponse.getAccessToken();
String scope = oAuthResponse.getScope();
long expiresIn = oAuthResponse.getExpiresIn();
logger.info("oAuthResponse:{}", oAuthResponse.toString());
logger.info("accessToken:{}", accessToken);
logger.info("scope:{}", scope);
logger.info("expiresIn:{}", expiresIn);
request.getSession().setAttribute("accessToken", accessToken);
return "google/callback";
}
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String user(
HttpServletRequest request,
HttpServletResponse response,
Model model) throws OAuthSystemException, OAuthProblemException {
logger.info("user");
String accessToken = (String) request.getSession().getAttribute("accessToken");
OAuthClientRequest bearerClientRequest =
new OAuthBearerClientRequest(API_USER_INFO_ENDPOINT)
.setAccessToken(accessToken)
.buildQueryMessage();
logger.info("locationURL:{}", bearerClientRequest.getLocationUri());
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, "GET", OAuthResourceResponse.class);
String userinfoJSON = resourceResponse.getBody();
logger.info("json:{}", userinfoJSON);
try {
GoogleUserProfile userProfile = new ObjectMapper().readValue(userinfoJSON, GoogleUserProfile.class);
model.addAttribute("userProfile", userProfile);
logger.info("{}", userProfile.toString());
} catch (IOException e) {
logger.error("Json parse error", e);
return "redirect:/";
}
return "google/user";
}
@RequestMapping(value = "/signout", method = RequestMethod.GET)
public String signout(
HttpServletRequest request,
HttpServletResponse response,
Model model) throws IOException, OAuthSystemException, OAuthProblemException {
logger.info("signout");
String accessToken = (String) request.getSession().getAttribute("accessToken");
OAuthClientRequest bearerClientRequest =
new OAuthBearerClientRequest(API_REVOKE_ENDPOINT + "?token=" + accessToken)
.buildQueryMessage();
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, "GET", OAuthResourceResponse.class);
String json = resourceResponse.getBody();
logger.info("json:{}", json);
return "index";
}
}
Response Object
package com.example.sbsns.oltu.web;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown=true)
public class GoogleUserProfile implements Serializable {
private static final long serialVersionUID = 3543234712791418623L;
@JsonProperty("id")
private String id;
@JsonProperty("name")
private String name;
@JsonProperty("gender")
private String gender;
@JsonProperty("given_name")
private String givenName;
@JsonProperty("family_name")
private String familyName;
@JsonProperty("verified_email")
private boolean verifiedEmail;
@JsonProperty("email")
private String email;
@JsonProperty("picture")
private String picture;
@JsonProperty("link")
private String link;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getGivenName() {
return givenName;
}
public void setGivenName(String givenName) {
this.givenName = givenName;
}
public String getFamilyName() {
return familyName;
}
public void setFamilyName(String familyName) {
this.familyName = familyName;
}
public boolean isVerifiedEmail() {
return verifiedEmail;
}
public void setVerifiedEmail(boolean verifiedEmail) {
this.verifiedEmail = verifiedEmail;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPicture() {
return picture;
}
public void setPicture(String picture) {
this.picture = picture;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
@Override
public String toString() {
return "GoogleUserProfile [id=" + id + ", name=" + name + ", gender="
+ gender + ", givenName=" + givenName + ", familyName=" + familyName
+ ", verifiedEmail=" + verifiedEmail + ", email=" + email
+ ", picture=" + picture + ", link=" + link + "]";
}
}
Facebookのサンプル
Config
package com.example.sbsns.oltu;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FacebookConfig {
@Value("${facebook.app-id}")
private String appID;
@Value("${facebook.app-secret}")
private String appSecret;
public String getAppID() {
return appID;
}
public String getAppSecret() {
return appSecret;
}
}
Form
package com.example.sbsns.oltu.web;
public class FacebookOAuthCallbackForm {
private String code;
private String state;
private String error;
private String error_code;
private String error_description;
private String error_reason;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getError_code() {
return error_code;
}
public void setError_code(String error_code) {
this.error_code = error_code;
}
public String getError_description() {
return error_description;
}
public void setError_description(String error_description) {
this.error_description = error_description;
}
public String getError_reason() {
return error_reason;
}
public void setError_reason(String error_reason) {
this.error_reason = error_reason;
}
@Override
public String toString() {
return "FacebookAuthCallbackForm [code=" + code + ", state=" + state
+ ", error=" + error + ", error_code=" + error_code
+ ", error_description=" + error_description + ", error_reason="
+ error_reason + "]";
}
}
Controller
package com.example.sbsns.oltu.web;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.GitHubTokenResponse;
import org.apache.oltu.oauth2.client.response.OAuthAuthzResponse;
import org.apache.oltu.oauth2.client.response.OAuthResourceResponse;
import org.apache.oltu.oauth2.common.OAuthProviderType;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.example.sbsns.oltu.FacebookConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
@Controller
@RequestMapping(value = "/facebook")
public class FacebookOAuthController {
private static Logger logger = LoggerFactory.getLogger(FacebookOAuthController.class);
@Autowired
private FacebookConfig config;
private static final String REDIRECT_URI = "http://localhost:9000/facebook/callback";
@RequestMapping(value = "/signin", method = RequestMethod.GET)
public void signin(
HttpServletRequest request,
HttpServletResponse response,
Model model) throws IOException, OAuthSystemException {
logger.info("signin");
String state = "abc123xyz456";
OAuthClientRequest clientRequest = OAuthClientRequest
.authorizationProvider(OAuthProviderType.FACEBOOK)
.setClientId(config.getAppID())
.setRedirectURI(REDIRECT_URI)
.setResponseType("code")
.setScope("public_profile,user_friends,user_birthday,email,user_photos,user_about_me")
.setState(state)
.buildQueryMessage();
String redirectURL = clientRequest.getLocationUri();
logger.info("redirectURL:{}", redirectURL);
response.sendRedirect(clientRequest.getLocationUri());
}
@RequestMapping(value = "/callback", method = RequestMethod.GET)
public String callback(
FacebookOAuthCallbackForm form,
HttpServletRequest request,
HttpServletResponse response,
Model model) throws OAuthProblemException, OAuthSystemException {
logger.info("callback");
logger.info("form:{}", form);
String code;
String state;
try {
OAuthAuthzResponse oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
code = oar.getCode();
state = oar.getState();
logger.info("code:{}",code);
logger.info("state:{}",state);
} catch (OAuthProblemException e) {
logger.error("OAuth response error", e);
return "redirect:/";
}
OAuthClientRequest oauthRequest = OAuthClientRequest
.tokenProvider(OAuthProviderType.FACEBOOK)
.setClientId(config.getAppID())
.setClientSecret(config.getAppSecret())
.setRedirectURI(REDIRECT_URI)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setCode(code)
.buildBodyMessage();
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
GitHubTokenResponse oAuthResponse = oAuthClient.accessToken(oauthRequest, GitHubTokenResponse.class);
String accessToken = oAuthResponse.getAccessToken();
String scope = oAuthResponse.getScope();
String expiresIn = oAuthResponse.getParam("expires");
logger.info("oAuthResponse:{}", oAuthResponse.toString());
logger.info("accessToken:{}",accessToken);
logger.info("scope:{}",scope);
logger.info("expire:{}",expiresIn);
request.getSession().setAttribute("accessToken", accessToken);
return "facebook/callback";
}
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String user(
HttpServletRequest request,
HttpServletResponse response,
Model model) throws OAuthSystemException, OAuthProblemException {
logger.info("user");
String accessToken = (String) request.getSession().getAttribute("accessToken");
OAuthClientRequest bearerClientRequest =
new OAuthBearerClientRequest("https://graph.facebook.com/me?fields=id,first_name,last_name,picture,email")
.setAccessToken(accessToken)
.buildQueryMessage();
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, "GET", OAuthResourceResponse.class);
String userinfoJSON = resourceResponse.getBody();
logger.info("json:{}", userinfoJSON);
try {
FacebookUserProfile userProfile = new ObjectMapper().readValue(userinfoJSON, FacebookUserProfile.class);
request.getSession().setAttribute("facebookId", userProfile.getId());
model.addAttribute("userProfile", userProfile);
logger.info("{}", userProfile.toString());
} catch (IOException e) {
logger.error("Json parse error", e);
return "redirect:/";
}
return "facebook/user";
}
@RequestMapping(value = "/signout", method = RequestMethod.GET)
public String signout(
HttpServletRequest request,
HttpServletResponse response,
Model model) throws IOException, OAuthSystemException, OAuthProblemException {
logger.info("signout");
String accessToken = (String) request.getSession().getAttribute("accessToken");
String facebookId = (String) request.getSession().getAttribute("facebookId");
OAuthClientRequest bearerClientRequest =
new OAuthBearerClientRequest("https://graph.facebook.com/" + facebookId + "/permissions")
.setAccessToken(accessToken)
.buildQueryMessage();
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, "DELETE", OAuthResourceResponse.class);
String json = resourceResponse.getBody();
logger.info("json:{}", json);
return "index";
}
}
Response Object
package com.example.sbsns.oltu.web;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown=true)
public class FacebookUserProfile implements Serializable {
private static final long serialVersionUID = 7957841067903054430L;
@JsonProperty("id")
private String id;
@JsonProperty("last_name")
private String lastName;
@JsonProperty("first_name")
private String firstName;
@JsonProperty("email")
private String email;
@JsonProperty("picture")
private FacebookPicture picture;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public FacebookPicture getPicture() {
return picture;
}
public void setPicture(FacebookPicture picture) {
this.picture = picture;
}
@Override
public String toString() {
return "FacebookUserProfile [id=" + id + ", lastName=" + lastName
+ ", firstName=" + firstName + ", email=" + email + ", picture="
+ picture + "]";
}
}
package com.example.sbsns.oltu.web;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown=true)
public class FacebookPicture implements Serializable {
private static final long serialVersionUID = 5591928605444403203L;
@JsonProperty("data")
private FacebookPictureData data;
public FacebookPictureData getData() {
return data;
}
public void setData(FacebookPictureData data) {
this.data = data;
}
@Override
public String toString() {
return "FacebookPicture [data=" + data + "]";
}
}
package com.example.sbsns.oltu.web;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown=true)
public class FacebookPictureData implements Serializable {
private static final long serialVersionUID = -857548118195277215L;
@JsonProperty("is_silhouette")
private boolean isSilhouette;
@JsonProperty("url")
private String url;
public boolean isSilhouette() {
return isSilhouette;
}
public void setSilhouette(boolean isSilhouette) {
this.isSilhouette = isSilhouette;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "FacebookPictureData [isSilhouette=" + isSilhouette + ", url=" + url
+ "]";
}
}