複数マイミクシィ認証

通常のOpenIDのRP(Relying Party)ではこんなことはしないだろうが、mixi OpenIDでは十分にありえる話だと思って試しに作ってみた。「Aさんのマイミクシィで、かつBさんのマイミクシィ」だけが使える、という複数条件による認証だ。

コードは前回のエントリ「mixi OpenIDで「マイミクシィだけの掲示板」を作ろう(Perl編)」のものを微修正し、リダイレクトを2回行うようにした。

#!/usr/local/bin/perl

use strict;
use utf8;
use CGI;
use LWP::UserAgent;
use Net::OpenID::Consumer;
use Encode;

my $query = CGI->new;
$query->charset("utf-8");

my $mixi_id1=2;    # このIDを持つ人のマイミクシィでないとログインできない
my $mixi_id2=4012; # さらに、このIDを持つ人のマイミクシィでないとログインできない
my $claimed_id1 = "https://id.mixi.jp/$mixi_id1/friends";
my $claimed_id2 = "https://id.mixi.jp/$mixi_id2/friends";
my $csr = Net::OpenID::Consumer->new(
  ua => LWP::UserAgent->new,
  args => $query,
  consumer_secret => "hoge",
  );

if($query->param('openid_url')){ # loginボタンを押された時
  if(my $cident1 = $csr->claimed_identity($claimed_id1)){
    # 1度目のリダイレクト先を決める
    my $check_url = $cident1->check_url(
      return_to  => URI->new($query->url.'?verify=1')->as_string,
      trust_root => $query->url,
      delayed_return => "checkid_setup",
      );
    # 1度目のリダイレクトをする
    print $query->redirect(-uri => $check_url);
  }
}elsif($query->param('verify')){ # 戻ってきた時
  if(my $setup_url = $csr->user_setup_url){
    print $query->redirect(-uri => $setup_url);
  }elsif($csr->user_cancel()){
    login_page($query, 'キャンセルされました');
  }elsif($query->param('openid.claimed_id')=~/https:\/\/id.mixi.jp\/$mixi_id1\/friends\/.+/){
    # 2度目のリダイレクト
    if(my $cident2 = $csr->claimed_identity($claimed_id2)){
      # SREG1.1でニックネームを取りたい
      $cident2->set_extension_args("http://openid.net/extensions/sreg/1.1", {
        required => "nickname"});
      # 2度目のリダイレクト先を決める
      my $check_url2 = $cident2->check_url(
        return_to  => URI->new($query->url.'?verify=1')->as_string,
        trust_root => $query->url,
        delayed_return => "checkid_setup",
        );
      # 2度目のリダイレクトをする
      print $query->redirect(-uri => $check_url2);
    }
  }elsif($query->param('openid.claimed_id')!~/https:\/\/id.mixi.jp\/$mixi_id2\/friends\/.+/){
    login_page($query, 'マイミクシィでないのでログインできません');    
  }elsif(my $identity = $csr->verified_identity){
    print $query->header, '<h1>ログイン完了!!</h1>'."\n";
    # ニックネームを添えて、掲示板風の画面を出す
    my $nickname = decode("utf-8",$query->param('openid.sreg.nickname'));
    if(!($nickname)){$nickname="名無し";} # ニックネームを出さない設定にしていたら「名無し」にする
    print "<h2>".$nickname."さんのオレオレ掲示板</h2>\n<form>\n";
    print "<textarea cols=70 rows=10></textarea><br>\n";
    print "<input type=\"submit\" value=\"書き込み\">\n";
    print "</form>\n";
    print "<hr>\n";
  } else {
    login_page($query);
  }
} else { # login前のページを表示する時
  login_page($query);
}

sub login_page {
    my ($query, $message) = @_;
    $message = $message ? $message= "<p class='error'>$message</p>" : '';
    print encode("utf-8",$query->header), <<PAGE;
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>mixi OpenID login</title>
</head>
<body>

<h1>オレオレ掲示板</h1>

$message

ログインしてみよう:
<a href="index.cgi?openid_url=https://mixi.jp"><img src="b_150.gif" alt="mixiでログイン" border="0"></a>
</body>
</html>
PAGE
}

これで問題なく実行できた。ガイドラインにも、そんなことをやっちゃいかんとは書かれていない。利用同意画面が2度出てくるのは面倒じゃないか?という場合には(本当にそれでよいのかという議論はありそうだが)「この外部サイトの場合常に同意する」ボタンを押してしまえばよい。

ものすごく安直な実装のため、「Aさんのマイミクシィで、かつBさんのマイミクシィ『でない』」のような要件には対応できない。2つの認証を直列につながず、別個に非同期に行えばできるかもしれない。