JAX-RS(Jersey)のFilter(RequestFilter)を使う
JAX-RSのフィルタを使うが、前回書いた通り認可処理でとりあえず使うだけなので、RequestFilterが中心。
参考
Chapter 10. Filters and Interceptors
環境
- Java8
- Tomcat8
- Jersey2.22.1
とりあえずFilter使う
ContainerRequestFilter
リクエストフィルタはContainerRequestFilterを使用する。
@Provider public class LoginFilter implements ContainerRequestFilter { @Inject private SessionDto sessionDto; @Override public void filter(ContainerRequestContext requestContext) throws IOException { System.out.println("loginfilter: " + this.sessionDto.getUserDto()); if (this.sessionDto.getUserDto() == null) { requestContext.abortWith( Response.status(Response.Status.UNAUTHORIZED) .entity("Not Logged In.") .build() ); } } }
SessionDtoは@SessionScopedなクラス。SessionDtoにUserDtoが無ければ401(UNAUTHORIZED)を返す。
Jerseyの設定2(web.xmlとかApplicationクラスとか) - edgegram の(1)パターンで動かしているので、Filterクラスを読みこませるために@Providerを付けている。
JerseyのドキュメントにあるFilterサンプルには@Providerが付いていないが、Applicationでクラス指定している前提なのだろう。
リソースクラス
@Path("/") @RequestScoped public class Users { @Inject private SessionDto sessionDto; @Path("/user") @GET @Produces(MediaType.APPLICATION_JSON) public UserDto user() { UserDto dto = this.sessionDto.getUserDto(); return dto; } }
セッションに格納されているUserDtoをJSONで返すだけ。
テスト
ブラウザで/userにアクセスすると「Not Logged In.」と表示される。
ブラウザの開発者ツールでステータスを確認するとちゃんと401になっている。
サーバーのコンソールには「loginfilter: null」が出力されている。
Name binding
次はUserDtoがセッションに格納されている時にFilterを通るか確認。
そのためにセッションにUserDtoを入れる擬似ログイン処理を追加する。
@Path("/") @RequestScoped public class Users { @Inject private SessionDto sessionDto; @Path("/user") @GET @Produces(MediaType.APPLICATION_JSON) public UserDto user() { UserDto dto = this.sessionDto.getUserDto(); return dto; } @Path("/login") @GET public Response login() { UserDto dto = new UserDto(); dto.setId(1); dto.setName("test"); ・・・ this.sessionDto.setUserDto(dto); return Response.ok().build(); } }
セッションにUserDtoを格納してステータス200を返す。
これで/login → /user の順にアクセスすれば、/userはUserDtoのJSONが返ってくるはず。
なのだが、Filterは全リソースに適用されるので、このままだと/loginにもFilterがかかって401が返される。
特定のリソースだけにFilterを適用させたい、という場合にName bindingを使う。
アノテーション
@NameBinding @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface LoggedIn {}
まず@NameBindingなアノテーションを作る。実行時に判別させるので@Retention(RetentionPolicy.RUNTIME)。
ContainerRequestFilter with NameBinding
@Provider @LoggedIn public class LoginFilter implements ContainerRequestFilter { @Inject private SessionDto sessionDto; @Override public void filter(ContainerRequestContext requestContext) throws IOException { System.out.println("loginfilter: " + this.sessionDto.getUserDto()); if (this.sessionDto.getUserDto() == null) { requestContext.abortWith( Response.status(Response.Status.UNAUTHORIZED) .entity("Not Logged In.") .build() ); } } }
Filterにアノテーションを付与する。
これでこのFilterは@LoggedInなリソースにしか効かなくなる。
リソースクラス with NameBinding
@Path("/") @RequestScoped public class Users { @Inject private SessionDto sessionDto; @Path("/user") @GET @Produces(MediaType.APPLICATION_JSON) @LoggedIn public UserDto user() { UserDto dto = this.sessionDto.getUserDto(); return dto; } @Path("/login") @GET public Response login() { UserDto dto = new UserDto(); dto.setId(1); dto.setName("test"); this.sessionDto.setUserDto(dto); return Response.ok().build(); } }
/userのメソッドだけ@LoggedIn付与。
テスト2
/loginにアクセスするとステータス200が返ってくる。
その後/userにアクセスすると「{"id":1,"name":"test"}」が表示される。
動的バインディング
NameBindingは静的Binding。動的にBindingしたい場合はDynamicFeatureを使う。
使い方はJerseyドキュメント参照。FeatureContextにFilterやInterceptorをresiterすればいい。
優先順
同じフィルタ(今回だとContainerRequestFilter)が複数ある場合に優先順を指定できる。
というか指定しなければ順番は保証されないのでするべし。
Interceptorも同様。
優先順を指定するにはFilterクラスに@Priorityを付与する。
@Provider @LoggedIn @Priority(Priorities.AUTHENTICATION) public class LoginFilter implements ContainerRequestFilter { ・・・ }
value値はint型。 Prioritiesクラスにざっくりの固定値が定義されているので、それを使ってもいいし直接数値を設定してもいい。
Priorityの順序
FilterやInterceptorの処理順序はPriorityの数値順ではない。
Priorityの数値が大きいほどリソースに近い処理となる。
具体的に言うと、Request系処理は数値が小さい順に処理され、Response系処理は逆に大きい順に処理される。
特にResponse処理の優先順を指定する時は注意。