LoginSignup
8
6

More than 5 years have passed since last update.

アノテーションでJAX-RSリソースへのアクセスを制限する

Last updated at Posted at 2014-11-20

概要

JAX-RSリソースへのアクセスをRolesAllowedアノテーションで制限したいなぁというお話です。

:fearful: :fearful: :fearful:

2014/12/23 追記
下記手順だと、SecurityContextを上書きできていないのでまともに動作しません。
また調べて書き直します。

:fearful: :fearful: :fearful:

環境

  • 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.xmldependenciesセクションに追加します。

        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
            <version>2.10.4</version>
            <scope>provided</scope>
        </dependency>
8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6