Méthode recommandée pour l’authentification par jeton REST avec JAX-RS et Jersey

Je cherche un moyen d’activer l’authentification basée sur les jetons à Jersey. J’essaie de ne pas utiliser de cadre particulier. Est-ce possible?

Mon plan est le suivant: un utilisateur s’inscrit à mon service Web, mon service Web génère un jeton, l’envoie au client et le client le conserve. Ensuite, le client, pour chaque demande, enverra le jeton au lieu du nom d’utilisateur et du mot de passe.

Je pensais utiliser un filtre personnalisé pour chaque requête et @PreAuthorize("hasRole('ROLE')") mais je pensais simplement que cela provoquait beaucoup de demandes à la firebase database pour vérifier si le jeton était valide.

Ou ne pas créer de filtre et dans chaque requête mettre un jeton de paramètre? Pour que chaque API vérifie d’abord le jeton et exécute après quelque chose pour récupérer la ressource.

Fonctionnement de l’authentification basée sur les jetons

Dans l’authentification basée sur les jetons, le client échange des informations d’identification matérielles (telles que le nom d’utilisateur et le mot de passe) pour une donnée appelée jeton . Pour chaque demande, au lieu d’envoyer les informations d’identification matérielles, le client enverra le jeton au serveur pour effectuer l’authentification, puis l’autorisation.

En quelques mots, un schéma d’authentification basé sur les jetons suit ces étapes:

  1. Le client envoie ses informations d’identification (nom d’utilisateur et mot de passe) au serveur.
  2. Le serveur authentifie les informations d’identification et, si elles sont valides, génère un jeton pour l’utilisateur.
  3. Le serveur stocke le jeton généré précédemment dans un stockage avec l’identifiant de l’utilisateur et une date d’expiration.
  4. Le serveur envoie le jeton généré au client.
  5. Le client envoie le jeton au serveur dans chaque requête.
  6. Le serveur, dans chaque requête, extrait le jeton de la demande entrante. Avec le jeton, le serveur recherche les détails de l’utilisateur pour effectuer l’authentification.
    • Si le jeton est valide, le serveur accepte la demande.
    • Si le jeton est invalide, le serveur refuse la demande.
  7. Une fois l’authentification effectuée, le serveur effectue une autorisation.
  8. Le serveur peut fournir un sharepoint terminaison pour actualiser les jetons.

Remarque: L’étape 3 n’est pas requirejse si le serveur a émis un jeton signé (tel que JWT, qui vous permet d’effectuer une authentification sans état ).

Que pouvez-vous faire avec JAX-RS 2.0 (Jersey, RESTEasy et Apache CXF)

Cette solution utilise uniquement l’API JAX-RS 2.0, en évitant toute solution spécifique au fournisseur . Il devrait donc fonctionner avec les implémentations JAX-RS 2.0, telles que Jersey , RESTEasy et Apache CXF .

Il est utile de mentionner que si vous utilisez une authentification basée sur des jetons, vous ne vous fiez pas aux mécanismes de sécurité des applications Web Java EE standard proposés par le conteneur de servlets et configurables via le descripteur web.xml l’application. C’est une authentification personnalisée.

Authentifier un utilisateur avec son nom d’utilisateur et son mot de passe et émettre un jeton

Créez une méthode de ressource JAX-RS qui reçoit et valide les informations d’identification (nom d’utilisateur et mot de passe) et émet un jeton pour l’utilisateur:

 @Path("/authentication") public class AuthenticationEndpoint { @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response authenticateUser(@FormParam("username") Ssortingng username, @FormParam("password") Ssortingng password) { try { // Authenticate the user using the credentials provided authenticate(username, password); // Issue a token for the user Ssortingng 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(Ssortingng username, Ssortingng password) throws Exception { // Authenticate against a database, LDAP, file or whatever // Throw an Exception if the credentials are invalid } private Ssortingng issueToken(Ssortingng username) { // Issue a token (can be a random Ssortingng persisted to a database or a JWT token) // The issued token must be associated to a user // Return the issued token } } 

Si des exceptions sont levées lors de la validation des informations d’identification, une réponse avec le statut 403 (Interdit) sera renvoyée.

Si les informations d’identification sont validées avec succès, une réponse avec le statut 200 (OK) sera renvoyée et le jeton émis sera envoyé au client dans la charge utile de réponse. Le client doit envoyer le jeton au serveur dans chaque requête.

Lorsque vous utilisez application/x-www-form-urlencoded , le client doit envoyer les informations d’identification au format suivant dans la charge utile de la requête:

 username=admin&password=123456 

Au lieu des parameters de formulaire, il est possible d’emballer le nom d’utilisateur et le mot de passe dans une classe:

 public class Credentials implements Serializable { private Ssortingng username; private Ssortingng password; // Getters and setters omitted } 

Et puis le consumr comme JSON:

 @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response authenticateUser(Credentials credentials) { Ssortingng username = credentials.getUsername(); Ssortingng password = credentials.getPassword(); // Authenticate the user, issue a token and return a response } 

En utilisant cette approche, le client doit envoyer les informations d’identification au format suivant dans la charge utile de la requête:

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

Extraire le jeton de la requête et le valider

Le client doit envoyer le jeton dans l’en-tête HTTP Authorization standard de la demande. Par exemple:

 Authorization: Bearer  

Le nom de l’en-tête HTTP standard est regrettable car il contient des informations d’ authentification , pas d’ autorisation . Cependant, c’est l’en-tête HTTP standard pour envoyer les informations d’identification au serveur.

JAX-RS fournit @NameBinding , une méta-annotation utilisée pour créer d’autres annotations permettant de lier des filtres et des intercepteurs aux classes de ressources et aux méthodes. Définissez une annotation @Secured comme suit:

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

L’annotation de liaison de nom définie ci-dessus sera utilisée pour décorer une classe de filtre, qui implémente ContainerRequestFilter , vous permettant d’intercepter la requête avant qu’elle ne soit traitée par une méthode de ressource. ContainerRequestContext peut être utilisé pour accéder aux en-têtes de requête HTTP, puis extraire le jeton:

 @Secured @Provider @Priority(Priorities.AUTHENTICATION) public class AuthenticationFilter implements ContainerRequestFilter { private static final Ssortingng REALM = "example"; private static final Ssortingng AUTHENTICATION_SCHEME = "Bearer"; @Override public void filter(ContainerRequestContext requestContext) throws IOException { // Get the Authorization header from the request Ssortingng authorizationHeader = requestContext.getHeaderSsortingng(HttpHeaders.AUTHORIZATION); // Validate the Authorization header if (!isTokenBasedAuthentication(authorizationHeader)) { abortWithUnauthorized(requestContext); return; } // Extract the token from the Authorization header Ssortingng token = authorizationHeader .subssortingng(AUTHENTICATION_SCHEME.length()).sortingm(); try { // Validate the token validateToken(token); } catch (Exception e) { abortWithUnauthorized(requestContext); } } private boolean isTokenBasedAuthentication(Ssortingng 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(Ssortingng 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 } } 

Si des problèmes surviennent pendant la validation du jeton, une réponse avec le statut 401 (non autorisé) sera renvoyée. Sinon, la demande passera à une méthode de ressource.

Sécuriser vos points de terminaison REST

Pour lier le filtre d’authentification aux méthodes de ressources ou aux classes de ressources, @Secured les avec l’annotation @Secured créée ci-dessus. Pour les méthodes et / ou les classes annotées, le filtre sera exécuté. Cela signifie que ces points de terminaison ne seront atteints que si la demande est effectuée avec un jeton valide.

Si certaines méthodes ou classes n’ont pas besoin d’authentification, ne les annotez pas simplement:

 @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 ... } } 

Dans l’exemple ci-dessus, le filtre sera exécuté uniquement pour la mySecuredMethod(Long) car elle est annotée avec @Secured .

Identifier l’utilisateur actuel

Il est très probable que vous deviez connaître l’utilisateur qui exécute la requête auprès de votre API REST. Les approches suivantes peuvent être utilisées pour y parvenir:

Remplacement du contexte de sécurité de la demande en cours

Dans votre méthode ContainerRequestFilter.filter(ContainerRequestContext) , une nouvelle instance SecurityContext peut être définie pour la requête en cours. Remplacez ensuite SecurityContext.getUserPrincipal() en renvoyant une instance Principal :

 final SecurityContext currentSecurityContext = requestContext.getSecurityContext(); requestContext.setSecurityContext(new SecurityContext() { @Override public Principal getUserPrincipal() { return () -> username; } @Override public boolean isUserInRole(Ssortingng role) { return true; } @Override public boolean isSecure() { return currentSecurityContext.isSecure(); } @Override public Ssortingng getAuthenticationScheme() { return AUTHENTICATION_SCHEME; } }); 

Utilisez le jeton pour rechercher l’identifiant de l’utilisateur (nom d’utilisateur), qui sera le nom du Principal .

Injectez le SecurityContext dans n’importe quelle classe de ressources JAX-RS:

 @Context SecurityContext securityContext; 

La même chose peut être faite dans une méthode de ressource JAX-RS:

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

Et puis obtenez le Principal :

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

Utilisation de CDI (injection de contexte et de dépendance)

Si, pour une raison quelconque, vous ne souhaitez pas remplacer SecurityContext , vous pouvez utiliser CDI (Context and Dependency Injection), qui fournit des fonctionnalités utiles telles que les événements et les producteurs.

Créez un qualificateur CDI:

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

Dans votre AuthenticationFilter créé ci-dessus, injectez un Event annoté avec @AuthenticatedUser :

 @Inject @AuthenticatedUser Event userAuthenticatedEvent; 

Si l’authentification réussit, déclenchez l’événement en transmettant le nom d’utilisateur en paramètre (n’oubliez pas que le jeton est émis pour un utilisateur et que le jeton sera utilisé pour rechercher l’identificateur d’utilisateur):

 userAuthenticatedEvent.fire(username); 

Il est très probable qu’une classe représente un utilisateur dans votre application. Appelons cette classe User .

Créez un bean CDI pour gérer l’événement d’authentification, recherchez une instance d’ User avec le nom d’utilisateur correspondant et atsortingbuez-la au champ du producteur authenticatedUser :

 @RequestScoped public class AuthenticatedUserProducer { @Produces @RequestScoped @AuthenticatedUser private User authenticatedUser; public void handleAuthenticationEvent(@Observes @AuthenticatedUser Ssortingng username) { this.authenticatedUser = findUser(username); } private User findUser(Ssortingng username) { // Hit the the database or a service to find a user by its username and return it // Return the User instance } } 

Le champ authenticatedUser produit une instance d’ User pouvant être injectée dans des beans gérés par conteneur, tels que les services JAX-RS, les beans CDI, les servlets et les EJB. Utilisez le morceau de code suivant pour injecter une instance d’ User (en fait, c’est un proxy CDI):

 @Inject @AuthenticatedUser User authenticatedUser; 

Notez que l’annotation CDI @Produces est différente de l’annotation JAX-RS @Produces :

  • CDI: javax.enterprise.inject.Produces
  • JAX-RS: javax.ws.rs.Produces

Veillez à utiliser l’annotation CDI @Produces dans votre bean AuthenticatedUserProducer .

La clé ici est le bean annoté avec @RequestScoped , vous permettant de partager des données entre les filtres et vos beans. Si vous ne souhaitez pas utiliser d’événements, vous pouvez modifier le filtre pour stocker l’utilisateur authentifié dans un bean de requête, puis le lire dans vos classes de ressources JAX-RS.

Par rapport à l’approche qui remplace SecurityContext , l’approche CDI vous permet d’obtenir l’utilisateur authentifié à partir de beans autres que les ressources et les fournisseurs JAX-RS.

Prise en charge de l’autorisation basée sur les rôles

Veuillez vous reporter à mon autre réponse pour plus de détails sur la prise en charge de l’autorisation basée sur les rôles.

Emission de jetons

Un jeton peut être:

  • Opaque: ne révèle aucun détail autre que la valeur elle-même (comme une chaîne aléatoire)
  • Autonome: contient des détails sur le jeton lui-même (comme JWT).

Voir les détails ci-dessous:

Chaîne aléatoire comme jeton

Un jeton peut être émis en générant une chaîne aléatoire et en le conservant dans une firebase database avec l’identifiant de l’utilisateur et une date d’expiration. Un bon exemple de la façon de générer une chaîne aléatoire en Java peut être vu ici . Vous pouvez également utiliser:

 Random random = new SecureRandom(); Ssortingng token = new BigInteger(130, random).toSsortingng(32); 

JWT (jeton Web JSON)

JWT (JSON Web Token) est une méthode standard de représentation sécurisée des revendications entre deux parties et est définie par la RFC 7519 .

C’est un jeton autonome qui vous permet de stocker des détails dans des revendications . Ces revendications sont stockées dans la charge utile de jeton qui est un JSON codé en tant que Base64 . Voici quelques revendications enregistrées dans la RFC 7519 et leur signification (lisez la RFC complète pour plus de détails):

  • iss : principal qui a émis le jeton.
  • sub : principal qui fait l’object de la JWT.
  • exp : date d’expiration du jeton.
  • nbf : heure à laquelle le jeton commencera à être accepté pour traitement.
  • iat : heure à laquelle le jeton a été émis.
  • jti : identifiant unique du jeton.

Sachez que vous ne devez pas stocker de données sensibles, telles que des mots de passe, dans le jeton.

La charge utile peut être lue par le client et l’intégrité du jeton peut être facilement vérifiée en vérifiant sa signature sur le serveur. La signature est ce qui empêche la falsification du jeton.

Vous n’avez pas besoin de conserver les jetons JWT si vous n’avez pas besoin de les suivre. Bien qu’en persistant les jetons, vous aurez la possibilité d’invalider et de révoquer leur access. Pour conserver la trace des jetons JWT, au lieu de conserver l’intégralité du jeton sur le serveur, vous pouvez conserver l’identifiant du jeton ( jti claim) avec d’autres informations telles que l’utilisateur pour jti vous avez émis le jeton, la date d’expiration, etc.

Lors de la persistance de jetons, envisagez toujours de supprimer les anciens pour éviter que votre firebase database ne se développe indéfiniment.

Utiliser JWT

Il existe quelques bibliothèques Java pour émettre et valider des jetons JWT tels que:

  • jjwt
  • java-jwt
  • jose4j

Pour trouver d’autres ressources intéressantes pour travailler avec JWT, consultez http://jwt.io .

Gestion du rafraîchissement de jeton avec JWT

Acceptez uniquement les jetons valides (et non expirés) pour le rafraîchissement. Il incombe au client d’actualiser les jetons avant la date d’expiration indiquée dans la réclamation exp .

Vous devez éviter que les jetons ne soient actualisés indéfiniment. Voir ci-dessous quelques approches que vous pourriez envisager.

Vous pouvez garder la trace du rafraîchissement des jetons en ajoutant deux revendications à votre jeton (les noms des réclamations vous appartiennent):

  • refreshLimit : Indique combien de fois le jeton peut être actualisé.
  • refreshCount : indique combien de fois le jeton a été actualisé.

N’actualisez donc le jeton que si les conditions suivantes sont remplies:

  • Le jeton n’est pas expiré ( exp >= now ).
  • Le nombre de fois où le jeton a été actualisé est inférieur au nombre de fois où le jeton peut être actualisé ( refreshCount < refreshLimit ).

Et lors du rafraîchissement du jeton:

  • Mettez à jour la date d'expiration ( exp = now + some-amount-of-time ).
  • Incrémente le nombre de fois que le jeton a été actualisé ( refreshCount++ ).

Sinon, vous pouvez garder une trace du nombre de rafraîchissements, vous pourriez avoir une revendication qui indique la date d'expiration absolue (qui fonctionne assez similaire à la revendication refreshLimit décrite ci-dessus). Avant la date d'expiration absolue , tout nombre de rafraîchissements est acceptable.

Une autre approche consiste à émettre un jeton de rafraîchissement à long terme distinct qui est utilisé pour émettre des jetons JWT à durée de vie courte.

La meilleure approche dépend de vos besoins.

Gestion de la révocation de jeton avec JWT

Si vous souhaitez révoquer les jetons, vous devez en garder la trace. Vous n'avez pas besoin de stocker l'intégralité du jeton côté serveur, ne stockez que l'identificateur de jeton (qui doit être unique) et certaines métadonnées si vous en avez besoin. Pour l'identificateur de jeton, vous pouvez utiliser UUID .

La revendication jti doit être utilisée pour stocker l'identificateur de jeton sur le jeton. Lors de la validation du jeton, assurez-vous qu'il n'a pas été révoqué en vérifiant la valeur de la revendication jti rapport aux identificateurs de jeton que vous avez du côté serveur.

Pour des raisons de sécurité, révoquez tous les jetons d'un utilisateur lorsqu'il modifie son mot de passe.

Information additionnelle

  • Le type d'authentification que vous décidez d'utiliser n'a pas d'importance. Faites -le toujours en haut d'une connexion HTTPS pour empêcher l' attaque intermédiaire .
  • Jetez un oeil à cette question de la sécurité de l'information pour plus d'informations sur les jetons.
  • Dans cet article, vous trouverez des informations utiles sur l'authentification basée sur les jetons.

Cette réponse concerne l’ autorisation et est un complément à ma réponse précédente sur l’ authentification.

Pourquoi une autre réponse? J’ai tenté d’élargir ma réponse précédente en ajoutant des détails sur la manière de prendre en charge les annotations JSR-250. Cependant, la réponse originale est devenue trop longue et a dépassé la longueur maximale de 30 000 caractères . J’ai donc déplacé l’intégralité des détails de l’autorisation vers cette réponse, en gardant l’autre réponse axée sur l’authentification et l’émission de jetons.


@Secured l’autorisation basée sur les @Secured avec l’annotation @Secured

Outre le stream d’authentification indiqué dans l’autre réponse , l’autorisation basée sur les rôles peut être prise en charge dans les noeuds finaux REST.

Créez une énumération et définissez les rôles en fonction de vos besoins:

 public enum Role { ROLE_1, ROLE_2, ROLE_3 } 

Modifiez l’annotation de liaison de nom @Secured créée auparavant pour prendre en charge les rôles:

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

Et puis annoter les classes de ressources et les méthodes avec @Secured pour effectuer l’autorisation. Les annotations de méthode remplaceront les annotations de classe:

 @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 ... } } 

Créez un filtre avec la priorité AUTHORIZATION , qui est exécuté après le filtre de priorité AUTHENTICATION défini précédemment.

ResourceInfo peut être utilisé pour obtenir la ressource Method et la Class ressources qui gérera la demande, puis extraira les annotations @Secured de celles-ci:

 @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 classRoles = extractRoles(resourceClass); // Get the resource method which matches with the requested URL // Extract the roles declared by it Method resourceMethod = resourceInfo.getResourceMethod(); List 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 extractRoles(AnnotatedElement annotatedElement) { if (annotatedElement == null) { return new ArrayList(); } else { Secured secured = annotatedElement.getAnnotation(Secured.class); if (secured == null) { return new ArrayList(); } else { Role[] allowedRoles = secured.value(); return Arrays.asList(allowedRoles); } } } private void checkPermissions(List 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 } } 

Si l’utilisateur n’est pas autorisé à exécuter l’opération, la demande est abandonnée avec un 403 (Interdit).

Pour connaître l’utilisateur qui exécute la demande, consultez ma réponse précédente . Vous pouvez l’obtenir à partir de SecurityContext (qui doit déjà être défini dans ContainerRequestContext ) ou l’injecter à l’aide de CDI, en fonction de l’approche choisie.

Si @Secured annotation @Secured n’a de rôle déclaré, vous pouvez supposer que tous les utilisateurs authentifiés peuvent accéder à ce noeud final, sans tenir compte des rôles des utilisateurs.

Prise en charge de l’autorisation basée sur les rôles avec des annotations JSR-250

Vous pouvez également définir les rôles dans l’annotation @Secured comme indiqué ci-dessus. Vous pouvez envisager des annotations JSR-250 telles que @RolesAllowed , @PermitAll et @DenyAll .

JAX-RS ne supporte pas ces annotations prêtes à l’emploi, mais cela pourrait être réalisé avec un filtre. Voici quelques points à prendre en compte si vous souhaitez tous les prendre en charge:

  • @DenyAll sur la méthode est prioritaire sur @RolesAllowed et @PermitAll sur la classe.
  • @RolesAllowed sur la méthode est prioritaire sur @PermitAll dans la classe.
  • @PermitAll sur la méthode a priorité sur @RolesAllowed sur la classe.
  • @DenyAll ne peut pas être attaché aux classes.
  • @RolesAllowed sur la classe est prioritaire sur @PermitAll dans la classe.

Ainsi, un filtre d’autorisation qui vérifie les annotations JSR-250 pourrait être comme suit:

 @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(Ssortingng[] rolesAllowed, ContainerRequestContext requestContext) { if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) { refuseRequest(); } for (final Ssortingng 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."); } } 

Remarque: L’implémentation ci-dessus est basée sur la fonctionnalité RolesAllowedDynamicFeature Jersey. Si vous utilisez Jersey, vous n’avez pas besoin d’écrire votre propre filtre, utilisez simplement l’implémentation existante.