end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

Net::OpenID::(Server|Consumer)によるOpenIDのRP(client)-OP(provider)

http://d.hatena.ne.jp/end0tknr/20131215/1387105372
先日のエントリの続き、Net::OpenID::Consumerに加え、Net::OpenID::Serverを使って、OpenIDのOP(OpenID Provider , server)とRP(Relying Party , client )を連携。
Net::OpenID::Server - search.cpan.org
Net::OpenID::Consumer - search.cpan.org

サイト構成( apache on coliux port:8081 )

今回はcolinux上のapache(port:8081)でRP, OPのそれぞれを動かします。

ディレクトリ構成
opid/
    opid.pl
    server.xrds
opid_c/
    openid_client.pl
opid_c/
    .htaccess
    openid_server.pl
httpd.conf抜粋
AddType application/xrds+xml .xrds

Alias /server.xrds   /home/endo/dev/opid/server.xrds

#ex. OpenIDの例=http://colinux.a4.jp:8081/opid/end0tknr
Alias /opid  /home/endo/dev/opid/opid.pl
<Directory "/home/endo/dev/opid">
  AllowOverride All
  <Files "*.pl">
   Options ExecCGI
   AddHandler cgi-script .pl
  </Files>
</Directory>

#LoadModule headers_module modules/mod_headers.so
#<Location />
#  Header add X-XRDS-Location http://colinux.a4.jp:8081/server.xrds
#</Location>

Alias /opid_c /home/endo/dev/opid_c
<Directory "/home/endo/dev/opid_c">
  AllowOverride All
  <Files "*.pl">
   Options ExecCGI
   AddHandler cgi-script .pl
  </Files>
</Directory>

Alias /opid_s /home/endo/dev/opid_s
<Directory "/home/endo/dev/opid_s">
  AllowOverride All
  <Files "*.pl">
   Options ExecCGI
   AddHandler cgi-script .pl
  </Files>
</Directory>

ディレクトリ opid_c (OpenID Client)

openid_client.pl

http://d.hatena.ne.jp/end0tknr/20131215/1387105372
OpenIDのclient側は、以前のエントリに記載している通リなので、詳細は記載しません

#!/usr/local/bin/perl
use strict;
use CGI;
use Net::OpenID::Consumer;
#use LWPx::ParanoidAgent;
use LWP::UserAgent;
use Data::Dumper;

main();

sub main {
    my $query = CGI->new;
    $query->charset('utf-8');

    # User-AgentにLWPx::ParanoidAgent を使用する理由は次のurlが分かりやすい.
    # 私の場合、colinux環境でこのOpenID Relying Party を試していますが
    # colinux環境はDNSに登録されていない為か LWPx::ParanoidAgent を使用すると
    # errorになるので、LWP::UserAgent を使用しています
    # http://www.atmarkit.co.jp/ait/articles/0711/20/news128.html
    my $ua = LWP::UserAgent->new;
#    my $ua = LWPx::ParanoidAgent->new();

    my $csr = Net::OpenID::Consumer->new(ua => $ua,
                                         args => $query,
                                         consumer_secret =>sub{$_[0]}, #TODO?
                                         #srcを読むと、debug optionがあるみたい
#                                         debug => 1
                                        );

    my $claimed_url = $query->param('openid-url');
    #### step2  userが入力したOpenID(url)を identity provider に認証依頼
    if ($claimed_url) {
        request_to_identity_provider($query,$csr,$claimed_url);
        return;
    }

    if ($query->param('verify')) {

        #### step? http://www.atmarkit.co.jp/ait/articles/0709/21/news142_3.html
        if ($csr->user_cancel) {
            show_login_form($query, 'your login is cancelled.');
            return;
        }

        #### step3 次のurlに記載されている通り。
        # checkid_immediateモードでopenid.user_setup_urlがIdPから返される場合
        # 再びUser-Agentをその指定URLにリダイレクトさせなければならないらしい
        # http://www.atmarkit.co.jp/ait/articles/0709/21/news142_3.html
        # http://d.hatena.ne.jp/clouder/20081226/p1
        if (my $setup_url = $csr->user_setup_url) {
            print $query->redirect(-uri => $setup_url);
            return;
        }

        my $id_tmp = $csr->verified_identity;

        #### step4 IDENTITY PROVIDERによる認証完了
        if (my $identity = $csr->verified_identity) {
            # my $user = +{ map { $_ => scalar $identity->$_ }
            #               qw( url display rss atom foaf declared_rss 
            #                 declared_atom declared_foaf foafmaker ) };
            print $query->header,"\n";
            print "<html><head></head><body>\n";
            print "<h1>OpenID logged in !!</h1>";
            print "<pre>VERIFIED_IDENTITY(OpenID, url)\n", $identity->url , "</pre>";
            print "<pre>", Dumper($identity) , "</pre>";
            print "</body></html>";
            return;
        }

        show_login_form($query);
        return;
    }

    #### step1 userがlogin formでopen_id(url)を入力
    show_login_form($query);
}


sub request_to_identity_provider {
    my ($query,$csr,$claimed_url) = @_;

    my $identity = $csr->claimed_identity($claimed_url);

    unless($identity){
        show_login_form($query, 'wrong identity'. Dumper($csr));
        return;
    }

    my $check_url =
        $identity->check_url(return_to=>
                             URI->new($query->url.'?verify=1')->as_string,
                             trust_root => $query->url);
    print $query->redirect(-uri => $check_url);
    return;
}


sub show_login_form {
    my ($query, $message) = @_;

    print $query->header, <<PAGE;
<html>
<head></head>
<body>
<form action='openid_client.pl' method='POST'>
  INPUT your OpenID (url)
  <input name='openid-url' maxlength='100' style="width:500px;"
         value="http://colinux.a4.jp:8081/opid/end0tknr" />

  <input type='submit' value='login by OpenID' /><br/><br/>
  ex.) https://me.yahoo.co.jp/a/XXXXXXXXXXXXX<br>
  ex.) http://colinux.a4.jp:8081/opid/end0tknr<br>
  http://help.yahoo.co.jp/help/jp/edit/openid

  <pre style="margin:5px;padding:5px;border:1px solid #000">
    $message</pre>
</form>
</body></html>
PAGE

}

1;
__END__

ディレクトリ opid

opid.pl

今回は、OpenID(identity url)を http://colinux.a4.jp:8081/opid/end0tknr としていますが、先程の openid_client.pl は、まず、このOpenIDをhttp getし、httpヘッダにあるxrdsファイルの場所を特定し、OpenIDの認証手続きへ進みます。

#!/usr/local/bin/perl
use strict;
use CGI;
use Net::OpenID::Server;
use Data::Dumper;

main();

sub main {
    my $cgi = CGI->new;
    print $cgi->header("-X-XRDS-Location"=>
			"http://colinux.a4.jp:8081/server.xrds");
}

私の場合、CGI.pmのheader()でX-XRDS-Locationを追加していますが、apachehttpd.confに次のように記載することで、同様にX-XRDS-Locationを追加することもできます。

LoadModule headers_module modules/mod_headers.so
<Location />
  Header add X-XRDS-Location http://colinux.a4.jp:8081/server.xrds
</Location>

※mod_headers.so がない場合、apacheへmoduleを追加インストールして下さい

server.xrds

xrdsファイルには、OpenID Providerの認証仕様?や、実際に認証手続きを行うend pointの場所(今回の場合、openid_server.pl)を記載します。

<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
    xmlns:xrds="xri://$xrds"
    xmlns:openid="http://openid.net/xmlns/1.0"
    xmlns="xri://$xrd*($v*2.0)">
  <XRD>
    <Service priority="0">
      <Type>http://specs.openid.net/auth/2.0/signon</Type>
      <Type>http://specs.openid.net/extensions/pape/1.0</Type>
      <Type>http://openid.net/srv/ax/1.0</Type>
      <Type>http://specs.openid.net/extensions/ui/1.0/mode/popup</Type>
      <Type>http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf</Type>
      <URI>http://colinux.a4.jp:8081/opid_s/openid_server.pl</URI>
    </Service>
  </XRD>
</xrds:XRDS>

※私の場合、yahooのxrdsファイルを参考にさせて頂きました。
https://open.login.yahooapis.jp/openid20/user_profile/xrds

ディレクトリ opid_s (OpenID Server)

.htaccess

opid_sでは環境変数:REMOTE_USERによりユーザIDを取得する為、basic認証を利用します。
また、OpenIDの認証手続きでは、openid_client.pl と openid_server.pl でのサーバ間直接連携も必要ですので、openid_client.pl ( 192.168.137.5 ) からのaccessは認証不要としています。

AuthType Basic
AuthName "Restricted Files"
AuthUserFile /home/endo/dev/htpasswd
Require valid-user

Satisfy any

order deny,allow
allow from 192.168.137.5
allow from 127.0.0.1
deny from all
openid_server.pl
#!/usr/local/bin/perl
use strict;
use CGI;
use Net::OpenID::Server;
use Data::Dumper;


my $cgi = CGI->new;

my $nos = Net::OpenID::Server->new(
    args     => $cgi,
    get_user     => \&get_user,
    get_identity => \&get_identity,
    is_identity  => \&is_identity,
    is_trusted   => \&is_trusted,
    server_secret => 'some_random_sequence_put_your_own',
    setup_url    => "http://colinux.a4.jp:8081/opid_s/openid_server.pl",
    endpoint_url => "http://colinux.a4.jp:8081/opid_s/openid_server.pl",
);

my ($type, $data) = $nos->handle_page;


if ($type eq "redirect") {
    #この部分は、ユーザ(ブラウザ)から呼ばれます
    my $trust = $cgi->param('openid.realm') || $cgi->param('openid.trust_root');

    print $cgi->header,
        $cgi->start_html(sprintf 'OpenID TEST'),
        sprintf(q|<h1>do you should login to <a href="">%s</a>?</h1>|, $trust, $trust),
        qq|<a href="$data">login</a>|,
        $cgi->end_html;
} elsif ($type eq "setup") {
#      my %setup_opts = %$data;
      # ... show them setup page(s), with options from setup_map
      # it's then your job to redirect them at the end to "return_to"
      # (or whatever you've named it in setup_map)
} else {
    #この部分は、openid_client.plから直接呼ばれます
    print $cgi->header($type);
    print $data;

    print STDERR "$data\n"
}

sub get_user {
    return $ENV{REMOTE_USER};
}

sub get_identity {
    my($user, $identity) = @_;
    return "http://colinux.a4.jp:8081/opid/$user";
}

sub is_identity {
    my($user, $identity) = @_;
    my $ret = $user && $user eq (split '/', $identity)[-1];
    return $ret;
}

sub is_trusted {
    my($user, $trust_root, $is_identity) = @_;
    return $is_identity;
}