makotan _at_ gmail dot com

jooby + pac4j + Auth0でログイン認証してみる

諦めてSpringにしようかと思いつつ、丸C日頑張ってみた成果


このクラスを用意する。jooby + pack4jで必要になるクラス

public class Auth0Client extends GenericOAuth20StateClient {
    public Auth0Client(final String key, // Client IDのこと
                       final String secret, // Client Secretのこと
                       final String authUrl,  // Advanced SettingsのOAuth Authorization URL
                       final String tokenUrl,  // Advanced SettingsのOAuth Token URL
                       final String profileUrl,  // Advanced SettingsのOAuth User Info URL
                       final String scope) { // 任意だけど openid email nickname こんな感じで色々取れる
        super(key, secret, authUrl, tokenUrl, profileUrl, scope);

    }

    protected String getOAuthScope() {
        return getScope(); // 何故Genericなのに上書きしない・・・
    }

    protected boolean hasOAuthGrantType() {
        return true; // GrantTypeを引数に渡す
    }
    @Override
    protected GenericOAuth20Profile extractUserProfile(String body) {
        final GenericOAuth20Profile profile = new GenericOAuth20Profile();
        if (attributesDefinition != null) {
            profile.setAttributesDefinition(attributesDefinition);
        }
        final JsonNode json = JsonHelper.getFirstNode(body);
        if (json != null) {
            profile.setId(JsonHelper.getElement(json, "user_id"));  // デフォルトはidなので変えたかっただけ
            for (final String attribute : profile.getAttributesDefinition().getPrimaryAttributes()) {
                profile.addAttribute(attribute, JsonHelper.getElement(json, attribute));
            }
        }
        return profile;
    }

}


基本的にdemoから色々取ってくるファイル群
html5.html,index.html,profile.html
あとAppからgetUserProfileもコピペ忘れずに

        get("/", () -> Results.html("index"));
        use(new Auth()
                .client("/oauth/**", conf -> {
                    return new Auth0Client(
                            conf.getString("auth.clientId")
                            , conf.getString("auth.clientSeacret")
                            , conf.getString("auth.authorizeUrl")
                            , conf.getString("auth.tokenUrl")
                            , conf.getString("auth.userinfoUrl")
                            , conf.getString("auth.scope")
                    );
                })
        );

        /** One handler for logged user. */
        Route.OneArgHandler handler = req -> {
            UserProfile profile = getUserProfile(req);

            return Results.html("profile")
                    .put("client", profile.getClass().getSimpleName().replace("Profile", ""))
                    .put("profile", profile);
        };

        get("/oauth", handler);


application.confにはこんな感じで書く

auth {
  # use default callback, require for oidc and others
  callback = "http://"${application.host}":"${application.port}${application.path}"callback"

  authorizeUrl = "https://xxxxxx.auth0.com/authorize"
  tokenUrl = "https://xxxxxxx.auth0.com/oauth/token"
  userinfoUrl = "https://xxxxxx.auth0.com/userinfo"
  clientId = "XXXXXXXXXXXXXXXXXXXXXX"
  clientSeacret = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
  scope = "openid email nickname"
}


この状態で起動した後、 localhost:8080/oauth?client_name=Auth0Client を表示すると、Auth0の画面が出てくる
その場でアカウントを作ってログインしたりすると
profileページが出てきてメアドなどの情報がゲット出来る



これ、すぐ終わると思ったのに長かった・・・
本当はconfigの自動設定もできるんだけど、そこに興味ないのでやってないw

joobyのrequestに何が入ってるのか見てみた

ちょっと気になってやってみただけ

            log.debug("body {}" , request.body());
            log.debug("charset {}" , request.charset());
            log.debug("contextPath {}" , request.contextPath());
            log.debug("headers {}" , request.headers());
            log.debug("hostname {}" , request.hostname());
            log.debug("ip {}" , request.ip());
            log.debug("length {}" , request.length());
            log.debug("locale {}" , request.locale());
            log.debug("params {}" , request.params());
            log.debug("port {}" , request.port());
            log.debug("protocol {}" , request.protocol());
            log.debug("queryString {}" , request.queryString());
            log.debug("rawPath {}" , request.rawPath());
            log.debug("secure {}" , request.secure());
            log.debug("timestamp {}" , request.timestamp());
            log.debug("path {}" , request.path());
            log.debug("path(true) {}" , request.path(true));
            log.debug("type {}" , request.type());
body org.jooby.internal.EmptyBodyReference@1b3f1f61
charset UTF-8
contextPath 
headers {Host=[localhost:8080], Connection=[keep-alive], Upgrade-Insecure-Requests=[1], User-Agent=[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36], Accept=[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8], Accept-Encoding=[gzip, deflate, sdch, br], Accept-Language=[ja,en-US;q=0.8,en;q=0.6], content-length=[0]}
hostname localhost
ip 0:0:0:0:0:0:0:1
length 0
locale ja_JP
params {queryparam=[hogehoge]}
port 8080
protocol HTTP/1.1
queryString Optional[queryparam=hogehoge]
rawPath /hoge
secure false
timestamp 1482669781042
path /hoge
path(true) /hoge
type */*

他にもsessionやらflashやら色々入ってるけど気にしない

IPv6の勉強(接続する人として)

基本的に全部publicIPになる

気にしないとどこからでも繋ぎ放題ハックし放題ですか!
って思ったけどルーターでINを防げるんだった

NATが無くなる

End to Endの接続になるのでNATがなくなる。といってもルーターはある
ルータの負荷は下がるので良いのかもしれない

DNS/DHCP他色々変わる

この辺が対応云々の話だろうなぁ〜って思った
家の中にローカルDNSサーバおいて・・・って人はさらに面倒そう

192.168.0.1なアドレスはある

いわゆるプライベートアドレス相当の物はある(もちろん128bit)

1つのinterfaceに複数アドレスが振れる

だからなに?それ面倒くさくない?って気もチョットする

接続方式は2種類っぽい

色んなプロパイダーが無料で繋がせてくれる奴と
特定のところしか始めてない有料オプション扱いでルーターも選ぶ奴の二つ
ってみると無料の方が良いような気がするけど、有料オプションのにすると200Mとかの制限がなくなって1Gまで・・・

接続先が対応してないとIPv4で繋ぐので意味ない

じつはここが一番のネックじゃないかと思った
公表してるところ少なそう
IPv6の上にIPv4のパケットを乗っけれるらしい

アドレスが長い

128bitを馬鹿にしちゃダメだなと思った

下半分は端末のMacアドレス

端末固有のアドレスが露呈!
露呈しない方法もあるらしい

ポート付きの表現が複雑

:がアドレスの区切りに使われてるのでポート番号付きは別の表現になってたけど、もうちょっとなんか方法あるだろって思ったw

結論

自分の家で使うときにはルーターが対応してるかから始まる
あとはどれくらい露呈させたくないか、オープンにしたいかが設定のポイントになりそう


そしてまだ勉強中

モデルを・・・の疑問

モデルをViewと切り離して純粋なModelが作れたとして
ModelとViewを完全に切り離す事って出来ないよなぁ〜
っとおもって、View用のModelを作ったとしてもそれがModelに依存してる限り、Modelのinterfaceとして何らかの影響を受ける事には変わらない気がする


結局あらゆるシステムにおいてModelとViewが完全に切り離せることを証明出来ない限り、大多数のシステムでModelはViewの影響を受けたinterfaceを持つと考えるのが自然。
まぁ例外的にすごく向いてるシステムもあると思うけど、それはちゃんとModelを作れば良いと思うし


ModelはViewの影響をある程度受ける前提として、何処まで許容するかの範囲を考えれば良いだけだと。
完全な切り離しが不可能としても変更処理と表示処理は影響の受け方が違うと想定出来る
変更処理は単独での変更と、複数一括での変更、他にも複数Modelにまたがった変更など
表示はさらに色んな種類の表示方法を考える必要が出てくる
といいつつ、表示関係に関わらず取得処理は最低限サポートをしないと変更処理が出来ない


そこから、Modelに必要なinterfaceに必要なのはこの二種類

  • 最低限の取得処理
  • 変更に関わる全て


表示関係は表示の都合に合わせて自力でデータソースから取得する
ただし、変更の影響を最小限に抑える方法としてRDBならViewの積極活用を推奨


こんな感じでずっと思ってたけど、
最近Modelを妙に頑張ってViewから独立させようとする流れがあって凄く気になる
(自分の力では証明出来なかったけど、もしかして誰か証明しちゃった!?って疑問)

httpステータスコードを拡張するとき

httpステータスコードは100から599まで中が抜けた状態で色々あるなぁ〜とぼーとして、最上位の桁(100の位)をbitに分割してみた


各bitをxyzとして
z = 継続フラグ
y = 成功フラグ
x = 失敗フラグ


と仮に名前を付けると
https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89
ここに書いてるHTTPステータスの意味と100の位の数値がすごく一致しててびっくりした


ということは、HTTPステータスコードを独自拡張したコード体系を作るときはさらにbitを増やしてstuvwxyzみたいなビット構成にした方が良いのかなぁ〜
ということをぼんやり思った
(まぁ6xxってステータスコードにすると矛盾するので、xとyは排他にしたりしないとダメだけど)
そうすると、エラーかどうかを判断したいときは100の位の3ビット目をみるだけでエラーって判断が出来たり
2ビット目がたってるのでとりあえず成功したとか、1ビット目がたってるので続きを処理するとか・・・
ちょっと素直な実装のような気がする


実際拡張コード作るときはステータスコードを管理する為のenumとか作ると思うけどw

C30K問題とC60K問題

だいぶ前にTomcatのフロントにnginx入れなかったら、入れないの??どうして??って質問されたのを思い出してふと書いてみる
その時は「必要ないから」ってだけ答えて詳しく説明してないけどw

歴史

ずっと昔


Apache - Axx - Tomcat 方式
ApacheをHTTPのネットワークインタフェースとして独自プロトコルTomcatと繋ぐ方法
ネイティブなApacheに静的ファイルを配置してTomcatは動的なファイルだけにすることで遅いTomcatを補完する方法



nginxが良かった頃


nginx - reverse proxy - Tomcat 方式
ApacheTomcatもBlockingIOなので、代わりにnginxをフロントに配置する方法
nignxがNon BlockingIOなのでTomcatが処理本体に集中して接続処理などはnginxが担当する


この辺りでNIOな各種方法がJavaでも出てきた
ちなみに既にApacheもNon BlockingIOなのが入ったのでnignxの優位性はそれほどない


最近
Tomcat単独方式
Apacheもnginxも必要ない
既にTomcatがNIOなのでクライアントから接続が来てもそれだけではスレッドを占有しない
例外はApache/nginx側のモジュールでゴニョゴニョしてからTomcatに引き渡したいとかレスポンスをゴニョゴニョしたいとき
静的ファイルはCDNに置くことを検討する


これから
No Tomcat方式
Javaでの開発ですらTomcat(=ServletAPI)であることのメリットが薄れる時代
最近作られてるJVMベースなWebフレームワークでは仕様化の遅いServletAPIよりnettyベースが多いことからそろそろServletAPIが終焉を迎える
処理としての自然な形での非同期化/小スレッド化を予想
静的ファイルはサーバ内部にそもそも持たずに積極的にCDNを使用して配布する

C30K問題とC60K問題

C30K問題はnginx - Tomcat方式で抱える接続数の上限問題
TCPのポート数が有限のためnginxとTomcatの間で3万程度で上限に来てしまう問題
Tomcatがさらに外部サービスに依存してたりするとこの上限はさらに下がる
nginxを外すことで随分緩和してC60K問題に変化する
あっ、ちなみにC30K/C60Kに達するにはOS側の設定変更が必須です

C60K問題解決案

TCPの接続元ポート数が有限である事を認識した上で・・・
サーバ間の接続を含めて効率的なポートの利用(HTTPからHTTP/2へ)などでポート数の枯渇を防ぐ
UDPの利用を検討する
少ないスレッドで効率的に処理をする(スレッド数分の処理しか行わないので結果的に消費するTCPポートの削減に繋がる)

というかそれ以前に・・・

そこまで必要か?って疑問を先に解決するべきだと思う
同時接続が1台のサーバ上で3万とか6万超えるサービス作ってたっけ?
それよりポチポチやって必要なときにインスタンス台数増やした方が楽じゃね?
みたいな疑問を持った方が開発中も幸せになると思う

macにapple watchでログイン

やってみた


とりあえず、macapple watchは最新にバージョンアップ


2段階認証してたので、解除して2ファクタ認証を有効にする
↑一番ハマったところ


mac側でapple watchログインを有効にする
最初にこれをやろうとするとエラーメッセージと一緒に2ファクタ認証を有効にしろって出る
このとき2ファクタ認証を受け取るのはSMS(なかなか通知が来なくて困ったけど後で判った)


時計付けたままmacをスリープにして、開くと・・・勝手にログインした!(≧▽≦)
mac2台でも同じ時計でログイン出来るし、これ凄いなぁ〜


2ファクタ認証すると2段階認証で有効だった通知受けとる端末たちが全部無効になるので、再度iCloudにログインし直すと通知が届くようになる
このOSなかなか面白いなぁ〜