OAuth ClientモジュールがGAEで動かない

Play! frameworkOAuth ClientモジュールGoogle App Engineでうまく動きませんでした。

検証環境

  • Play! framework 1.0-r1036 (1.0.2.1以降のナイトリービルド)
    • GAE-1.0.2
    • Siena-1.1
    • OAuth-1.0
  • appengine-java-sdk-1.3.3.1 (GAEのSDK

問題点

理由は、OAuth Clientモジュールの内部でplay.libs.WSを使っているからです。play.libs.WSは、org.apache.commons.httpclientを使い、これがGAEではサポートされおらず、次のようなエラーをログに残します。

RuntimeException occured : java.security.AccessControlException: access denied (java.lang.RuntimePermission modifyThreadGroup)

OAuthの認証自体はできたのですが、例えばTwitterにつぶやきをPOSTすると上記のエラーが発生しました。

String statusUrl = "http://api.twitter.com/1/statuses/update.xml?status=" + URLEncoder.encode("This is a pen.", "utf-8");
getConnector().sign(user, WS.url(statusUrl), "POST").post();  // getConnectorは、OAuthClientクラスのインスタンス

解決方法

そこで、このモジュールに同梱されているsignpost-core-1.2.jarを直接使うことにしました。

    OAuthConsumer consumer = new DefaultOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);

    URL url = new URL("http://api.twitter.com/1/statuses/update.xml?status=" + URLEncoder.encode("This is a pen.", "utf-8"));
    HttpURLConnection statusRequest = (HttpURLConnection) url.openConnection();
    statusRequest.setRequestMethod("POST");

    consumer.setTokenWithSecret(user.getToken(), user.getSecret());
    consumer.sign(statusRequest);

その他

このモジュールをGAEで使うため、いくつか修正をおこないました。

GAE用を作成

オリジナルのOAuth Clientモジュールを修正するので、Play! frameworkのmodulesディレクトリにあるoauth-1.0をコピーし、oauth-gaeとしました。

Credintialsモデルの削除

GAEでSienaを使うため、また不必要なのでoauth-gae/app/models/oauthclient/Credentials.javaは削除しました。

バグの修正

oauth-gae/app/play/modules/oauthclient/OAuthClient.javaのretrieveAccessTokenメソッドにuser.setTokenが2回実行されるバグ(setSecretが無い)があるので修正しました。

public void retrieveAccessToken(ICredentials user, String verifier) throws Exception {
    Logger.info("Token before retrieve: " + getConsumer(user).getToken());
    Logger.info("Verifier: " + verifier);
    getProvider().retrieveAccessToken(getConsumer(user), verifier);
    user.setToken(consumer.getToken());
    // user.setToken(consumer.getTokenSecret());
    user.setSecret(consumer.getTokenSecret());
}
再ビルド

モジュールのルートディレクトリに戻って、antを実行し、oauth-gae/libに新しいplay-oauthclient.jarを作成しました。

Siena用のUsersモデル

Siena用のICredentialsインターフェースを実装したUsersモデルを用意しました。

package models;

import siena.*;
import play.modules.oauthclient.ICredentials;

@Table("Users")
public class Users extends Model implements ICredentials {

    @Id(Generator.AUTO_INCREMENT)
    public Long id;

    public String username;

    private String token;

    private String secret;

    public Users(String username) {
        this.username = username;
        insert();
    }
    
    public static Users find(String username) {
        return Model.all(Users.class).filter("username", username).get();
    }
    
    public static Users findOrCreate(String username) {
        Users user = find(username);

        if (user == null) {
            user = new Users(username);
        }
        
        return user;
    }

    public void setToken(String token) {
        this.token = token;
        update();
    }

    public String getToken() {
	return token;
    }

    public void setSecret(String secret) {
	this.secret = secret;
    	update();
    }

    public String getSecret() {
    	return secret;
    }

}