web-dev-qa-db-ja.com

のベストプラクティス REST JAX-RSとJerseyによるトークンベースの認証

私はJerseyでトークンベースの認証を有効にする方法を探しています。私は特定のフレームワークを使用しないようにしています。それは可能ですか?

私の計画は次のとおりです。ユーザーが私のWebサービスにサインアップし、私のWebサービスがトークンを生成し、それをクライアントに送信すると、クライアントはそれを保持します。それからクライアントは、リクエストごとに、ユーザー名とパスワードの代わりにトークンを送信します。

各リクエストと@PreAuthorize("hasRole('ROLE')")にカスタムフィルタを使用することを考えていましたが、これはトークンが有効であるかどうかをチェックするためにデータベースへのリクエストを大量に発生させると思いました。

それとも、フィルタを作成しないで、それぞれのリクエストにparamトークンを入れますか?各APIは最初にトークンをチェックし、その後リソースを取得するために何かを実行します。

409
DevOps85

トークンベースの認証の仕組み

トークンベースの認証では、クライアントはtokenと呼ばれるデータとhardクレデンシャル(ユーザー名やパスワードなど)を交換します。クライアントは、要求ごとに、ハード資格情報を送信する代わりに、トークンをサーバーに送信して認証と承認を実行します。

一言で言えば、トークンに基づく認証スキームは次の手順に従います。

  1. クライアントは、資格情報(ユーザー名とパスワード)をサーバーに送信します。
  2. サーバーは資格情報を認証し、有効な場合はユーザーのトークンを生成します。
  3. サーバーは、以前に生成されたトークンをユーザー識別子と有効期限とともに何らかのストレージに保存します。
  4. サーバーは、生成されたトークンをクライアントに送信します。
  5. クライアントは、各要求でトークンをサーバーに送信します。
  6. サーバーは、各リクエストで、着信リクエストからトークンを抽出します。トークンを使用して、サーバーはユーザーの詳細を検索して認証を実行します。
    • トークンが有効な場合、サーバーは要求を受け入れます。
    • トークンが無効な場合、サーバーは要求を拒否します。
  7. 認証が実行されると、サーバーは承認を実行します。
  8. サーバーは、トークンを更新するエンドポイントを提供できます。

注:サーバーが署名済みトークン(JWTなど、ステートレスを実行できる場合)を発行した場合、ステップ3は不要です。 認証)。

JAX-RS 2.0でできること(ジャージー、RESTEasy、Apache CXF)

このソリューションはJAX-RS 2.0 APIのみを使用します。ベンダー固有のソリューションを回避します。したがって、 JerseyRESTEasy 、および Apache CXF などのJAX-RS 2.0実装で動作するはずです。

トークンベースの認証を使用している場合、サーブレットコンテナによって提供され、アプリケーションのweb.xml記述子で設定可能な標準のJava EE Webアプリケーションセキュリティメカニズムに依存していないことに言及する価値があります。これはカスタム認証です。

ユーザー名とパスワードでユーザーを認証し、トークンを発行する

資格情報(ユーザー名とパスワード)を受け取って検証するJAX-RSリソースメソッドを作成し、ユーザーのトークンを発行します。

@Path("/authentication")
public class AuthenticationEndpoint {

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response authenticateUser(@FormParam("username") String username, 
                                     @FormParam("password") String password) {

        try {

            // Authenticate the user using the credentials provided
            authenticate(username, password);

            // Issue a token for the user
            String token = issueToken(username);

            // Return the token on the response
            return Response.ok(token).build();

        } catch (Exception e) {
            return Response.status(Response.Status.FORBIDDEN).build();
        }      
    }

    private void authenticate(String username, String password) throws Exception {
        // Authenticate against a database, LDAP, file or whatever
        // Throw an Exception if the credentials are invalid
    }

    private String issueToken(String username) {
        // Issue a token (can be a random String persisted to a database or a JWT token)
        // The issued token must be associated to a user
        // Return the issued token
    }
}

資格情報の検証時に例外がスローされた場合、ステータス403(禁止)の応答が返されます。

資格情報が正常に検証されると、ステータス200(OK)の応答が返され、発行されたトークンが応答ペイロードでクライアントに送信されます。クライアントは、すべての要求でトークンをサーバーに送信する必要があります。

application/x-www-form-urlencodedを使用する場合、クライアントは要求ペイロードで次の形式の資格情報を送信する必要があります。

username=admin&password=123456

フォームパラメーターの代わりに、ユーザー名とパスワードをクラスにラップすることができます。

public class Credentials implements Serializable {

    private String username;
    private String password;

    // Getters and setters omitted
}

そして、それをJSONとして消費します:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response authenticateUser(Credentials credentials) {

    String username = credentials.getUsername();
    String password = credentials.getPassword();

    // Authenticate the user, issue a token and return a response
}

このアプローチを使用して、クライアントは、リクエストのペイロードで次の形式で資格証明を送信する必要があります。

{
  "username": "admin",
  "password": "123456"
}

リクエストからトークンを抽出して検証する

クライアントは、リクエストの標準HTTP Authorizationヘッダーでトークンを送信する必要があります。例えば:

Authorization: Bearer <token-goes-here>

標準のHTTPヘッダーの名前は、authorizationではなく、authentication情報を運ぶため、残念です。ただし、資格情報をサーバーに送信するための標準HTTPヘッダーです。

JAX-RSは @NameBinding を提供します。これは、フィルターとインターセプターをリソースクラスとメソッドにバインドする他の注釈を作成するために使用されるメタ注釈です。次のように@Secured注釈を定義します。

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured { }

上記で定義された名前バインディングアノテーションは、 ContainerRequestFilter を実装するフィルタークラスを装飾するために使用され、リソースメソッドによって処理される前にリクエストをインターセプトできます。 ContainerRequestContext を使用して、HTTP要求ヘッダーにアクセスし、トークンを抽出できます。

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    private static final String REALM = "example";
    private static final String AUTHENTICATION_SCHEME = "Bearer";

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        // Get the Authorization header from the request
        String authorizationHeader =
                requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

        // Validate the Authorization header
        if (!isTokenBasedAuthentication(authorizationHeader)) {
            abortWithUnauthorized(requestContext);
            return;
        }

        // Extract the token from the Authorization header
        String token = authorizationHeader
                            .substring(AUTHENTICATION_SCHEME.length()).trim();

        try {

            // Validate the token
            validateToken(token);

        } catch (Exception e) {
            abortWithUnauthorized(requestContext);
        }
    }

    private boolean isTokenBasedAuthentication(String authorizationHeader) {

        // Check if the Authorization header is valid
        // It must not be null and must be prefixed with "Bearer" plus a whitespace
        // The authentication scheme comparison must be case-insensitive
        return authorizationHeader != null && authorizationHeader.toLowerCase()
                    .startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
    }

    private void abortWithUnauthorized(ContainerRequestContext requestContext) {

        // Abort the filter chain with a 401 status code response
        // The WWW-Authenticate header is sent along with the response
        requestContext.abortWith(
                Response.status(Response.Status.UNAUTHORIZED)
                        .header(HttpHeaders.WWW_AUTHENTICATE, 
                                AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"")
                        .build());
    }

    private void validateToken(String token) throws Exception {
        // Check if the token was issued by the server and if it's not expired
        // Throw an Exception if the token is invalid
    }
}

トークンの検証中に問題が発生した場合、ステータス401(未承認)の応答が返されます。それ以外の場合、要求はリソースメソッドに進みます。

RESTエンドポイントの保護

認証フィルターをリソースメソッドまたはリソースクラスにバインドするには、上記で作成した@Securedアノテーションでアノテーションを付けます。注釈が付けられているメソッドまたはクラス、あるいはその両方に対して、フィルターが実行されます。これは、有効なトークンを使用して要求が実行された場合に、そのようなエンドポイントにonlyだけが到達することを意味します。

一部のメソッドまたはクラスが認証を必要としない場合、それらに注釈を付けないでください:

@Path("/example")
public class ExampleResource {

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response myUnsecuredMethod(@PathParam("id") Long id) {
        // This method is not annotated with @Secured
        // The authentication filter won't be executed before invoking this method
        ...
    }

    @DELETE
    @Secured
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response mySecuredMethod(@PathParam("id") Long id) {
        // This method is annotated with @Secured
        // The authentication filter will be executed before invoking this method
        // The HTTP request must be performed with a valid token
        ...
    }
}

上記の例では、@Securedアノテーションが付けられているため、mySecuredMethod(Long)メソッドに対してフィルターがonly実行されます。

現在のユーザーを識別する

リクエストを実行しているユーザーをREST AP​​Iで再度知る必要がある可能性が非常に高くなります。以下のアプローチを使用して、それを達成できます。

現在のリクエストのセキュリティコンテキストをオーバーライドする

ContainerRequestFilter.filter(ContainerRequestContext) メソッド内で、現在のリクエストに新しい SecurityContext インスタンスを設定できます。次に SecurityContext.getUserPrincipal() をオーバーライドし、 Principal インスタンスを返します:

final SecurityContext currentSecurityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {

        @Override
        public Principal getUserPrincipal() {
            return () -> username;
        }

    @Override
    public boolean isUserInRole(String role) {
        return true;
    }

    @Override
    public boolean isSecure() {
        return currentSecurityContext.isSecure();
    }

    @Override
    public String getAuthenticationScheme() {
        return AUTHENTICATION_SCHEME;
    }
});

トークンを使用して、ユーザー識別子(ユーザー名)を検索します。これは、 Principal の名前になります。

JAX-RSリソースクラスに SecurityContext を挿入します。

@Context
SecurityContext securityContext;

JAX-RSリソースメソッドでも同じことができます。

@GET
@Secured
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myMethod(@PathParam("id") Long id, 
                         @Context SecurityContext securityContext) {
    ...
}

そして、 Principal を取得します:

Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();

CDI(Context and Dependency Injection)の使用

何らかの理由で SecurityContext をオーバーライドしたくない場合は、イベントやプロデューサーなどの便利な機能を提供するCDI(Context and Dependency Injection)を使用できます。

CDI修飾子を作成します。

@Qualifier
@Retention(RUNTIME)
@Target({ METHOD, FIELD, PARAMETER })
public @interface AuthenticatedUser { }

上記で作成したAuthenticationFilterに、 Event@AuthenticatedUserアノテーションを付けて挿入します。

@Inject
@AuthenticatedUser
Event<String> userAuthenticatedEvent;

認証が成功した場合、ユーザー名をパラメーターとして渡すイベントを発生させます(トークンはユーザーに対して発行され、トークンはユーザー識別子の検索に使用されることに注意してください)。

userAuthenticatedEvent.fire(username);

アプリケーション内のユーザーを表すクラスが存在する可能性が非常に高くなります。このクラスをUserと呼びましょう。

認証イベントを処理するCDI Beanを作成し、対応するユーザー名を持つUserインスタンスを見つけて、authenticatedUser producerフィールドに割り当てます。

@RequestScoped
public class AuthenticatedUserProducer {

    @Produces
    @RequestScoped
    @AuthenticatedUser
    private User authenticatedUser;

    public void handleAuthenticationEvent(@Observes @AuthenticatedUser String username) {
        this.authenticatedUser = findUser(username);
    }

    private User findUser(String username) {
        // Hit the the database or a service to find a user by its username and return it
        // Return the User instance
    }
}

authenticatedUserフィールドは、JAX-RSサービス、CDI Bean、サーブレット、EJBなどのコンテナー管理Beanに注入できるUserインスタンスを生成します。次のコードを使用して、Userインスタンスを挿入します(実際、CDIプロキシです)。

@Inject
@AuthenticatedUser
User authenticatedUser;

CDI @Produces 注釈は、JAX-RS @Produces 注釈とはdifferentであることに注意してください。

AuthenticatedUserProducer BeanでCDI @Produces アノテーションを必ず使用してください。

ここで重要なのは、 @RequestScoped アノテーションが付けられたBeanです。これにより、フィルターとBeanの間でデータを共有できます。イベントを使用したくない場合は、フィルターを変更して、認証されたユーザーを要求スコープBeanに格納し、JAX-RSリソースクラスから読み取ることができます。

SecurityContext をオーバーライドするアプローチと比較して、CDIアプローチでは、JAX-RSリソースおよびプロバイダー以外のBeanから認証済みユーザーを取得できます。

役割ベースの承認をサポートする

役割ベースの承認をサポートする方法の詳細については、他の answer を参照してください。

トークンの発行

トークンには次のものがあります。

  • Opaque:値自体以外の詳細は明らかにしません(ランダムな文字列など)
  • 自己完結型:トークン自体に関する詳細が含まれます(JWTなど)。

以下の詳細を参照してください。

トークンとしてのランダムな文字列

トークンは、ランダムな文字列を生成し、ユーザー識別子と有効期限とともにデータベースに保持することで発行できます。 Javaでランダムな文字列を生成する方法の良い例は、 here にあります。また、使用することができます:

Random random = new SecureRandom();
String token = new BigInteger(130, random).toString(32);

JWT(JSON Webトークン)

JWT(JSON Web Token)は、2者間でクレームを安全に表すための標準的な方法であり、 RFC 7519 で定義されています。

これは自己完結型のトークンであり、claimsに詳細を保存できます。これらのクレームは、 Base64 としてエンコードされたJSONであるトークンペイロードに保存されます。 RFC 7519 に登録されているいくつかのクレームとその意味(詳細については完全なRFCを読んでください):

  • iss :トークンを発行したプリンシパル。
  • sub :JWTのサブジェクトであるプリンシパル。
  • exp :トークンの有効期限。
  • nbf :トークンが処理のために受け入れられ始める時間。
  • iat :トークンが発行された時刻。
  • jti :トークンの一意の識別子。

パスワードなどの機密データをトークンに保存しないでください。

クライアントはペイロードを読み取ることができ、サーバー上の署名を検証することでトークンの整合性を簡単に確認できます。署名は、トークンが改ざんされるのを防ぎます。

追跡する必要がない場合は、JWTトークンを永続化する必要はありません。ただし、トークンを永続化することにより、トークンのアクセスを無効化および取り消すことができます。サーバー上でトークン全体を永続化する代わりに、JWTトークンを追跡するために、トークン識別子( jti クレーム)を、トークンを発行したユーザーなどの他の詳細とともに永続化できます。有効期限など.

トークンを永続化するときは、データベースが無期限に成長するのを防ぐために、常に古いトークンを削除することを検討してください。

JWTを使用する

次のようなJWTトークンを発行および検証するいくつかのJavaライブラリがあります。

JWTで動作する他の優れたリソースを見つけるには、 http://jwt.io をご覧ください。

JWTを使用したトークンの更新の処理

リフレッシュのためにonly有効な(および期限切れでない)トークンを受け入れます。 exp クレームで示される有効期限の前にトークンを更新するのは、クライアントの責任です。

トークンが無期限に更新されるのを防ぐ必要があります。以下で検討できるいくつかのアプローチを参照してください。

トークンに2つのクレームを追加することで、トークンの更新を追跡できます(クレーム名はあなた次第です)。

  • refreshLimit:トークンを更新できる回数を示します。
  • refreshCount:トークンが更新された回数を示します。

したがって、次の条件に該当する場合にのみトークンを更新してください。

  • トークンは期限切れではありません(exp >= now)。
  • トークンが更新された回数は、トークンを更新できる回数(refreshCount < refreshLimit)よりも少ないです。

そして、トークンを更新するとき:

  • 有効期限(exp = now + some-amount-of-time)を更新します。
  • トークンが更新された回数を増やします(refreshCount++)。

リフレッシュメントの数を追跡する代わりに、絶対有効期限を示すクレームを作成することもできます(これは上記のrefreshLimitクレームとほぼ同様に機能します)。 絶対有効期限の前に、任意の数のリフレッシュメントが受け入れられます。

別のアプローチでは、短命のJWTトークンを発行するために使用される別個の長命の更新トークンを発行します。

最適なアプローチは要件によって異なります。

JWTを使用したトークン取り消しの処理

トークンを取り消す場合は、それらを追跡する必要があります。トークン全体をサーバー側に保存する必要はなく、必要に応じてトークン識別子(一意である必要があります)とメタデータのみを保存する必要があります。トークン識別子には、 UUID を使用できます。

jti クレームを使用して、トークン識別子をトークンに保存する必要があります。トークンを検証するときは、サーバー側にあるトークン識別子に対して jti クレームの値をチェックして、トークンが取り消されていないことを確認してください。

セキュリティ上の理由から、ユーザーがパスワードを変更した場合、ユーザーのすべてのトークンを取り消します。

追加情報

  • どのタイプの認証を使用するかは問題ではありません。 AlwaysHTTPS接続の上部で実行して、 中間者攻撃 を防ぎます。
  • トークンの詳細については、情報セキュリティの この質問 をご覧ください。
  • この記事では トークンベースの認証に関する有用な情報があります。
1307
cassiomolin

この答えはすべてauthorizationについてのものであり、 私の前の答え about authenticationを補足するものです。

なぜanother答えるのですか?私は、JSR-250アノテーションをサポートする方法についての詳細を追加することによって、以前の答えを拡張しようとしました。しかし元の答えは長すぎるとなり、 最大長30,000文字を超えました 。そのため、認証の詳細とトークンの発行に焦点を当てながら、認証の詳細全体をこの回答に移動しました。


@Securedアノテーションによるロールベースの承認のサポート

他の answer に示されている認証フローの他に、役割ベースの承認はRESTエンドポイントでサポートできます。

列挙を作成し、ニーズに応じて役割を定義します。

public enum Role {
    ROLE_1,
    ROLE_2,
    ROLE_3
}

ロールをサポートするために、以前に作成した@Securedネームバインディングアノテーションを変更します。

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured {
    Role[] value() default {};
}

次に、リソースクラスとメソッドに@Securedというアノテーションを付けて承認を行います。メソッド注釈はクラス注釈をオーバーライドします。

@Path("/example")
@Secured({Role.ROLE_1})
public class ExampleResource {

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response myMethod(@PathParam("id") Long id) {
        // This method is not annotated with @Secured
        // But it's declared within a class annotated with @Secured({Role.ROLE_1})
        // So it only can be executed by the users who have the ROLE_1 role
        ...
    }

    @DELETE
    @Path("{id}")    
    @Produces(MediaType.APPLICATION_JSON)
    @Secured({Role.ROLE_1, Role.ROLE_2})
    public Response myOtherMethod(@PathParam("id") Long id) {
        // This method is annotated with @Secured({Role.ROLE_1, Role.ROLE_2})
        // The method annotation overrides the class annotation
        // So it only can be executed by the users who have the ROLE_1 or ROLE_2 roles
        ...
    }
}

AUTHORIZATION priorityを指定してフィルターを作成します。これは、前に定義した AUTHENTICATION priorityフィルターの後に実行されます。

ResourceInfo を使用して、リクエストを処理し、それらから@Securedアノテーションを抽出するリソース Method およびリソース Class を取得できます。

@Secured
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        // Get the resource class which matches with the requested URL
        // Extract the roles declared by it
        Class<?> resourceClass = resourceInfo.getResourceClass();
        List<Role> classRoles = extractRoles(resourceClass);

        // Get the resource method which matches with the requested URL
        // Extract the roles declared by it
        Method resourceMethod = resourceInfo.getResourceMethod();
        List<Role> methodRoles = extractRoles(resourceMethod);

        try {

            // Check if the user is allowed to execute the method
            // The method annotations override the class annotations
            if (methodRoles.isEmpty()) {
                checkPermissions(classRoles);
            } else {
                checkPermissions(methodRoles);
            }

        } catch (Exception e) {
            requestContext.abortWith(
                Response.status(Response.Status.FORBIDDEN).build());
        }
    }

    // Extract the roles from the annotated element
    private List<Role> extractRoles(AnnotatedElement annotatedElement) {
        if (annotatedElement == null) {
            return new ArrayList<Role>();
        } else {
            Secured secured = annotatedElement.getAnnotation(Secured.class);
            if (secured == null) {
                return new ArrayList<Role>();
            } else {
                Role[] allowedRoles = secured.value();
                return Arrays.asList(allowedRoles);
            }
        }
    }

    private void checkPermissions(List<Role> allowedRoles) throws Exception {
        // Check if the user contains one of the allowed roles
        // Throw an Exception if the user has not permission to execute the method
    }
}

ユーザーが操作を実行する権限を持っていない場合、要求は403(中止)で中止されます。

要求を実行しているユーザーを知るには、 私の以前の回答 を参照してください。あなたはそれを SecurityContext (すでに ContainerRequestContext に設定されているはずです)から入手するか、CDIを使って注入することができます。

@Securedアノテーションにロールが宣言されていない場合は、認証されたすべてのユーザーがそのエンドポイントにアクセスできると想定できます。ただし、ユーザーのロールは無視されます。

JSR-250アノテーションを使用した役割ベースの許可のサポート

上記のように@Securedアノテーションでロールを定義する代わりに、 @RolesAllowed@PermitAll@DenyAll などのJSR-250アノテーションを検討することもできます。

JAX-RSは、そのようなアノテーションをそのままではサポートしませんが、フィルタを使用して実現することができます。これらすべてをサポートしたい場合に留意する必要があるいくつかの考慮事項があります。

そのため、JSR-250アノテーションをチェックする認証フィルターは以下のようになります。

@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        Method method = resourceInfo.getResourceMethod();

        // @DenyAll on the method takes precedence over @RolesAllowed and @PermitAll
        if (method.isAnnotationPresent(DenyAll.class)) {
            refuseRequest();
        }

        // @RolesAllowed on the method takes precedence over @PermitAll
        RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
        if (rolesAllowed != null) {
            performAuthorization(rolesAllowed.value(), requestContext);
            return;
        }

        // @PermitAll on the method takes precedence over @RolesAllowed on the class
        if (method.isAnnotationPresent(PermitAll.class)) {
            // Do nothing
            return;
        }

        // @DenyAll can't be attached to classes

        // @RolesAllowed on the class takes precedence over @PermitAll on the class
        rolesAllowed = 
            resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
        if (rolesAllowed != null) {
            performAuthorization(rolesAllowed.value(), requestContext);
        }

        // @PermitAll on the class
        if (resourceInfo.getResourceClass().isAnnotationPresent(PermitAll.class)) {
            // Do nothing
            return;
        }

        // Authentication is required for non-annotated methods
        if (!isAuthenticated(requestContext)) {
            refuseRequest();
        }
    }

    /**
     * Perform authorization based on roles.
     *
     * @param rolesAllowed
     * @param requestContext
     */
    private void performAuthorization(String[] rolesAllowed, 
                                      ContainerRequestContext requestContext) {

        if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
            refuseRequest();
        }

        for (final String role : rolesAllowed) {
            if (requestContext.getSecurityContext().isUserInRole(role)) {
                return;
            }
        }

        refuseRequest();
    }

    /**
     * Check if the user is authenticated.
     *
     * @param requestContext
     * @return
     */
    private boolean isAuthenticated(final ContainerRequestContext requestContext) {
        // Return true if the user is authenticated or false otherwise
        // An implementation could be like:
        // return requestContext.getSecurityContext().getUserPrincipal() != null;
    }

    /**
     * Refuse the request.
     */
    private void refuseRequest() {
        throw new AccessDeniedException(
            "You don't have permissions to perform this action.");
    }
}

注:上記の実装はJersey RolesAllowedDynamicFeature に基づいています。 Jerseyを使用している場合は、独自のフィルタを作成する必要はありません。既存の実装を使用するだけです。

74
cassiomolin