CompassをMavenから実行できるプラグインを作った

CSS を効率よく書こうみたいなフレームワーク(と言うのか不明ですが)が流行ってて、LESS とか Sass とか色々あります。

この中で自分が結構いいなぁと思ったものに、Compass (Sass の拡張)というものがあって、ちょろちょろと試したりしているんですが、こういう CSS フレームワークっていうのは、静的にコンパイルする仕組みになっていて、アプリケーション開発とかに組み込む場合は、やっぱりビルドプロセスにうまく組み込みたいなぁと思うのが普通です(手動でやったら絶対ミスりますしね)

ということで、Maven から Compass を実行できるプラグインを作ってみました。

maven-compass-plugin https://github.com/sinsengumi/maven-compass-plugin

README にも書いてますが、実行には Compass へのパスが通っている必要があります。 こういう外部プログラムを起動するタイプのやつはどうするのがベストプラクティスなんですかね。そこら辺のお作法がよくわからなかったので、直接コマンドを叩いてますけど。 (ちなみに LESS の maven プラグインJRuby 経由で実行する形になってたけど)

Mavenリポジトリで提供されていないサードパーティJarをどうするか

Maven のセントラルリポジトリやリムーブリポジトリ(※)で提供されていないサードパーティ Jar や俺俺ライブラリを pom.xml 上でどう管理するかという話です。 管理の仕方によって、開発者や管理者(環境構築をするようなアーキテクトっぽい人)の仕事が変わってくると思います。

http://daipresents.com/2009/maven2_internal_repository_webdav/

方法としては、主に以下の3つがあると思います。

  1. ローカルリポジトリに Jar をインストールする
  2. インターナルリポジトリを立てる
  3. system スコープを使う

1. ローカルリポジトリに Jar をインストールする

Jar ファイルをローカルのリポジトリに手動でインストールする方法。 以下のコマンドを叩くことで、自分のローカルリポジトリに Jar ファイルがインストールされる。

[bash] mvn install:install-file -Dfile=jarまでの絶対パス -DgroupId=<group-id> -DartifactId=<artifact-id> -Dversion=バージョン -Dpackaging=jar -DgeneratePom=true [/bash]

メリット

  • 特になし?

デメリット

  • 開発者全員が同じコマンドを叩く必要がある
  • ライブラリのバージョンアップ時にも同じ作業が必要

2. インターナルリポジトリを立てる

開発用に新たなリポジトリサーバを立てる。いわゆる社内リポジトリ。 やり方は省略。 http://daipresents.com/2009/maven2_internal_repository_webdav/

メリット

  • 開発者自体は特別な操作を必要としない

デメリット

  • インターナルリポジトリの構築が手間
  • インターナルリポジトリがネットワーク経由でアクセス出来ないと環境構築できない(ソースだけあってもビルドできない)

3. system スコープを使う

ライブラリを system スコープのライブラリとして定義して、Jar ファイル自体はプロジェクト内で持つという方法です。 pom.xml には環境依存しないように systemPath を ${basedir} 等を使って定義する。。

[xml] net.sinsengumi ThirdPartyJar 1.0 system ${basedir}/lib/ThirdPartyJar.jar [/xml]

メリット

  • 開発者は特別な操作を必要としない

デメリット

  • ライブラリをプロジェクト内で持つ必要がある(バージョン管理システムに含めなければならなかったりして、ファイルサイズが大きくなる)

※Webアプリケーションについて

Webアプリケーションの場合、WEB-INF/lib に Jar ファイルを置くことで自動的にクラスパスに含まれるので、ここに配置してしまうという手もありますが、pom.xml に依存性を書く必要がなくなるので、このライブラリだけ pom.xml に書かれてないみたいな気持ち悪さがあります。 そして、pom.xml に書かれてないと、mvn test 時にクラスパスが通らないという問題もあります。

あと、ライブラリをプロジェクト内で持つ必要がある、というデメリットも当然あります。

まとめ

方法としては、2. か 3. のどちらかかなぁと思います。 自分は管理の簡便性を求めて、3. でやっています。 プロジェクトの規模感で 2. でやる方法もありかなぁと思います。

HttpServletRequestWrapperを使って、HTTPリクエストを改変する

OpenAMは、認証済のユーザー情報をHTTPリクエストヘッダーに載せてくれるんですが、Servletでそれってどうやって実現してるのかなーと思って、調べたら HttpServletRequestWrapper クラスを使うようです。

使い方ですが、HttpServletRequestWrapper は、HttpServletRequestインタフェースを実装しているので、HttpServletRequestインタフェースのメソッドをオーバーライドして独自の実装を定義します。 http://mergedoc.sourceforge.jp/tomcat-servletapi-5-ja/javax/servlet/http/HttpServletRequestWrapper.html

今は、すべてのリクエストのヘッダーに情報を付与しようとしているので、getHeader辺りをオーバーライドします。

[java] package org.sample;

import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper;

public class HeaderAddedHttpServletRequest extends HttpServletRequestWrapper {

private Map&lt;String, String&gt; addHeaders;

public HeaderAddedHttpServletRequest(HttpServletRequest request, Map&lt;String, String&gt; addHeaders) {
    super(request);

    if (addHeaders == null) {
        this.addHeaders = new HashMap&lt;String, String&gt;();
    } else {
        this.addHeaders = addHeaders;
    }
}

@Override
public String getHeader(String name) {
    String header = super.getHeader(name);
    if (header != null) {
        return header;
    }

    header = addHeaders.get(name);
    if (header != null) {
        return header;
    }

    return null;
}

@Override
public Enumeration&lt;String&gt; getHeaderNames() {
    List&lt;String&gt; newHeaderNames = new ArrayList&lt;String&gt;();

    Enumeration&lt;String&gt; currentHeaderNames = super.getHeaderNames();
    while (currentHeaderNames.hasMoreElements()) {
        newHeaderNames.add(currentHeaderNames.nextElement());
    }

    for (Entry&lt;String, String&gt; e : addHeaders.entrySet()) {
        newHeaderNames.add(e.getKey());
    }

    return Collections.enumeration(newHeaderNames);
}

} [/java]

で、すべてのリクエストがこのWrapperクラスを使うようにFilterを定義します。

[java] package org.sample;

import java.io.IOException; import java.util.HashMap; import java.util.Map;

import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest;

@WebFilter("/*") public class AddHeaderFilter implements Filter {

@Override
public void init(FilterConfig fConfig) throws ServletException {
}

@Override
public void destroy() {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

    Map&lt;String, String&gt; addHeaders = new HashMap&lt;String, String&gt;();
    addHeaders.put(&quot;X-USER-ID&quot;, &quot;HIJIKATA&quot;);
    addHeaders.put(&quot;X-USER-NAME&quot;, &quot;土方歳三&quot;);

    chain.doFilter(new HeaderAddedHttpServletRequest((HttpServletRequest) request, addHeaders), response);
}

} [/java]

これで、HTTPリクエストが飛んできたら自動でヘッダーが付与サれてます。

[java] package org.sample;

import java.io.IOException; import java.util.Enumeration;

import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;

@WebServlet("/SampleServlet") public class SampleServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    Enumeration&lt;String&gt; headerNames = request.getHeaderNames();
    while (headerNames.hasMoreElements()) {
        String headerName = headerNames.nextElement();
        String headerValue = request.getHeader(headerName);

        System.out.println(headerName + &quot;=&quot; + headerValue);
    }
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
}

} [/java]

host=localhost:8080
connection=keep-alive
user-agent=Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.152 Safari/535.19
accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-encoding=gzip,deflate,sdch
accept-language=ja,en-US;q=0.8,en;q=0.6
accept-charset=Shift_JIS,utf-8;q=0.7,*;q=0.3
X-USER-ID=HIJIKATA
X-USER-NAME=土方歳三

同様の事はレスポンスでもできます。

シンプルな HTTP Server 書いた

以前の記事の続きということで、シンプルな HTTP Server を書いた。

classファイル一つで動くというお手軽サーバー。マルチスレッド?自分しか使わないからいいんですよ。 お手軽に使いたいのでデフォルトパッケージ。

java SimpleHttpServer "C:\htdocs" 81

[java] import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map;

public class SimpleHttpServer {

private static String requestPath;

private static Map&lt;String, String&gt; CONTENT_TYPES = new HashMap&lt;String, String&gt;();

static {
    CONTENT_TYPES.put(&quot;html&quot;, &quot;text/html&quot;);
    CONTENT_TYPES.put(&quot;htm&quot;, &quot;text/html&quot;);
    CONTENT_TYPES.put(&quot;css&quot;, &quot;text/css&quot;);
    CONTENT_TYPES.put(&quot;js&quot;, &quot;text/javascript&quot;);
    CONTENT_TYPES.put(&quot;jpg&quot;, &quot;image/jpeg&quot;);
    CONTENT_TYPES.put(&quot;jpeg&quot;, &quot;image/jpeg&quot;);
    CONTENT_TYPES.put(&quot;png&quot;, &quot;image/png&quot;);
    CONTENT_TYPES.put(&quot;gif&quot;, &quot;image/gif&quot;);
    CONTENT_TYPES.put(&quot;pdf&quot;, &quot;application/pdf&quot;);
    CONTENT_TYPES.put(&quot;txt&quot;, &quot;text/plain&quot;);
    CONTENT_TYPES.put(&quot;xml&quot;, &quot;text/xml&quot;);
    CONTENT_TYPES.put(&quot;zip&quot;, &quot;application/zip&quot;);
    CONTENT_TYPES.put(&quot;exe&quot;, &quot;application/octet-stream&quot;);
}

public static void main(String[] args) throws IOException {
    String documentRoot = args[0];
    int port = Integer.parseInt(args[1]);

    ServerSocket server = new ServerSocket(port);

    while (true) {
        Socket client = server.accept();

        // HTTPリクエストを出力
        outputRequest(client);

        // HTTPレスポンスを出力
        outputResponse(client, documentRoot);
    }
}

private static void outputRequest(Socket client) throws IOException {
    System.out.println(&quot;------------------------------------------&quot;);

    BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));

    // 1行目からリクエストパスを取得しておく(/でアクセスされた場合はindex.htmlを表示)
    String inline = br.readLine();
    requestPath = inline.split(&quot; &quot;)[1];
    if (requestPath.endsWith(&quot;/&quot;)) {
        requestPath += &quot;index.html&quot;;
    }

    while (br.ready() &amp;&amp; inline != null) {
        System.out.println(inline);
        inline = br.readLine();
    }

    System.out.println(&quot;------------------------------------------&quot;);
}

private static void outputResponse(Socket client, String documentRoot) throws IOException {
    PrintStream ps = new PrintStream(client.getOutputStream());

    String responseFile = documentRoot + requestPath;
    File file = new File(responseFile);

    FileInputStream fis = null;
    try {
        fis = new FileInputStream(file);

        // ヘッダー出力
        int bodyLength = (int) file.length();
        ps.println(&quot;HTTP/1.1 200 OK&quot;);
        ps.println(&quot;Content-Length: &quot; + bodyLength);
        ps.println(&quot;Content-Type: &quot; + getContentType(file));
        ps.println(&quot;&quot;);

        // ボディ出力
        byte buf[] = new byte[bodyLength];

        fis.read(buf);
        ps.write(buf, 0, bodyLength);
        ps.flush();
    } catch (FileNotFoundException e) {
        // ヘッダー出力
        ps.println(&quot;HTTP/1.1 404 Not Found&quot;);
        ps.println(&quot;&quot;);
        ps.println(&quot;&lt;h1&gt;指定されたファイルは存在しません。&lt;/h1&gt;&quot;);
    } finally {
        if (fis != null) {
            fis.close();
        }
    }

    ps.close();
}

private static String getContentType(File file) {
    String extension = getExtension(file);

    String contentType = CONTENT_TYPES.get(extension);

    if (contentType == null) {
        return &quot;text/html&quot;;
    } else {
        return contentType;
    }
}

private static String getExtension(File file) {
    String path = file.getPath();
    int lastDotPosition = path.lastIndexOf(&quot;.&quot;);

    String extension = null;
    if (lastDotPosition == -1) {
        extension = &quot;&quot;;
    } else {
        extension = path.substring(lastDotPosition + 1).toLowerCase();
    }

    return extension;
}

} [/java]

コードはGistにもペタリ。 https://gist.github.com/2260004

Socket の勉強のためにエコーサーバー書いた

前々から、Jarひとつで動くHTTPサーバーを書いてみたいと思っていて(既にたくさんあると思うけど)、それには Socket でがちゃがちゃしないといけないとの事だったので、重い腰を上げて Socket の勉強をしてみた。

とりあえず、リクエストされた文字列をそのまま返すだけのエコーサーバーを書いてみた。 一応、マルチスレッド対応。

Server

[java] import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket;

public class EchoServer {

public static void main(String[] args) throws IOException {

    ServerSocket server = new ServerSocket(5000);

    // クライアントからの接続を待ち受ける
    while (true) {
        Socket client = server.accept();

        new Thread(new EchoServer().new EchoThread(client)).start();
    }
}

class EchoThread implements Runnable {

    Socket client;

    public EchoThread(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        echo(client);
    }

    private void echo(Socket client) {
        System.out.println(&quot;処理開始&quot;);

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }

        System.out.println(&quot;接続中 : &quot; + client.getRemoteSocketAddress());

        try {
            InputStream in = client.getInputStream();
            OutputStream out = client.getOutputStream();

            int recvByte;
            while ((recvByte = in.read()) != -1) {
                out.write(recvByte);
            }
        } catch (IOException e) {
            System.err.println(e);
        } finally {
            closeQuietly(client);
        }

        System.out.println(&quot;処理終了&quot;);
    }

    private void closeQuietly(Socket socket) {
        try {
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            // ignore
        }
    }
}

} [/java]

Client

[java] import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket;

public class EchoClient {

public static void main(String[] args) throws IOException {

    Socket socket = new Socket(&quot;localhost&quot;, 5000);
    System.out.println(&quot;サーバとの接続を確立 : &quot; + socket.getLocalPort());

    InputStream in = socket.getInputStream();
    OutputStream out = socket.getOutputStream();

    byte[] data = args[0].getBytes();
    out.write(data);
    System.out.println(&quot;送信 : &quot; + new String(data));

    int i = 0;
    byte[] output = new byte[data.length];
    int recvByte;
    while ((recvByte = in.read()) != -1) {
        output[i++] = (byte) recvByte;

        if (i &gt;= data.length) {
            break;
        }
    }

    System.out.println(&quot;受信 : &quot; + new String(output));

    socket.close();
}

} [/java]

バイトの読み書きを1バイトずつやってるので、あまり効率的ではないんだろうな。

OpenAM(旧OpenSSO)でシングルサインオンをしてみる

やりたいことは、クロスドメイン(CDSSO)でシングルサインオンを実現し、ユーザーを識別(認可)して、アクセスできるURLを制限したい、というもの。 SSO製品は色々あるようですが、一番とっつきやすそうなOpenAMで試す。 SSOエージェントなど、独特のSSO用語は以下のページがとてもわかり易いです。 http://dev.ariel-networks.com/column/tech/opensso/

前提

SSOサーバーとSSOエージェントは別マシン(同じマシンで実行した(もちろんポートを変えて)が上手くいかなかった為)

  • SSOサーバーのホスト(10.29.56.64) : sso.server.com
  • SSOエージェントのホスト(10.29.55.56) : sso.agent.com

SSOはCookieを用いて、SSO Tokenをやり取りするので、localhostなどでアクセスして設定を行なってしまうと、Cookieドメイン属性がうまく設定できず、動かない可能性がある。 そのため、設定を行う場合は必ずFQDNでアクセスする。 試しにやる場合は、hostsファイルを編集するのが一番楽かと思います。

hosts

10.29.56.64  sso.server.com
10.29.55.56  sso.agent.com

なお、このhosts設定はSSOサーバーのマシン、SSOエージェントのマシン両方に設定しておく必要がある(どちらもそれぞれのホストを見に行く必要があるため)

環境

今回は、JavaEEサーバーにエージェントを組み込む、「エージェント型」で動かしてみる。 また、OpenSSOを組み込むTomcat(SSOサーバー用)とSSOエージェントを組み込むTomcat(SSOエージェント用)は、必ず別にする必要がある(SSOエージェントをインストールする際に、SSOサーバー用Tomcatは起動していて、SSOエージェント用Tomcatは停止している必要があるため)

手順

  1. OpenAMをインストール
  2. OpenAMにプロファイルを作成
  3. SSOエージェントをインストール
  4. SSO対象のWebアプリにフィルタを設定
  5. OpenAMにポリシーを作成
  6. 確認

1. OpenAMをインストール

SSOサーバーとして機能するOpenAMは、JavaのWebアプリとして配布されているので、配備はWARをデプロイするだけです。 以下から、WAR(openam_954.war)をダウンロードする。 http://www.forgerock.org/openam.html

WARを以下のように配置する(openam_954.war を openam.war にリネームした)

C:\Program Files\apache-tomcat-6.0.35_server\webapps\openam.war

Tomcatを起動したら、以下にアクセスする。 http://sso.server.com:8080/openam

以下の画面が出れば正常にデプロイされている。

「デフォルト設定の作成」を選択する。 パスワードは、以下。

  • amAdmin : password1
  • UrlAccessAgent : password2

インストールが始まる。

正常に完了したら、「ログインに進む」をクリックする。

「amAdmin」ユーザのID、パスワードを入力すればログインできる(IDは大文字・小文字を区別しないようだ)

ログイン成功!

2. OpenAMにプロファイルを作成

引き続きそのままプロファイルを作成する。 プロファイルは~~みたいなものだと思います。 [アクセス制御]タブ → [/ (最上位のレルム)] → [エージェント]タブ → [J2EE]タブ に移動する。

「エージェント」の「新規...」ボタンをクリックして、プロファイルを作成する。

名前、パスワード、サーバーURL、エージェントURLは以降でも使うので、覚えておく。

ついでに、クロスドメインの設定もしておく 先ほど作成した、[tomcatagent] → [SSO]タブ → [クロスドメインSSO] に移動し、「有効」のチェックを付け、ページ上部の「保存」ボタンを押下する。

3. SSOエージェントをインストール

(SSOエージェント用マシンで実施) まず以下から、ZIPをダウンロードし、解凍する。 http://www.forgerock.org/openam.html 場所は以下にした(この場所をエージェントが参照しているようなので、日本語パス等は避けたほうがいいかも)

C:\work\SSO\j2ee_agents

次に、プロファイルで設定したパスワードをパスワードファイルとして保存しておく必要がある。 以下に、「pass」というファイル名でパスワードファイルを作成した(場所はどこでもいいと思う)

C:\Program Files\apache-tomcat-6.0.35-agent\agentpass\pass

そして、このファイルにパスワードを平文で記述する(ここでは「password3」)

これで、準備が整ったので以下を実行する(SSOサーバー用のTomcatを起動しておくこと)

C:\work\SSO\j2ee_agents\tomcat_v6_agent\bin\agentadmin.bat --install

  1. Enter the Tomcat Server Config Directory Path→ C:\Program Files\apache-tomcat-6.0.35-agent\conf
  2. OpenSSO server URL→ http://sso.server.com:8080/openam
  3. Enter the $CATALINA_HOME environment variable→ C:\Program Files\apache-tomcat-6.0.35-agent
  4. Install agent filter in global web.xml ?→ false
  5. Agent URL→ http://sso.agent.com:8080/agentapp
  6. Enter the Agent Profile name→ tomcatagent
  7. Enter the path to the password file→ C:\Program Files\apache-tomcat-6.0.35-agent\agentpass\pass

あと、忘れずに、以下のフォルダにある「agentapp.war」をエージェント用のTomcatに配備(C:\Program Files\apache-tomcat-6.0.35-agent\webapps)しておく。

C:\work\SSO\j2ee_agents\tomcat_v6_agent\etc

4. SSO対象のWebアプリにフィルタを設定

これで、SSOを行う準備が整ったので、SSO対象のWEBアプリへのリクエストがエージェントを経由するように、WEBアプリのweb.xmlにフィルタを作成する。 [xml] <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>SampleWeb2</display-name>

&lt;filter&gt;
    &lt;filter-name&gt;Agent&lt;/filter-name&gt;
    &lt;filter-class&gt;com.sun.identity.agents.filter.AmAgentFilter&lt;/filter-class&gt;
&lt;/filter&gt;
&lt;filter-mapping&gt;
    &lt;filter-name&gt;Agent&lt;/filter-name&gt;
    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
    &lt;dispatcher&gt;INCLUDE&lt;/dispatcher&gt;
    &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
    &lt;dispatcher&gt;ERROR&lt;/dispatcher&gt;
&lt;/filter-mapping&gt;

&lt;servlet&gt;
    &lt;servlet-name&gt;Sample1&lt;/servlet-name&gt;
    &lt;servlet-class&gt;org.sample.Sample1&lt;/servlet-class&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;Sample1&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/Sample1&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
&lt;servlet&gt;
    &lt;servlet-name&gt;Sample2&lt;/servlet-name&gt;
    &lt;servlet-class&gt;org.sample.Sample2&lt;/servlet-class&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;Sample2&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/Sample2&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;

</web-app> [/xml] 大事なところはフィルタ「Agent」です。これでこのアプリへの全てのリクエストがエージェントを経由することになる。

ちなみにサンプル用のWEBアプリは以下のような構成。

アプリケーション アクセスパス アクセスできるユーザー
SampleWeb1 /SampleWeb1/Sample1 sample0, sample1
/SampleWeb1/Sample2 sample0, sample1
SampleWeb2 /SampleWeb2/Sample1 sample0, sample2
/SampleWeb2/Sample2 sample0, sample2

5. OpenAMにポリシーを作成

最後に、OpenAMにどのURLをどのユーザに許可するなどのポリシーを作成する。 まずユーザーを作成しておく。 [アクセス制御]タブ → [/ (最上位のレルム)] → [対象]タブ で「新規...」ボタンを押下し、ユーザーを作成する。 今回は先ほど書いてるように「sample0, sample1, sample2」を作成した。

次にポリシーを作成する。 [アクセス制御]タブ → [/ (最上位のレルム)] → [ポリシー]タブ で「新規ポリシー...」ボタンを押下する。

一般 名前 : SampleWeb1

ルール サービスタイプ : URL ポリシーエージェント (リソース名あり) 名前 : Sample1 リソース名 : http://sso.agent.com:8080/SampleWeb1/Sample1 アクション : GET,POST(共に許可)

同様にして「/SampleWeb1/Sample2」を許可するルールも追加しておく。

対象 タイプ : OpenAM アイデンティティー対象 名前 : identity1 sample0, sample1 を選択

SampleWeb2用の新規ポリシーも上記と同様にして作成する。

6. 確認

最後に正しく、SSOとアクセス制御が出来ているか確認する。 エージェントが組み込まれたTomcatが起動していない場合は起動する。

http://sso.agent.com:8080/SampleWeb1/Sample1 にアクセス。未ログインなので、ログイン画面が表示される。

「sample1」でログインすると、正常にアプリにアクセスできる。

http://sso.agent.com:8080/SampleWeb2/Sample1 にアクセス。権限が無いので、403エラーになる。

一旦、ログアウト(http://sso.server.com:8080/openam/UI/Logout にアクセス)して、 再度、http://sso.agent.com:8080/SampleWeb1/Sample1 にアクセス。今度は「sample0」でログインする。

「sample0」は別アプリの /SampleWeb1 と /SampleWeb2 にそれぞれアクセスできる。

なぜ Java の配列は共変で、Generics は共変ではないのか

まず、Java の配列がタイプセーフではない話。 [java] public class ExampleArray {

public static void main(String[] args) {
    String[] strArray = {&quot;test1&quot;, &quot;test2&quot;};
    Object[] objArray = strArray; // 配列は共変なので代入可能

    objArray[0] = new Integer(3); // java.lang.ArrayStoreException
}

} [/java] 上記のように、Java の配列は共変という性質を持っているので、Object に String を代入することができます。つまり、String は Object のサブクラスである、ということです。 しかし、Generics の場合は、この性質が当てはまりません(Generics は共変ではない) [java] public class ExampleGenerics {

public static void main(String[] args) {
    List&lt;String&gt; strList = new ArrayList&lt;String&gt;();
    strList.add(&quot;test1&quot;);
    strList.add(&quot;test2&quot;);

    List&lt;Object&gt; objList = new ArrayList&lt;Object&gt;();

    objList = strList; // コンパイルエラー
}

} [/java]

なぜ、配列は共変で、Generics は共変ではないのか?(一緒の方が分かりやすいのに)

その理由の前に、 配列の例で見たように、型の混入現象は、強い型付け言語である Java としては避けたい現象です。 配列の場合は、不正な型が混入した場所で例外(java.lang.ArrayStoreException)を投げてくれます。 (これは、結構大事な所で、使用時(get)ではなく、設定時(set)にきちんと例外を投げてくれると、バグの混入場所が特定しやすいです) なぜこんなことができるかというと、配列は自分が何の型であるかを自身で知っている(バイトコードに型情報が存在する)ので、違う型を入れたときに、自分と違うということが判定できるのです。

一方、Generics の場合はコンパイル時に型消去という操作が行われます。 型消去については、別エントリーでも書いてます(Generics(Java)の型消去について

Generics のコードは、コンパイル後にはその型情報を一切残していないので、java.lang.ArrayStoreException のような例外を投げることができません。 そのため、共変ではなくして、型の混入を防いでいるのではないかと思います。

Generics で共変っぽいことをしたい場合は、extends とかの境界条件をつければ可能です。