アノテーションでJAX-RSリソースへのアクセスを制限する
概要
JAX-RSリソースへのアクセスをRolesAllowed
アノテーションで制限したいなぁというお話です。
2014/12/23 追記
下記手順だと、SecurityContextを上書きできていないのでまともに動作しません。
また調べて書き直します。
環境
- Java 1.8.0 u25
- Glassfish 4.1
仕様
http://example.com/ へのアクセスは誰にでも許し、http://example.com/secure へのアクセスを制限します(403 Forbiddenを返します) 。
403 Forbiddenのときには、自前のメッセージを出力します。
ソースコード
JAX-RSフィルタプロバイダクラス
ContainerRequestFilter
を継承して作ります。ContainerRequestFilter
は全リソースへのアクセス前、リソースが作成される前に実行されます。
@PreMatching
アノテーションを付けると、どのリソースを実行するのか決定する前にフィルタがかかります。
@Priority
アノテーションはフィルタを実行する順番を決めます。
肝はfilter
メソッドです。ここでsetSecurityContext
を呼んで、自前のSecurityContext
をぶっこんでいます。
SecurityContextFilter.java
import com.sun.security.auth.UserPrincipal;
import java.security.Principal;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
@PreMatching
@Provider
@Priority(Priorities.AUTHENTICATION)
public class SecurityContextFilter implements ContainerRequestFilter {
private static final Logger LOG = Logger.getLogger(SecurityContextFilter.class.getName());
UserPrincipal userPrincipal = new UserPrincipal("My name");
SecurityContext secuityContext = new SecurityContext() {
@Override
public Principal getUserPrincipal() {
LOG.log(Level.INFO, "*** getUserPrincipal");
return userPrincipal;
}
@Override
public boolean isUserInRole(String role) {
LOG.log(Level.INFO, "*** isUserInRole: {0}", role);
return false;
}
@Override
public boolean isSecure() {
LOG.log(Level.INFO, "*** isSecure");
return false;
}
@Override
public String getAuthenticationScheme() {
LOG.log(Level.INFO, "*** getAuthenticationScheme");
return null;
}
};
@Override
public void filter(ContainerRequestContext requestContext) {
LOG.log(Level.INFO, "*** Filter invoked");
requestContext.setSecurityContext(secuityContext);
}
}
SecurityContextクラス
このクラスで認証しているユーザがロールを持っているかどうかを判断します。
isUserInRoleメソッド
本来であれば、下記オーバライドメソッドの中で、引数role
が認証しているユーザにマッチしているかどうかを返してあげます。
ここではお試しなのでfalse
を返しています。つまり誰もアクセスできません。
@Override
public boolean isUserInRole(String role) {
LOG.log(Level.INFO, "*** isUserInRole: {0}", role);
return false;
}
getUserPrincipal
ユーザー名とかを保持しているUserPrincipal
を返してあげます。
@Override
public Principal getUserPrincipal() {
LOG.log(Level.INFO, "*** getUserPrincipal");
return userPrincipal;
}
isSecure
コネクションがセキュアかどうか(例えばhttps)を返してあげます。
@Override
public boolean isSecure() {
LOG.log(Level.INFO, "*** isSecure");
return false;
}
getAuthenticationScheme
認証済みの場合は、どうやって認証したか(BASIC認証、DIGEST認証、フォーム認証)を返してあげます。
とりあえずNull
でおk(?)
@Override
public String getAuthenticationScheme() {
LOG.log(Level.INFO, "*** getAuthenticationScheme");
return null;
}
リソースクラス
誰でもアクセスできるリソース
GenericResource.java
import javax.annotation.security.PermitAll;
import javax.ws.rs.Produces;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
@Path("/")
@PermitAll
public class GenericResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getXml() {
return "Hello JAX-RS";
}
}
秘密のリソース
「user」ロールのひとしかアクセスできません(ということにしています)。
SecureResource.java
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Produces;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
@Path("secure")
@RolesAllowed("user")
public class SecureResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getXml() {
return "Hello Secret";
}
}
RolesAllowed
アノテーションで、アクセスを許可するユーザのロールを文字列で記載します。
403 Forbidden例外を捕まえるプロバイダクラス
ForbiddenException
が起こったら、このクラスで捕まえさせます。
「members only」というメッセージを出力しています。
ForbiddenMapper.java
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class ForbiddenMapper implements ExceptionMapper<javax.ws.rs.ForbiddenException> {
private static final Logger LOG = Logger.getLogger(ForbiddenMapper.class.getName());
@Override
public Response toResponse(ForbiddenException e) {
LOG.log(Level.INFO, "Forbbidden catched.");
return Response.status(Response.Status.FORBIDDEN)
.entity("Members only")
.type(MediaType.TEXT_PLAIN)
.build();
}
}
アプリケーションクラス
作成したリソースとプロバイダを登録しています。
肝はRolesAllowedDynamicFeature
です。これがないとRolesAllowd
アノテーションが動きません。
ApplicationConfig.java
import java.util.Set;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
@javax.ws.rs.ApplicationPath("/")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
register(RolesAllowedDynamicFeature.class);
registerClasses(getMyClasses());
}
private Set<Class<?>> getMyClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
addRestResourceClasses(resources);
resources.add(RolesAllowedDynamicFeature.class);
return resources;
}
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(ForbiddenMapper.class);
resources.add(GenericResource.class);
resources.add(SecureResource.class);
resources.add(SecurityContextFilter.class);
}
}
pom.xml
pom.xml
のdependencies
セクションに追加します。
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>2.10.4</version>
<scope>provided</scope>
</dependency>