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がなくなる。といってもルーターはある
ルータの負荷は下がるので良いのかもしれない
192.168.0.1なアドレスはある
いわゆるプライベートアドレス相当の物はある(もちろん128bit)
1つのinterfaceに複数アドレスが振れる
だからなに?それ面倒くさくない?って気もチョットする
接続方式は2種類っぽい
色んなプロパイダーが無料で繋がせてくれる奴と
特定のところしか始めてない有料オプション扱いでルーターも選ぶ奴の二つ
ってみると無料の方が良いような気がするけど、有料オプションのにすると200Mとかの制限がなくなって1Gまで・・・
アドレスが長い
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ビット目がたってるので続きを処理するとか・・・
ちょっと素直な実装のような気がする
C30K問題とC60K問題
だいぶ前にTomcatのフロントにnginx入れなかったら、入れないの??どうして??って質問されたのを思い出してふと書いてみる
その時は「必要ないから」ってだけ答えて詳しく説明してないけどw
歴史
ずっと昔
Apache - Axx - Tomcat 方式
ApacheをHTTPのネットワークインタフェースとして独自プロトコルでTomcatと繋ぐ方法
ネイティブなApacheに静的ファイルを配置してTomcatは動的なファイルだけにすることで遅いTomcatを補完する方法
nginxが良かった頃
nginx - reverse proxy - Tomcat 方式
ApacheもTomcatも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でログイン
やってみた
とりあえず、macとapple watchは最新にバージョンアップ
2段階認証してたので、解除して2ファクタ認証を有効にする
↑一番ハマったところ
mac側でapple watchログインを有効にする
最初にこれをやろうとするとエラーメッセージと一緒に2ファクタ認証を有効にしろって出る
このとき2ファクタ認証を受け取るのはSMS(なかなか通知が来なくて困ったけど後で判った)
時計付けたままmacをスリープにして、開くと・・・勝手にログインした!(≧▽≦)
mac2台でも同じ時計でログイン出来るし、これ凄いなぁ〜
2ファクタ認証すると2段階認証で有効だった通知受けとる端末たちが全部無効になるので、再度iCloudにログインし直すと通知が届くようになる
このOSなかなか面白いなぁ〜