X

An Oracle blog about WebLogic Channel

[連載] WebLogic Server 12cでJava EE 6を動かしてみよう!(7) JAX-RS 第2回

Nobuhiko Sekiya
Sales Consultant
実践! Java EE 6 ロゴ

本連載は、サンプル・アプリケーションの開発を通じてJava Platform, Enterprise Edition 6 (Java EE 6)の仕様とその魅力をお伝えすることを目的としています。前回はOEPEとWebLogic Server12cを使ってJAX-RSのサンプルアプリを実装しました。第2回では第1回に続き、JAX-RSのプログラミングを行っていきます。その中で、WebLogic Server12c上でJAX-RSを開発するときのポイントとなるJerseyのアップグレード方法やJAX-RSのトレース方法、セキュリティの設定についてもご紹介します。(日本オラクル Fusion Middleware事業統括本部 関屋信彦)

JAX-RSのトレース方法

いきなりですが、最初にWebLogic Server12cにおけるJAX-RSのトレース方法について紹介したい思います。このトレースを出力させることで開発中のデバッギング作業が大分楽になります。やり方は簡単です。web.xmlにパラメータを追加するだけです。

web.xml (変更) ※赤文字の部分を追加してください
  

<servlet>

<description>JAX-RS Tools Generated - Do not modify</description>

<servlet-name>JAX-RS Servlet</servlet-name>

<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>

<init-param>

<param-name>com.sun.jersey.config.feature.Trace</param-name>

<param-value>true</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

追加後、前回同様、「Project Explorer」ペインで「JaxRsSample1」を右クリックし、「Run as」→「Run on Server」からアプリケーションを実行します。そして、前回と同じようにWindowsのコマンドプロンプトを開き、前回作成したput_msg1.jsonのテキストが置いてあるフォルダに移動し、次のコマンドを実行します。

curl -X PUT -i -H "Content-type: application/json" -d @put_msg1.json  http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001

レスポンスには、トレース情報が含まれてきます。

HTTP/1.1 204 No Content
Date: Fri, 26 Apr 2013 06:43:27 GMT
Content-Length: 0
X-Jersey-Trace-010: matched message body reader: class org.codehaus.jettison.json.JSONObject, "application/json" -> com.sun.jersey.json.impl.provider.
entity.JSONObjectProvider$App@29a4b80
X-Jersey-Trace-002: accept right hand path java.util.regex.Matcher[pattern=/messages(/.*)? region=0,20 lastmatch=/messages/uid-abc001]: "/messages/uid
-abc001" -> "/messages" : "/uid-abc001"
X-Jersey-Trace-008: accept resource methods: "messages/uid-abc001", PUT -> jaxrssample1.resource.UserMessagesResource@2bea0a0
X-Jersey-Trace-009: matched resource method: public void jaxrssample1.resource.UserMessagesResource.setUserMessage(org.codehaus.jettison.json.JSONObje
ct) throws org.codehaus.jettison.json.JSONException
X-Jersey-Trace-006: accept sub-resource locator: "messages" : "/uid-abc001" -> @Path("/uid-{userid}") jaxrssample1.resource.MessagesResource@2be81c7.g
etUserMessages(java.lang.String) = jaxrssample1.resource.UserMessagesResource@2bea0a0
X-Jersey-Trace-007: match path "" -> "/seq-([^/]+?)(/.*)?", ""
X-Jersey-Trace-004: match path "/uid-abc001" -> "/uid-([^/]+?)(/.*)?", ""
X-Jersey-Trace-005: accept right hand path java.util.regex.Matcher[pattern=/uid-([^/]+?)(/.*)? region=0,11 lastmatch=/uid-abc001]: "/uid-abc001" -> "/
uid-abc001" : ""
X-Powered-By: Servlet/3.0 JSP/2.2
X-Jersey-Trace-003: accept resource: "messages" -> @Path("/messages") jaxrssample1.resource.MessagesResource@2be81c7
X-Jersey-Trace-000: accept root resource classes: "/messages/uid-abc001"
X-Jersey-Trace-001: match path "/messages/uid-abc001" -> "/application\.wadl(/.*)?", "/messages(/.*)?"

トレース行の頭にX-Jersey-Trace-xxxの形式でシーケンス番号があり、これがサーバー側で処理された順番を表します。出力される行の順番が必ずしもシーケンス順でないことに注意してください。トレースを見ていただくとわかる通り、これでURLが正しく@Pathで指定したパスにマッチしているかや、どのJAX-RSリソースクラスやリソースメソッドが使われているかがわかります。そして何より、今まで目に見えなかったエンティティプロバイダの情報を表示してくれます。これで送られてきたデータを、JAX-RSのリソースメソッドの引数の型のインスタンスに変換してくれている実体を把握することができます。今回の例では、JSON形式のデータをjettisonライブラリのJSONObject型に変換してくれているのは、jerseyライブラリのJSONObjectProviderであることがわかります。

X-Jersey-Trace-010: matched message body reader: class org.codehaus.jettison.json.JSONObject, "application/json" -> com.sun.jersey.json.impl.provider.
entity.JSONObjectProvider$App@29a4b80

この記事では、以降はトレースがOFFになっているものとして出力ログを記載します。

GETでメッセージリストを取得


前回は、メッセージの投稿と、メッセージIDを指定してのメッセージの取得を、それぞれ/messages/uid-{userid}のパスに対するPUTと/usermessages/uid-{userid}/seq-{usermessageid}のパスに対するGETとして実装しました。今回は残り2つの処理を実装したいと思います。








URIパス(リソース)HTTPメソッド説明
/messagesGETWebサービス内の全メッセージを表すリソースです。GETにより一覧を取得できます。
/messages/uid-{userid}
例: /messages/uid-sekiya123
GET/PUT指定されたユーザーIDの全てのメッセージを表すリソースです。PUTによりそのユーザーのメッセージを投稿できます。
/messages/uid-{userid}/seq-{usermessageid}
例:/messages/uid-sekiya123/seq-1
GET各ユーザーの個々のメッセージを表します。ユーザー内で一意なユーザーメッセージIDで指定します。

まずは特定ユーザーに関する全てのメッセージを取得する処理を実装します。これは/messages/uid-{userid}に対するGETとして実装します。/messages/uid-{userid}に対応するリソースクラスはUserMessagesResourceクラスでした。新しくgetUserMessagesというJavaメソッドをこのクラスに追加し、この処理を実装しましょう。そしてこれに@GETをつけることで呼び出し可能となります。

getUserMessagesでは以下のことを行います。


  • メッセージが保存されているクラス(StaticMemory)からuserid指定でそのユーザーのメッセージを全てリストとして取得します。
  • JSONArrayというJettisonライブラリの配列型に情報を変換してgetUserMessagesの戻り値とします。@Produces("application/json")もつけ、出力はJSONに限定させます。
  • その時に、追加の情報としてそれぞれのメッセージのURLを含めます。これにより、クライアントアプリケーションはメッセージの一覧を受け取った後に、特定のメッセージの詳細にアクセスする手段を得ることができます。一覧で渡す情報は最小限にし、あとは詳細を見る、といった使い方ができます。URLの作成にはJAX-RSのAPIであるUriBuilderを使います。これで簡単にURLを作成できます。UriBuilderはUriInfoというクラスから取得できますが、そのUriInfoはJAX-RSのコンテキスト情報経由で取得できます。JAX-RSのコンテキストは「@Context コンテキストタイプの型 変数名」という記述を、リソースクラスのフィールド変数あるいはリソースメソッドの引数として書けば、そこにインジェクションされます。コンテキストタイプとしてどのようなものがあるかはJAX-RS1.1の仕様書に記載されており、UriInfo、HttpHeaders、Requeest、SecurityContext、Providersといった型があります。 ここで実装するgetUserMessagesのJavaメソッドは@GETが付加されることでJAX-RSのリソースメソッドとなるので、引数として@Context UriInfoをインジェクションすることが可能となります。
UserMessagesResource.java (追加)

@GET

@Produces("application/json")

public JSONArray getUserMessages(@Context UriInfo uriInfo) {

List<Message> msglist = StaticMemory.getMessage(userid);

if (msglist == null) {

return null;

}

JSONArray uriArray = new JSONArray();

for (Message msg : msglist) {

try {

uriArray.put(MessageJSONBind.getJSON(msg).append("url", getUri(uriInfo, msg)));

} catch (JSONException e) {

e.printStackTrace();

}

}

return uriArray;

}


private String getUri(UriInfo uriInfo, Message msg) {

UriBuilder ub = uriInfo.getAbsolutePathBuilder();

URI userMessageUri = ub.path("seq-{usermessageid}").build(Integer.toString(msg.getUserMessageId()));

return userMessageUri.toString();

}

全ユーザーのメッセージの取得処理

次に、全ユーザーのメッセージ取得です。/messagesのパスに対するGETで取得できるようにします。/messagesに対するリソースクラスはMessagesResourceでした。さきほどと同じようなやり方で実装していきます。ただ、返すメッセージ情報はデータ量を減らす意味でメッセージ本文は含めず、タイトルだけとします。

MessagesResource.java (追加)

//ルートリソースクラス(クラス定義の上に@Pathが指定されているクラス)はフィールド変数にコンテキスト情報をインジェクションできます

@Context UriInfo uriInfo;

@GET

@Produces("application/json")

public JSONArray getMessages() {

List<Message> msglist = StaticMemory.getAll();

JSONArray uriArray = new JSONArray();

for (Message msg : msglist) {

try {

uriArray.put(MessageJSONBind.getJSONSmall(msg).put("url", getUri(msg)));

} catch (JSONException e) {

e.printStackTrace();

}

}

return uriArray;

}


private String getUri(Message msg) {

UriBuilder ub = uriInfo.getAbsolutePathBuilder();

URI userMessageUri = ub.path("/uid-{userid}").path("seq-{usermessageid}").build(msg.getUserid(), Integer.toString(msg.getUserMessageId()));

return userMessageUri.toString();

}
MessageJSONBind.java (追加)

public static JSONObject getJSONSmall(Message msg) {

try {

return new JSONObject().put("userid", msg.getUserid())

.put("title", msg.getTitle())

.put("posttime", msg.getPosttime());

} catch (JSONException je) {

return null;

}

}

Response型の利用

前回、PUTによるメッセージの投稿の処理をUserMessagesResourceクラスのsetUserMessageのJavaメソッドとして実装しましたが、このときのレスポンスとしてそのメッセージのアドレスであるURLをユーザーに返した方が親切なので、その実装を追加したいと思います。さらに、HTTPのステータスコードとして201 Createdを返すようにします。そうすることで、ユーザーに対して、投稿した情報が作成されたことを明確に示すことができます。このようにHTTPのレスポンスに色々な情報をつけたい場合はJAX-RS APIのResponse型を使います。

UserMessagesResourceのsetUserMessageに対しての変更コードです。今まで戻り値はvoidでしたが、ここでResponse型を返すようにするのがポイントです。

UserMessagesResource.java (変更) ※赤い文字列が変更部分です

@PUT

@Consumes("application/json")

public Response setUserMessage(@Context UriInfo uriInfo, JSONObject jsonEntity) throws JSONException {

Message msg = new Message();

msg.setUserid(userid);

int usermessageid = 0;

if (!jsonEntity.isNull("usermessageid")) {

usermessageid = jsonEntity.getInt("usermessageid");

}

msg.setUserMessageId(usermessageid);

msg.setPosttime(new java.util.Date());

msg.setTitle(jsonEntity.getString("title"));

msg.setMessage(jsonEntity.getString("message"));

StaticMemory.putMesasge(msg);

URI usermessageUri = uriInfo.getAbsolutePathBuilder().path("seq-{usermessageid}").build(msg.getUserMessageId());

ResponseBuilder response = Response.created(usermessageUri);

response.entity("Message posted as id: " + msg.getUserMessageId());

return response.build();

}

テスト

ここまでの実装を、実際にテストで確認します。前回同様、「Project Explorer」ペインで「JaxRsSample1」を右クリックし、「Run as」→「Run on Server」からアプリケーションを実行します。そして、前回と同じようにWindowsのコマンドプロンプトを開き、前回作成したput_msg1.jsonのテキストが置いてあるフォルダに移動し、次のコマンドを実行します。

curl -X PUT -i -H "Content-type: application/json" -d @put_msg1.json  http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001

そうすると、次のレスポンスが返ってきます。

HTTP/1.1 201 Created
Date: Wed, 24 Apr 2013 21:43:37 GMT
Transfer-Encoding: chunked
Location: http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001/seq-1
Content-Type: application/json
Message posted as id: 1

201 Createdのステータスコードが返り、Http ヘッダのLocationとして作成されたメッセージへのURLを返すことができました。curlのGETでこのLocationへのURLを指定してみましょう。前回と同じコマンドになり、前回同様のレスポンスが得られます。

curl -X GET -i -H "Accept: application/json" http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001/seq-1
HTTP/1.1 200 OK
Date: Wed, 24 Apr 2013 22:00:31 GMT
Transfer-Encoding: chunked
Content-Type: application/json
{"userid":"abc001","usermessageid":1,"title":"today tweet","message":"Is this going to end??","posttime":"Thu Apr 25 07:00:01 JST 2013"}

何気なく前回のテストと全くと同じURLを指定したわけですが、前回は開発したのが私たち自身だったのでURLの書き方がわかっていました。しかし、今回はサーバーから通知されたURLを使ったという違いがあります。JAX-RSはステートレスなアプリケーションが基本であるため、このように各種リソースのURLをクライアントに通知することが重要になってきます。そのためにもUriBuilderなどのAPIがJAX-RSにはあります。

次に、同じcurlのPUT処理を何回か実行し、複数のメッセージを投稿してください。また、ユーザーIDをuid-abc001からuid-def002に変更し、同じく何度かメッセージを投稿してください。この後、特定のユーザーの全メッセージの取得を行います。

curl -X GET -i -H "Accept: application/json" http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001/

レスポンスとして、そのユーザーabc001の全てのメッセージが取得できました。

HTTP/1.1 200 OK
Date: Wed, 24 Apr 2013 22:13:14 GMT
Transfer-Encoding: chunked
Content-Type: application/json
[{"userid":"abc001","usermessageid":1,"title":"today tweet","message":"Is this going to end??","posttime":"Thu Apr 25 07:00:01 JST 2013","url":["http:
\/\/localhost:7001\/JaxRsSample1\/jaxrs\/messages\/uid-abc001\/seq-1"]},{"userid":"abc001","usermessageid":2,"title":"today tweet","message":"Is this
going to end??","posttime":"Thu Apr 25 07:00:03 JST 2013","url":["http:\/\/localhost:7001\/JaxRsSample1\/jaxrs\/messages\/uid-abc001\/seq-2"]},{"useri
d":"abc001","usermessageid":3,"title":"today tweet","message":"Is this going to end??","posttime":"Thu Apr 25 07:12:05 JST 2013","url":["http:\/\/loca
lhost:7001\/JaxRsSample1\/jaxrs\/messages\/uid-abc001\/seq-3"]}]

次にURLを変えて全ユーザー分の情報を取得しましょう。

curl -X GET -i -H "Accept: application/json" http://localhost:7001/JaxRsSample1/jaxrs/messages/

レスポンスとして、そのユーザーabc001とdef002の両方のメッセージを取得できました。こちらの方は意図したとおりmessage本文は含まれていません。

HTTP/1.1 200 OK
Date: Wed, 24 Apr 2013 22:15:45 GMT
Transfer-Encoding: chunked
Content-Type: application/json
[{"userid":"abc001","title":"today tweet","posttime":"Thu Apr 25 07:00:01 JST 2013","url":"http:\/\/localhost:7001\/JaxRsSample1\/jaxrs\/messages\/uid
-abc001\/seq-1"},{"userid":"abc001","title":"today tweet","posttime":"Thu Apr 25 07:00:03 JST 2013","url":"http:\/\/localhost:7001\/JaxRsSample1\/jaxr
s\/messages\/uid-abc001\/seq-2"},{"userid":"abc001","title":"today tweet","posttime":"Thu Apr 25 07:12:05 JST 2013","url":"http:\/\/localhost:7001\/Ja
xRsSample1\/jaxrs\/messages\/uid-abc001\/seq-3"},{"userid":"def002","title":"today tweet","posttime":"Thu Apr 25 07:12:11 JST 2013","url":"http:\/\/lo
calhost:7001\/JaxRsSample1\/jaxrs\/messages\/uid-def002\/seq-1"},{"userid":"def002","title":"today tweet","posttime":"Thu Apr 25 07:12:13 JST 2013","u
rl":"http:\/\/localhost:7001\/JaxRsSample1\/jaxrs\/messages\/uid-def002\/seq-2"},{"userid":"def002","title":"today tweet","posttime":"Thu Apr 25 07:12
:14 JST 2013","url":"http:\/\/localhost:7001\/JaxRsSample1\/jaxrs\/messages\/uid-def002\/seq-3"}]

全メッセージが取得されていますが、JAX-RSの@QueryParamを使えば、GET時のURLにパラメータを追加でき、例えばhttp://localhost:7001/JaxRsSample1/jaxrs/messages?count=10のように取得数を指定することも可能です。この辺りはご自身で試してみてください。

JAXBを使ってJSONとXMLの両方に対応

前回はJettisonというJSONライブラリを使ってJSONObjectのインスタンスを作成し、Jerseyに含まれるエンティティプロバイダがJSONObjectのインスタンスをHTTP出力に変換していました。今回は、JSON形式以外に、XMLの形式も出力させたいと思います。使用するのはJAXBです。JAXBは、Javaに含まれるXMLのライブラリですが、JAXBを使ったままJSON出力にも対応できます。前回のJettisonライブラリを使ったやり方は、シンプルで柔軟にJSON出力できるのですが、JAXBに慣れている方は今回のやり方でJSON出力させてもいいかもしれません。

Jerseyのアップグレード

図1: JerseyアップグレードのWEB-INF/lib構成
図1: JerseyアップグレードのWEB-INF/lib構成(クリックして拡大)

実は、JAXBを使ってのJSONデータ形式を出力するには、Jerseyをアップグレードする必要があります。よってここではJAXB対応前にWebLogic Server12cを使った場合のJerseyのアップグレード方法を紹介します。
アップグレードの基本的なやり方はRESTful Webサービスの開発に紹介されていますがライブラリを取得するところなどは紹介されていないので、ここでは一例として紹介させていただきます。まずはJerseyを自分でダウンロードします。Jerseyのサイトに行き、1.xxの最新のバージョンをダウンロードします。今回は1.17のバージョンをダウンロードします。その他のjettisonやjackson系のライブラリを含め、プロジェクトのWEB-INF/lib配下にjarライブラリをコピーします。確認したところ図のライブラリが今回のアプリケーションを動かすのに最低限必要なライブラリでした。

次に、WebContent/weblogic.xmlの編集が必要となります。ここではさきほどWEB-INF/libにコピーしたjarが使われるように明示的に設定を行う必要があります。通常WEB-INF/lib配下にコピーしたjarはweblogic.xmlの設定なく使うことができますが、今回のようにWebLogic Server12cの本体(システムクラスローダーで読み込まれるクラス)で使用されているクラス(Jersety1.9版)と同じクラスをWEB-INF/libに入れる場合は、設定が必要になります。そうしないと本体で使用されている古いクラスの方が使われてしまいます。

weblogic.xml (変更) ※赤文字の部分が追加個所です
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd">
<wls:weblogic-version>12.1.1</wls:weblogic-version>
<wls:context-root>JaxRsSample1</wls:context-root>
<wls:container-descriptor>
<wls:prefer-application-packages>
<!-- jersey-bundle-*.jar -->
<wls:package-name>com.sun.jersey.*</wls:package-name>
<wls:package-name>com.sun.research.ws.wadl.*</wls:package-name>
<wls:package-name>com.sun.ws.rs.ext.*</wls:package-name>
<!-- Jackson-*.jar -->
<wls:package-name>org.codehaus.jackson.*</wls:package-name>
<!-- jettison-*.jar -->
<wls:package-name>org.codehaus.jettison.*</wls:package-name>
<!-- jsr311*.jar -->
<wls:package-name>javax.ws.rs.*</wls:package-name>
<!-- asm.jar -->
<wls:package-name>org.objectweb.asm.*</wls:package-name>
</wls:prefer-application-packages>
</wls:container-descriptor>

</wls:weblogic-web-app>

MessageクラスのJAXB対応

MessageクラスをJAXB対応するためにはいくつかのJAXBアノテーションをつけるだけで基本的にOKです。ただ、今回はjava.util.Date型の日時を文字列に変換するためにDateAdapterというクラスを作って一つ工夫を入れています。

Message.java (変更) ※赤文字の部分が変更個所です
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)

public class Message {


@XmlElement(name="message")

private String message;


@XmlElement(name="userid")

private String userid;


@XmlElement(name="posttime")

@XmlJavaTypeAdapter(jaxrssample1.util.DateAdapter.class)

private Date posttime;


@XmlElement(name="usermessageid")

private int userMessageId;


@XmlElement(name="title")

private String title;
DateAdapter.java (jaxrssample1.utilパッケージ)
package jaxrssample1.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, Date> {


@Override

public String marshal(Date date) throws Exception {

SimpleDateFormat sf = new SimpleDateFormat("yyyy/mm/dd hh:mm:ss");

return sf.format(date);

}


@Override

public Date unmarshal(String date) throws Exception {

SimpleDateFormat sf = new SimpleDateFormat("yyyy/mm/dd hh:mm:ss");

return sf.parse(date);

}
}

今回はPUTによるメッセージの投稿と、GETによるユーザーの全メッセージ取得の部分だけをJAXB版に変更したいと思います。よって、UserMessageResourceクラスのsetUserMessageと、getUserMessagesを変更するわけですが、前回の同メソッドはコメントアウトし、今回のJAXB版は新しく作成してください。前回の同メソッドのコードと比べMessageクラスをそのままJAX-RSリソースメソッドの引数や戻り値として使えるようになり、大分コードが簡略化されました。

UserMessagesResource.java (追加)
//※前のsetUserMessageとgetUserMessagesはコメントアウトしてください

@PUT

@Consumes({"application/json", "application/xml"})

public Response setUserMessage(@Context UriInfo uriInfo, Message msg) throws JSONException {

msg.setUserid(userid);

StaticMemory.putMesasge(msg);


URI usermessageUri = uriInfo.getAbsolutePathBuilder()

.path("seq-{usermessageid}").build(msg.getUserMessageId());

ResponseBuilder response = Response.created(usermessageUri);

response.entity("Message posted as id: " + msg.getUserMessageId());

return response.build();

}

@GET

@Produces({"application/json", "application/xml"})

public List<Message> getUserMessages(@Context UriInfo uriInfo) {

return StaticMemory.getMessage(userid);

}

テスト

では、ここで再度「Run on Server」で再デプロイを行い、テストしてきましょう。
投稿する情報はxml形式のファイルとして新しく作成します。put_msg1.xmlとします。

put_msg1.xml
<message>

<message>I want to post a xml message</message>

<title>Mesage By Xml</title>
</message>

curlコマンドではContent-typeをapplication/xmlに変更するのがポイントです。@で指定するファイル名の変更も忘れないようにしてください。

curl -X PUT -i -H "Content-type: application/xml" -d @put_msg1.xml  http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001

そうすると、次のレスポンスが返ってきます。

HTTP/1.1 201 Created
Date: Thu, 25 Apr 2013 02:33:56 GMT
Transfer-Encoding: chunked
Location: http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001/seq-1
Content-Type: text/plain
Message posted as id: 1

今度はuid-abc001のメッセージリストを取得しましょう。curlコマンドの中のAcceptをapplication/xmlとするのがポイントです。

curl -X GET -i -H "Accept: application/xml" http: //localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001/

そうすると、次のレスポンスが返ってきます。

HTTP/1.1 200 OK
Date: Thu, 25 Apr 2013 02:34:10 GMT
Transfer-Encoding: chunked
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><messages><message><message>I want to post a xml message</message><userid>abc001</userid><postt
ime>2013/33/25 11:33:56</posttime><usermessageid>1</usermessageid><title>Mesage By Xml</title></message></messages>

次にjson形式も試してみます。

curl -X GET -i -H "Accept: application/json" http: //localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001/

そうすると、次のレスポンスが返ってきます。

HTTP/1.1 200 OK
Date: Thu, 25 Apr 2013 02:36:07 GMT
Transfer-Encoding: chunked
Content-Type: application/json
{"message":[{"message":"I want to post a xml message","userid":"abc001","posttime":"2013/33/25 11:33:56","usermessageid":"1","title":"Mesage By Xml"},
{"message":"Is this going to end??","userid":"abc001","posttime":"2013/35/25 11:35:56","usermessageid":"2","title":"today tweet"}]}

JAX-RSのセキュリティ対応

現時点ではメッセージの投稿を誰でもできてしまっています。ここではメッセージの投稿は登録されたユーザーのみ、自身のユーザーIDに対して投稿できるようにセキュリティをかけたいと思います。今回ご紹介する方法は最もシンプルなやり方です。BASIC認証を使い、認証するユーザーは予めWebLogicに登録するやり方です。
まず、WebLogicへのユーザーの登録です。WebLogicの管理コンソールで登録するので次のURLでアクセスしてください。

http://localhost:7001/console

セキュリティ・レルムをクリックします。次の画面ではデフォルトのmyrealmという設定があるので、そのリンクをクリックします。



図2: WebLogic管理コンソール
図2: WebLogic管理コンソール(クリックして拡大)

「ユーザーとグループ」タブを開きます。

図3: セキュリティ・レルム - ユーザー画面
図3: セキュリティ・レルム - ユーザー画面(クリックして拡大)

ここにユーザーとしてabc001を登録します。「新規」ボタンをクリックし、次の画面では「名前」にabc001、「パスワード」にwelcome1と入力して「OK」を押します。

次にグループrestuserを登録します。グループを登録することで、後のweb.xmlでの指定で個々のユーザー名を指定せずに済みます。「グループ」タブを開き、「新規」ボタンを押し、次の画面で「名前」にrestuserを入力し、「OK」を押します。



図4: グループへのユーザーの登録
図4: グループへのユーザーの登録(クリックして拡大)

「ユーザー」タブを開き、さきほど作成したabc001ユーザーのリンクをクリックします。次に
「グループ」タブを開き、さきほど作成したrestuserを選択済みに移動して保存します。

次にweb.xmlの設定です。次のように追加してください。ポイントはセキュリティをかけるURLとそのHTTPメソッドを絞っていることです。今回はPUTによるメッセージの投稿処理にだけ、セキュリティをかけて勝手にメッセージを投稿できないようにします。

web.xml (変更) ※赤文字が追加部分です
(上部省略)

<servlet-mapping>

<servlet-name>JAX-RS Servlet</servlet-name>

<url-pattern>/jaxrs/*</url-pattern>

</servlet-mapping>

<security-constraint>

<web-resource-collection>

<web-resource-name>MessagesResource</web-resource-name>

<url-pattern>/jaxrs/messages/*</url-pattern>

<http-method>PUT</http-method>

</web-resource-collection>

<auth-constraint>

<role-name>user</role-name>

</auth-constraint>

</security-constraint>

<login-config>

<auth-method>BASIC</auth-method>

<realm-name>default</realm-name>

</login-config>

<security-role>

<role-name>user</role-name>

</security-role>

</web-app>

weblogic.xmlではweb.xmlのsecurity-roleのrole-nameと、管理コンソールで登録したユーザー名またはグループ名を関連付けます。ここではグループrestuserに関連付けます。

weblogic.xml (変更) ※赤文字が追加部分です
(上部省略)
<wls:context-root>JaxRsSample1</wls:context-root>

<security-role-assignment>

<role-name>user</role-name>

<principal-name>restuser</principal-name>

</security-role-assignment>

(下部省略)

では、さっそくテストしてみます。Eclipseの「Run on Server」で再デプロイを行ってください。まずは今まで通りのPUTコマンドを実行します。

curl -X PUT -i -H "Content-type: application/json" -d @put_msg1.json  http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001

「HTTP/1.1 401 Unauthorized」というレスポンスが返ってくるはずです。セキュリティがかかりました。
認証情報としてcurlのパラメータにユーザーとパスワードを渡します。

curl –u abc001:weblogic1 -X PUT -i -H "Content-type: application/json" -d @put_msg1.json  http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001

「HTTP/1.1 201 Created」となり、うまく認証されたことがわかります。次は認証情報なしでそのユーザーのメッセージをGETで取得できることを確認してください。

curl -X GET -i -H " Accept: application/json"  http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-abc001

現時点では自分が認証さえされればPUT時に自分以外のユーザーIDをURLパスに指定してもメッセージが投稿できてしまいます。今回のアプリケーションでは、これを防ぐにはJAX-RSリソースメソッド内で認証情報とURLパス上のuseridをマッチングする方法をとりたいと思います。認証情報はJAX-RS APIの@Context SecurityContextからアクセスできます。PUT処理を実装しているUserMessagesResourceクラスのsetUserMessageメソッドの引数にこのパラメータを追加します。URLパス上のユーザーIDと認証時のIDがマッチしない場合、HTTPステータスコード403を返すようにします。

UserMessagesResource.java ※赤文字が変更箇所です

@PUT

@Consumes({"application/json", "application/xml"})

public Response setUserMessage(@Context UriInfo uriInfo, @Context SecurityContext sc, Message msg) throws JSONException {

String username = sc.getUserPrincipal().getName();

if (!username.equals(userid)) {

ResponseBuilder response = Response.status(403);

return response.build();

}

msg.setUserid(userid);

StaticMemory.putMesasge(msg);


URI usermessageUri = uriInfo.getAbsolutePathBuilder()

.path("seq-{usermessageid}").build(msg.getUserMessageId());

ResponseBuilder response = Response.created(usermessageUri);

response.entity("Message posted as id: " + msg.getUserMessageId());

return response.build();

}

再度アプリケーションを実行します。認証時はabc001ユーザーとし認証し、URLパス上はdef002のユーザーIDを指定してみましょう。

curl -u abc001:welcome1 -X PUT -i -H "Content-type: application/json" -d @put_msg1.json  http://localhost:7001/JaxRsSample1/jaxrs/messages/uid-def002

「HTTP/1.1 403 Forbidden」がレスポンスとして返されます。これで他のユーザーのメッセージを投稿することはできなくなりました。

まとめ

本連載では2回にわたり、JAX-RS のプログラミングやWebLogic Server12cでJAX-RSを使う際のポイントについて説明しきました。JAX-RSは今年発表されるJavaEE7でも新しいバージョンが予定されており、今後の拡張が期待できます。

次回は、Java EE 6の目玉の一つであるCDI(Contexts and Dependency Injection)をとりあげます。お楽しみに。

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.