From 7297ba172f2d8b6e97704fe25089bde42f6173e4 Mon Sep 17 00:00:00 2001
From: Emmanuel Bruno <emmanuel.bruno@univ-tln.fr>
Date: Thu, 4 Mar 2021 18:08:17 +0100
Subject: [PATCH] adds authentication and samples.

---
 README.md                                     |   9 +-
 queries/sample-requests.rest                  |  91 ++++++++++++
 .../samples/jaxrs/model/BiblioModel.java      |  68 +++++++--
 .../jaxrs/resources/BiblioResource.java       |  40 +++--
 .../jaxrs/resources/PaginationInfo.java       |  10 +-
 .../jaxrs/security/InMemoryLoginModule.java   | 137 ++++++++++++++++++
 .../jaxrs/security/JsonWebTokenFilter.java    | 128 ----------------
 .../jaxrs/security/MySecurityContext.java     |  35 +++++
 .../bruno/samples/jaxrs/security/User.java    |  11 +-
 .../samples/jaxrs/security/UserDatabase.java  |  66 ---------
 .../security/{ => annotations}/BasicAuth.java |   2 +-
 .../security/{ => annotations}/JWTAuth.java   |   2 +-
 .../BasicAuthenticationFilter.java}           |  77 ++++------
 .../security/filter/JsonWebTokenFilter.java   | 110 ++++++++++++++
 .../univtln/bruno/samples/jaxrs/ServerIT.java |  78 ++++++++++
 .../samples/jaxrs/security/UserTest.java      |  27 ++++
 16 files changed, 612 insertions(+), 279 deletions(-)
 create mode 100644 queries/sample-requests.rest
 create mode 100644 src/main/java/fr/univtln/bruno/samples/jaxrs/security/InMemoryLoginModule.java
 delete mode 100644 src/main/java/fr/univtln/bruno/samples/jaxrs/security/JsonWebTokenFilter.java
 create mode 100644 src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java
 delete mode 100644 src/main/java/fr/univtln/bruno/samples/jaxrs/security/UserDatabase.java
 rename src/main/java/fr/univtln/bruno/samples/jaxrs/security/{ => annotations}/BasicAuth.java (85%)
 rename src/main/java/fr/univtln/bruno/samples/jaxrs/security/{ => annotations}/JWTAuth.java (85%)
 rename src/main/java/fr/univtln/bruno/samples/jaxrs/security/{AuthenticationFilter.java => filter/BasicAuthenticationFilter.java} (55%)
 create mode 100644 src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java
 create mode 100644 src/test/java/fr/univtln/bruno/samples/jaxrs/security/UserTest.java

diff --git a/README.md b/README.md
index 05e98b8..af934ff 100644
--- a/README.md
+++ b/README.md
@@ -40,12 +40,12 @@ curl -s -D - -H "Accept: application/json"  \
 
 Removes an author
 ```shell
-curl -s -D - -X DELETE "http://localhost:9998/myapp/biblio/authors/1"
+curl -s -D - -X DELETE "http://localhost:9998/myapp/biblio/auteurs/1"
 ```
 
 Removes all authors
 ```shell
-curl -s -D - -X DELETE "http://localhost:9998/myapp/biblio/authors"
+curl -s -D - -X DELETE "http://localhost:9998/myapp/biblio/auteurs"
 ```
 
 Adds an author
@@ -77,8 +77,13 @@ Filter resources with query parameters :
 curl -v -H "Accept: application/json"  \
  "http://127.0.0.1:9998/myapp/biblio/auteurs/filter?nom=Durand&prenom⁼Marie"
 ```
+
 Control sort key with header param (default value "nom") :
 ```shell
 curl -v -H "Accept: application/json"  -H "sortKey: prenom"\
 "http://127.0.0.1:9998/myapp/biblio/auteurs/filter"
+```
+Login and get a Java Web Token
+```shell
+TOKEN=$(curl -v --user "john.doe@nowhere.com:admin" "http://localhost:9998/myapp/biblio/login")
 ```
\ No newline at end of file
diff --git a/queries/sample-requests.rest b/queries/sample-requests.rest
new file mode 100644
index 0000000..ad3082f
--- /dev/null
+++ b/queries/sample-requests.rest
@@ -0,0 +1,91 @@
+# curl -D - http://localhost:9998/myapp/biblio
+### Get a Hello message
+GET http://localhost:9998/myapp/biblio
+
+### Init the database with two authors
+PUT http://localhost:9998/myapp/biblio/init
+
+### Get author 1 in JSON
+GET http://localhost:9998/myapp/biblio/auteurs/1
+Accept: application/json
+
+### Get author 2 in XML
+GET http://localhost:9998/myapp/biblio/auteurs/2
+Accept: text/xml
+
+### Get authors in JSON
+GET http://localhost:9998/myapp/biblio/auteurs
+Accept: application/json
+
+### Removes an author
+DELETE http://localhost:9998/myapp/biblio/auteurs/1
+
+### Removes all authors
+DELETE http://localhost:9998/myapp/biblio/auteurs
+
+### Adds an author
+POST http://localhost:9998/myapp/biblio/auteurs/
+Accept: application/json
+Content-type: application/json
+
+{"nom":"John","prenom":"Smith","biographie":"My life"}
+
+### Fully update an author
+PUT http://localhost:9998/myapp/biblio/auteurs/1
+Accept: application/json
+Content-type: application/json
+
+{"nom":"Martin","prenom":"Jean","biographie":"ma vie"}
+
+### If a resource doesn't exist an exception is raised, and the 404 http status code is returned
+GET http://localhost:9998/myapp/biblio/auteurs/1000
+Accept: application/json
+
+### Filter resources with query parameters :
+GET http://localhost:9998/myapp/biblio/auteurs/filter?nom=Durand&prenom⁼Marie
+Accept: application/json
+
+### Control sort key with header param (default value "nom") :
+GET http://127.0.0.1:9998/myapp/biblio/auteurs/filter
+Accept: application/json
+sortKey: prenom
+
+### Init the database with 10k random authors
+PUT http://localhost:9998/myapp/biblio/init/10000
+
+### Get page 3 with page size of 10 authors sorted by lastname
+GET http://localhost:9998/myapp/biblio/auteurs/page?pageSize=10&page=3
+Accept: application/json
+sortKey: prenom
+
+### Authorization by token, part 1. Retrieve and save token with Basic Authentication
+#  TOKEN=$(curl -v --user "john.doe@nowhere.com:admin" "http://localhost:9998/myapp/biblio/login")
+GET http://localhost:9998/myapp/biblio/login
+Authorization: Basic john.doe@nowhere.com admin
+
+> {% client.global.set("auth_token", response.body); %}
+
+### Authorization by token, part 2. Use token to authorize. Admin & User OK
+# curl -H "Authorization: Bearer $TOKEN" -v "http://localhost:9998/myapp/biblio/secured"
+GET http://localhost:9998/myapp/biblio/secured
+Authorization: Bearer {{auth_token}}
+
+### Authorization by token, part 2. Use token to authorize. Admin OK
+GET http://localhost:9998/myapp/biblio/secured/admin
+Authorization: Bearer {{auth_token}}
+
+### Authorization with another user.
+#  TOKEN=$(curl -v --user "mary.roberts@here.net:user" "http://localhost:9998/myapp/biblio/login")
+GET http://localhost:9998/myapp/biblio/login
+Authorization: Basic mary.roberts@here.net user
+
+> {% client.global.set("auth_token", response.body); %}
+
+### Authorization by token, part 2. Use token to authorize. Admin & User OK.
+# curl -H "Authorization: Bearer $TOKEN" -v "http://localhost:9998/myapp/biblio/secured"
+GET http://localhost:9998/myapp/biblio/secured
+Authorization: Bearer {{auth_token}}
+
+### Authorization by token, part 2. Use token to authorize. Admin KO.
+GET http://localhost:9998/myapp/biblio/secured/admin
+Authorization: Bearer {{auth_token}}
\ No newline at end of file
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/model/BiblioModel.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/model/BiblioModel.java
index a1386c3..5df3f2b 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/model/BiblioModel.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/model/BiblioModel.java
@@ -3,9 +3,6 @@ package fr.univtln.bruno.samples.jaxrs.model;
 import fr.univtln.bruno.samples.jaxrs.exceptions.IllegalArgumentException;
 import fr.univtln.bruno.samples.jaxrs.exceptions.NotFoundException;
 import fr.univtln.bruno.samples.jaxrs.resources.PaginationInfo;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.core.*;
 import jakarta.xml.bind.annotation.XmlAccessType;
 import jakarta.xml.bind.annotation.XmlAccessorType;
 import jakarta.xml.bind.annotation.XmlAttribute;
@@ -27,16 +24,29 @@ import java.util.stream.Stream;
 
 import static fr.univtln.bruno.samples.jaxrs.model.BiblioModel.Field.valueOf;
 
+
+/**
+ * The type Biblio model.
+ */
 @Log
 @Getter
 @FieldDefaults(level = AccessLevel.PRIVATE)
 @NoArgsConstructor(staticName = "of")
 public class BiblioModel {
-    private static AtomicLong lastId = new AtomicLong(0);
+    public enum Field {NOM, PRENOM, BIOGRAPHIE}
+
+    private static final AtomicLong lastId = new AtomicLong(0);
 
     @Delegate
     final MutableLongObjectMap<Auteur> auteurs = LongObjectMaps.mutable.empty();
 
+    /**
+     * Add auteur auteur.
+     *
+     * @param auteur the auteur
+     * @return the auteur
+     * @throws IllegalArgumentException the illegal argument exception
+     */
     public Auteur addAuteur(Auteur auteur) throws IllegalArgumentException {
         if (auteur.id != 0) throw new IllegalArgumentException();
         auteur.id = lastId.incrementAndGet();
@@ -44,6 +54,15 @@ public class BiblioModel {
         return auteur;
     }
 
+    /**
+     * Update auteur auteur.
+     *
+     * @param id     the id
+     * @param auteur the auteur
+     * @return the auteur
+     * @throws NotFoundException        the not found exception
+     * @throws IllegalArgumentException the illegal argument exception
+     */
     public Auteur updateAuteur(long id, Auteur auteur) throws NotFoundException, IllegalArgumentException {
         if (auteur.id != 0) throw new IllegalArgumentException();
         auteur.id = id;
@@ -52,33 +71,63 @@ public class BiblioModel {
         return auteur;
     }
 
+    /**
+     * Remove auteur.
+     *
+     * @param id the id
+     * @throws NotFoundException the not found exception
+     */
     public void removeAuteur(long id) throws NotFoundException {
         if (!auteurs.containsKey(id)) throw new NotFoundException();
         auteurs.remove(id);
     }
 
+    /**
+     * Gets auteur.
+     *
+     * @param id the id
+     * @return the auteur
+     * @throws NotFoundException the not found exception
+     */
     public Auteur getAuteur(long id) throws NotFoundException {
         if (!auteurs.containsKey(id)) throw new NotFoundException();
         return auteurs.get(id);
     }
 
+    /**
+     * Gets auteur size.
+     *
+     * @return the auteur size
+     */
     public int getAuteurSize() {
         return auteurs.size();
     }
 
+
+    /**
+     * Returns a sorted, filtered and paginated list of authors.
+     *
+     * @param paginationInfo the pagination info
+     * @return the sorted, filtered page.
+     */
     public List<Auteur> getWithFilter(PaginationInfo paginationInfo) {
+        //We build a author stream, first we add sorting
         Stream<Auteur> auteurStream = auteurs.stream()
                 .sorted(Comparator.comparing(auteur -> switch (valueOf(paginationInfo.getSortKey().toUpperCase())) {
                     case NOM -> auteur.getNom();
                     case PRENOM -> auteur.getPrenom();
                     default -> throw new InvalidParameterException();
                 }));
-        if (paginationInfo.getNom() != null)
+
+        //The add filters according to parameters
+        if (paginationInfo.getNom()!=null)
             auteurStream = auteurStream.filter(auteur -> auteur.getNom().equalsIgnoreCase(paginationInfo.getNom()));
-        if (paginationInfo.getPrenom() != null)
+        if (paginationInfo.getPrenom()!=null)
             auteurStream = auteurStream.filter(auteur -> auteur.getPrenom().equalsIgnoreCase(paginationInfo.getPrenom()));
-        if (paginationInfo.getBiographie() != null)
+        if (paginationInfo.getBiographie()!=null)
             auteurStream = auteurStream.filter(auteur -> auteur.getBiographie().contains(paginationInfo.getBiographie()));
+
+        //Finally add pagination instructions.
         if ((paginationInfo.getPage() > 0) && (paginationInfo.getPageSize() > 0)) {
             auteurStream = auteurStream
                     .skip(paginationInfo.getPageSize() * (paginationInfo.getPage() - 1))
@@ -93,8 +142,9 @@ public class BiblioModel {
         lastId.set(0);
     }
 
-    public enum Field {NOM, PRENOM, BIOGRAPHIE}
-
+    /**
+     * The type Auteur.
+     */
     @Builder
     @Getter
     @Setter
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/BiblioResource.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/BiblioResource.java
index 1359628..a1fef79 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/BiblioResource.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/BiblioResource.java
@@ -5,10 +5,10 @@ import fr.univtln.bruno.samples.jaxrs.exceptions.IllegalArgumentException;
 import fr.univtln.bruno.samples.jaxrs.exceptions.NotFoundException;
 import fr.univtln.bruno.samples.jaxrs.model.BiblioModel;
 import fr.univtln.bruno.samples.jaxrs.model.BiblioModel.Auteur;
-import fr.univtln.bruno.samples.jaxrs.security.BasicAuth;
-import fr.univtln.bruno.samples.jaxrs.security.JWTAuth;
+import fr.univtln.bruno.samples.jaxrs.security.annotations.BasicAuth;
+import fr.univtln.bruno.samples.jaxrs.security.annotations.JWTAuth;
 import fr.univtln.bruno.samples.jaxrs.security.User;
-import fr.univtln.bruno.samples.jaxrs.security.UserDatabase;
+import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule;
 import fr.univtln.bruno.samples.jaxrs.status.Status;
 import io.jsonwebtoken.Jwts;
 import jakarta.annotation.security.RolesAllowed;
@@ -18,12 +18,11 @@ import lombok.extern.java.Log;
 
 import javax.naming.AuthenticationException;
 import java.security.SecureRandom;
-import java.sql.Date;
-import java.text.ParseException;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
 @Log
@@ -86,7 +85,7 @@ public class BiblioResource {
      *
      * @param auteur The author to be added without its id.
      * @return The added author with its id.
-     * @throws IllegalArgumentException
+     * @throws IllegalArgumentException if the author has an explicit id (id!=0).
      */
     @POST
     @Status(Status.CREATED)
@@ -145,10 +144,9 @@ public class BiblioResource {
     @GET
     @Path("context")
     @RolesAllowed("ADMIN")
-    public String getContext(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders, @Context Request request, @Context SecurityContext securityContext) throws ParseException {
+    public String getContext(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders, @Context Request request, @Context SecurityContext securityContext) {
         return "UriInfo: (" + uriInfo.getRequestUri().toString()
                + ")\n HttpHeaders(" + httpHeaders.getRequestHeaders().toString()
-               //+")\n Request Precondition("+request.evaluatePreconditions(new SimpleDateFormat("dd/MM/yyyy-HH:mm:ss").parse("03/02/2021-10:30:00"))
                + ")\n SecurityContext(Auth.scheme: [" + securityContext.getAuthenticationScheme()
                + "] user: [" + securityContext.getUserPrincipal().getName()
                + "] secured: [" + securityContext.isSecure() + "] )";
@@ -158,31 +156,43 @@ public class BiblioResource {
     @Path("adminsonly")
     @RolesAllowed("ADMIN")
     @BasicAuth
-    public String getRestrictedToAdmins() {
-        return "secret for admins !";
+    public String getRestrictedToAdmins(@Context SecurityContext securityContext) {
+        return "secret for admins !" + securityContext.getUserPrincipal().getName();
     }
 
     @GET
     @Path("usersonly")
     @RolesAllowed("USER")
     @BasicAuth
-    public String getRestrictedToUsers() {
-        return "secret for users !";
+    public String getRestrictedToUsers(@Context SecurityContext securityContext) {
+        return "secret for users ! to " + securityContext.getUserPrincipal().getName();
     }
 
     @GET
     @Path("secured")
     @RolesAllowed({"USER", "ADMIN"})
     @JWTAuth
+    @Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
     public String securedByJWT(@Context SecurityContext securityContext) {
-        log.info("USER ACCESS :"+securityContext.getUserPrincipal().getName());
-        return "Access with JWT ok for "+securityContext.getUserPrincipal().getName();
+        log.info("USER ACCESS :" + securityContext.getUserPrincipal().getName());
+        return "Access with JWT ok for " + securityContext.getUserPrincipal().getName();
+    }
+
+    @GET
+    @Path("secured/admin")
+    @RolesAllowed({"ADMIN"})
+    @JWTAuth
+    @Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
+    public String securedByJWTAdminOnly(@Context SecurityContext securityContext) {
+        log.info("ADMIN ACCESS :" + securityContext.getUserPrincipal().getName());
+        return "Access with JWT ok for " + securityContext.getUserPrincipal().getName();
     }
 
     @GET
     @Path("login")
     @RolesAllowed({"USER", "ADMIN"})
     @BasicAuth
+    @Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
     public String login(@Context SecurityContext securityContext) {
         if (securityContext.isSecure() && securityContext.getUserPrincipal() instanceof User) {
             User user = (User) securityContext.getUserPrincipal();
@@ -194,7 +204,7 @@ public class BiblioResource {
                     .claim("lastname", user.getLastName())
                     .claim("roles", user.getRoles())
                     .setExpiration(Date.from(LocalDateTime.now().plus(15, ChronoUnit.MINUTES).atZone(ZoneId.systemDefault()).toInstant()))
-                    .signWith(UserDatabase.KEY).compact();
+                    .signWith(InMemoryLoginModule.KEY).compact();
         }
         throw new WebApplicationException(new AuthenticationException());
     }
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/PaginationInfo.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/PaginationInfo.java
index 6e4c2cd..40801ab 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/PaginationInfo.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/PaginationInfo.java
@@ -13,18 +13,20 @@ import lombok.experimental.FieldDefaults;
 @NoArgsConstructor
 @AllArgsConstructor
 public class PaginationInfo {
-    @HeaderParam("sortKey")
-    @DefaultValue("nom")
-    String sortKey;
-
+    @SuppressWarnings("FieldMayBeFinal")
     @QueryParam("page")
     @Builder.Default
     long page = 1;
 
+    @SuppressWarnings("FieldMayBeFinal")
     @QueryParam("pageSize")
     @Builder.Default
     long pageSize = 10;
 
+    @HeaderParam("sortKey")
+    @DefaultValue("nom")
+    String sortKey;
+
     @QueryParam("nom")
     String nom;
 
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/InMemoryLoginModule.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/InMemoryLoginModule.java
new file mode 100644
index 0000000..6ba281c
--- /dev/null
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/InMemoryLoginModule.java
@@ -0,0 +1,137 @@
+package fr.univtln.bruno.samples.jaxrs.security;
+
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
+import lombok.AccessLevel;
+import lombok.ToString;
+import lombok.experimental.FieldDefaults;
+import lombok.extern.java.Log;
+
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.*;
+
+/**
+ * The type User database.
+ */
+@Log
+@ToString
+@FieldDefaults(level = AccessLevel.PRIVATE)
+public class InMemoryLoginModule {
+    /**
+     * The constant USER_DATABASE mocks a user database in memory.
+     */
+    public static final InMemoryLoginModule USER_DATABASE = new InMemoryLoginModule();
+
+    /**
+     * The constant KEY is used as a signing key for the bearer JWT token.
+     * It is used to check that the token hasn't been modified.
+     */
+    public static final Key KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
+
+    //We add three demo users.
+    static {
+        try {
+            USER_DATABASE.addUser("John", "Doe", "john.doe@nowhere.com", "admin", EnumSet.of(Role.ADMIN));
+            USER_DATABASE.addUser("William", "Smith", "william.smith@here.net", "user", EnumSet.of(Role.USER));
+            USER_DATABASE.addUser("Mary", "Robert", "mary.roberts@here.net", "user", EnumSet.of(Role.USER));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            log.severe("In memory user database error "+e.getLocalizedMessage());
+        }
+    }
+
+    final Map<String, User> users = new HashMap<>();
+
+    public static boolean isInRoles(Set<Role> rolesSet, String username) {
+        return !(Collections.disjoint(rolesSet, InMemoryLoginModule.USER_DATABASE.getUserRoles(username)));
+    }
+
+    /**
+     * Add user.
+     *
+     * @param firstname the firstname
+     * @param lastname  the lastname
+     * @param email     the email
+     * @param password  the password
+     * @param roles     the roles
+     * @throws InvalidKeySpecException  the invalid key spec exception
+     * @throws NoSuchAlgorithmException the no such algorithm exception
+     */
+    public void addUser(String firstname, String lastname, String email, String password, Set<Role> roles)
+            throws InvalidKeySpecException, NoSuchAlgorithmException {
+        users.put(email, User.builder().firstName(firstname).lastName(lastname).email(email).password(password).roles(roles).build());
+    }
+
+    /**
+     * Gets users.
+     *
+     * @return the users
+     */
+    public Map<String, User> getUsers() {
+        return Collections.unmodifiableMap(users);
+    }
+
+    /**
+     * Remove user.
+     *
+     * @param email the email
+     */
+    public void removeUser(String email) {
+        users.remove(email);
+    }
+
+    /**
+     * Check password boolean.
+     *
+     * @param email    the email
+     * @param password the password
+     * @return the boolean
+     */
+    public boolean login(String email, String password) {
+        return users.get(email).checkPassword(password);
+    }
+
+    /**
+     * Gets user.
+     *
+     * @param email the email
+     * @return the user
+     */
+    public User getUser(String email) {
+        return users.get(email);
+    }
+
+    /**
+     * Gets user roles.
+     *
+     * @param email the email
+     * @return the user roles
+     */
+    public Set<Role> getUserRoles(String email) {
+        return users.get(email).getRoles();
+    }
+
+    @SuppressWarnings("SameReturnValue")
+    public boolean logout() {
+        return false;
+    }
+
+    /**
+     * The enum Role.
+     */
+    public enum Role {
+        /**
+         * Admin role.
+         */
+        ADMIN,
+        /**
+         * User role.
+         */
+        USER,
+        /**
+         * Guest role.
+         */
+        GUEST
+    }
+}
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JsonWebTokenFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JsonWebTokenFilter.java
deleted file mode 100644
index 1d19aeb..0000000
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JsonWebTokenFilter.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package fr.univtln.bruno.samples.jaxrs.security;
-
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.Jws;
-import io.jsonwebtoken.Jwts;
-import jakarta.annotation.Priority;
-import jakarta.annotation.security.DenyAll;
-import jakarta.annotation.security.PermitAll;
-import jakarta.annotation.security.RolesAllowed;
-import jakarta.ws.rs.Priorities;
-import jakarta.ws.rs.container.ContainerRequestContext;
-import jakarta.ws.rs.container.ContainerRequestFilter;
-import jakarta.ws.rs.container.ResourceInfo;
-import jakarta.ws.rs.core.Context;
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.SecurityContext;
-import jakarta.ws.rs.ext.Provider;
-import lombok.SneakyThrows;
-import lombok.extern.java.Log;
-
-import java.lang.reflect.Method;
-import java.security.Principal;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.stream.Collectors;
-
-@JWTAuth
-@Provider
-@Priority(Priorities.AUTHENTICATION)
-@Log
-/**
- * This class if a filter for JAX-RS to perform authentication via JWT.
- */
-public class JsonWebTokenFilter implements ContainerRequestFilter {
-    private static final String AUTHORIZATION_PROPERTY = "Authorization";
-    private static final String AUTHENTICATION_SCHEME = "Bearer";
-
-    //We inject the data from the acceded resource.
-    @Context
-    private ResourceInfo resourceInfo;
-
-    @SneakyThrows
-    @Override
-    public void filter(ContainerRequestContext requestContext) {
-        //We use reflection on the acceded method to look for security annotations.
-        Method method = resourceInfo.getResourceMethod();
-        //if its PermitAll access is granted
-        //otherwise if its DenyAll the access is refused
-        if (!method.isAnnotationPresent(PermitAll.class)) {
-            if (method.isAnnotationPresent(DenyAll.class)) {
-                requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
-                        .entity("Access denied to all users").build());
-                return;
-            }
-
-            //We get the authorization header
-            final String authorization = requestContext.getHeaderString(AUTHORIZATION_PROPERTY);
-
-
-            if (method.isAnnotationPresent(RolesAllowed.class)) {
-                RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
-                EnumSet<UserDatabase.Role> rolesSet =
-                        Arrays.stream(rolesAnnotation.value())
-                                .map(r -> UserDatabase.Role.valueOf(r))
-                                .collect(Collectors.toCollection(() -> EnumSet.noneOf(UserDatabase.Role.class)));
-
-
-                //We check the credentials presence
-                if (authorization == null || authorization.isEmpty()) {
-                    requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
-                            .entity("Please provide your credentials").build());
-                    return;
-                }
-
-                //Gets the token
-                log.info("AUTH: "+authorization);
-                final String compactJwt = authorization.substring(AUTHENTICATION_SCHEME.length()).trim();
-                if (!authorization.contains(AUTHENTICATION_SCHEME) || compactJwt == null || compactJwt.isEmpty()) {
-                    requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
-                            .entity("Please provide your credentials").build());
-                    return;
-                }
-                log.info("JWT: "+compactJwt);
-
-                Jws<Claims> jws = Jwts.parserBuilder()
-                        .setSigningKey(UserDatabase.KEY)
-                        .build()
-                        .parseClaimsJws(compactJwt);
-                log.info("JWT decoded: "+jws.toString());
-
-                final String username = jws.getBody().getSubject();
-
-                //We check if the role is allowed
-                if (Collections.disjoint(rolesSet, UserDatabase.USER_DATABASE.getUserRoles(username))) {
-                    requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
-                            .entity("Roles not allowed").build());
-                    return;
-                }
-
-                //We build a new securitycontext to transmit the security data to JAX-RS
-                requestContext.setSecurityContext(new SecurityContext() {
-
-                    @Override
-                    public Principal getUserPrincipal() {
-                        return UserDatabase.USER_DATABASE.getUser(username);
-                    }
-
-                    @Override
-                    public boolean isUserInRole(String role) {
-                        return UserDatabase.USER_DATABASE.getUserRoles(username).contains(UserDatabase.Role.valueOf(role));
-                    }
-
-                    @Override
-                    public boolean isSecure() {
-                        return true;
-                    }
-
-                    @Override
-                    public String getAuthenticationScheme() {
-                        return AUTHENTICATION_SCHEME;
-                    }
-                });
-
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java
new file mode 100644
index 0000000..300894c
--- /dev/null
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java
@@ -0,0 +1,35 @@
+package fr.univtln.bruno.samples.jaxrs.security;
+
+import jakarta.ws.rs.core.SecurityContext;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+import java.security.Principal;
+
+@FieldDefaults(level = AccessLevel.PRIVATE)
+@AllArgsConstructor(staticName = "newInstance")
+public class MySecurityContext implements SecurityContext {
+    private final String authenticationScheme;
+    private final String username;
+
+    @Override
+    public Principal getUserPrincipal() {
+        return InMemoryLoginModule.USER_DATABASE.getUser(username);
+    }
+
+    @Override
+    public boolean isUserInRole(String role) {
+        return InMemoryLoginModule.USER_DATABASE.getUserRoles(username).contains(InMemoryLoginModule.Role.valueOf(role));
+    }
+
+    @Override
+    public boolean isSecure() {
+        return true;
+    }
+
+    @Override
+    public String getAuthenticationScheme() {
+        return authenticationScheme;
+    }
+}
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java
index a18f358..6ea65b1 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java
@@ -23,17 +23,19 @@ import java.util.*;
 @EqualsAndHashCode(of = "email")
 public class User implements Principal {
     UUID uuid = UUID.randomUUID();
-    String firstName, lastName, email;
+    String firstName;
+    String lastName;
+    String email;
     byte[] passwordHash;
     byte[] salt = new byte[16];
 
     @Delegate
-    EnumSet<UserDatabase.Role> roles;
+    Set<InMemoryLoginModule.Role> roles;
 
     SecureRandom random = new SecureRandom();
 
     @Builder
-    public User(String firstName, String lastName, String email, String password, EnumSet<UserDatabase.Role> roles)
+    public User(String firstName, String lastName, String email, String password, Set<InMemoryLoginModule.Role> roles)
             throws NoSuchAlgorithmException, InvalidKeySpecException {
         this.firstName = firstName;
         this.lastName = lastName;
@@ -55,7 +57,8 @@ public class User implements Principal {
         return email + "" + Base64.getEncoder().encodeToString(passwordHash);
     }
 
-    public boolean checkPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
+    @SneakyThrows
+    public boolean checkPassword(String password) {
         KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
         SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
         byte[] submittedPasswordHash = factory.generateSecret(spec).getEncoded();
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/UserDatabase.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/UserDatabase.java
deleted file mode 100644
index 7e0cc79..0000000
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/UserDatabase.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package fr.univtln.bruno.samples.jaxrs.security;
-
-import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.security.Keys;
-import lombok.ToString;
-import lombok.extern.java.Log;
-
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.*;
-
-@Log
-@ToString
-public class UserDatabase {
-    public static final UserDatabase USER_DATABASE = new UserDatabase();
-
-    // We need a signing key for the id token, so we'll create one just for this example. Usually
-    // the key would be read from your application configuration instead.
-    public static final Key KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
-
-    static {
-        try {
-            USER_DATABASE.addUser("John", "Doe", "john.doe@nowhere.com", "admin", EnumSet.of(Role.ADMIN));
-            USER_DATABASE.addUser("William", "Smith", "william.smith@here.net", "user", EnumSet.of(Role.USER));
-            USER_DATABASE.addUser("Mary", "Robert", "mary.roberts@here.net", "user", EnumSet.of(Role.USER));
-        } catch (InvalidKeySpecException e) {
-            e.printStackTrace();
-        } catch (NoSuchAlgorithmException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private final Map<String, User> users = new HashMap<>();
-
-    public static void main(String[] args) {
-        USER_DATABASE.users.values().forEach(u->log.info(u.toString()));
-    }
-
-    public void addUser(String firstname, String lastname, String email, String password, EnumSet<Role> roles)
-            throws InvalidKeySpecException, NoSuchAlgorithmException {
-        users.put(email, User.builder().firstName(firstname).lastName(lastname).email(email).password(password).roles(roles).build());
-    }
-
-    public Map<String, User> getUsers() {
-        return Collections.unmodifiableMap(users);
-    }
-
-    public void removeUser(String email) {
-        users.remove(email);
-    }
-
-    public boolean checkPassword(String email, String password) throws InvalidKeySpecException, NoSuchAlgorithmException {
-        return users.get(email).checkPassword(password);
-    }
-
-    public User getUser(String email) {
-        return users.get(email);
-    }
-
-    public Set<Role> getUserRoles(String email) {
-        return users.get(email).getRoles();
-    }
-
-    public enum Role {ADMIN, USER, GUEST}
-}
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/BasicAuth.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java
similarity index 85%
rename from src/main/java/fr/univtln/bruno/samples/jaxrs/security/BasicAuth.java
rename to src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java
index 458387e..d61e29a 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/BasicAuth.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java
@@ -1,4 +1,4 @@
-package fr.univtln.bruno.samples.jaxrs.security;
+package fr.univtln.bruno.samples.jaxrs.security.annotations;
 
 import jakarta.ws.rs.NameBinding;
 
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java
similarity index 85%
rename from src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java
rename to src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java
index 00b7522..ed69c43 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java
@@ -1,4 +1,4 @@
-package fr.univtln.bruno.samples.jaxrs.security;
+package fr.univtln.bruno.samples.jaxrs.security.annotations;
 
 import jakarta.ws.rs.NameBinding;
 
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java
similarity index 55%
rename from src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java
rename to src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java
index 17d48f3..4cc921a 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java
@@ -1,5 +1,8 @@
-package fr.univtln.bruno.samples.jaxrs.security;
+package fr.univtln.bruno.samples.jaxrs.security.filter;
 
+import fr.univtln.bruno.samples.jaxrs.security.MySecurityContext;
+import fr.univtln.bruno.samples.jaxrs.security.annotations.BasicAuth;
+import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule;
 import jakarta.annotation.Priority;
 import jakarta.annotation.security.DenyAll;
 import jakarta.annotation.security.PermitAll;
@@ -10,24 +13,24 @@ import jakarta.ws.rs.container.ContainerRequestFilter;
 import jakarta.ws.rs.container.ResourceInfo;
 import jakarta.ws.rs.core.Context;
 import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.SecurityContext;
 import jakarta.ws.rs.ext.Provider;
 import lombok.SneakyThrows;
 import lombok.extern.java.Log;
 
 import java.lang.reflect.Method;
-import java.security.Principal;
 import java.util.*;
 import java.util.stream.Collectors;
 
+/**
+ * The type Authentication filter is a JAX-RS filter (@Provider with implements ContainerRequestFilter) is applied to every request whose method is annotated with @BasicAuth
+ * as it is itself annotated with @BasicAuth (a personal annotation).
+ * It performs authentication and check permissions against the acceded method with a basic authentication.
+ */
 @BasicAuth
 @Provider
 @Priority(Priorities.AUTHENTICATION)
 @Log
-/**
- * This class if a filter for JAX-RS to perform authentication and to check permissions against the acceded method.
- */
-public class AuthenticationFilter implements ContainerRequestFilter {
+public class BasicAuthenticationFilter implements ContainerRequestFilter {
     private static final String AUTHORIZATION_PROPERTY = "Authorization";
     private static final String AUTHENTICATION_SCHEME = "Basic";
 
@@ -40,8 +43,8 @@ public class AuthenticationFilter implements ContainerRequestFilter {
     public void filter(ContainerRequestContext requestContext) {
         //We use reflection on the acceded method to look for security annotations.
         Method method = resourceInfo.getResourceMethod();
-        //if its PermitAll access is granted
-        //otherwise if its DenyAll the access is refused
+        //if it is PermitAll access is granted
+        //otherwise if it is DenyAll the access is refused
         if (!method.isAnnotationPresent(PermitAll.class)) {
             if (method.isAnnotationPresent(DenyAll.class)) {
                 requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
@@ -59,64 +62,40 @@ public class AuthenticationFilter implements ContainerRequestFilter {
                 return;
             }
 
-            //Get encoded username and password
+            //We extract the username and password encoded in base64
             final String encodedUserPassword = authorization.substring(AUTHENTICATION_SCHEME.length()).trim();
 
-            //Decode username and password
-            String usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword.getBytes()));
+            //We Decode username and password (username:password)
+            String[] usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword.getBytes())).split(":");
 
-            //Split username and password tokens
-            final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
-            final String username = tokenizer.nextToken();
-            final String password = tokenizer.nextToken();
+            final String username = usernameAndPassword[0];
+            final String password = usernameAndPassword[1];
 
-            log.info(username + " tries to log in with " + password);
+            log.info(username + " tries to log in");
 
-            //Verify user access
+            //We verify user access rights according to roles
+            //After Authentication we are doing Authorization
             if (method.isAnnotationPresent(RolesAllowed.class)) {
                 RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
-                EnumSet<UserDatabase.Role> rolesSet =
+                EnumSet<InMemoryLoginModule.Role> rolesSet =
                         Arrays.stream(rolesAnnotation.value())
-                                .map(r -> UserDatabase.Role.valueOf(r))
-                                .collect(Collectors.toCollection(() -> EnumSet.noneOf(UserDatabase.Role.class)));
+                                .map(InMemoryLoginModule.Role::valueOf)
+                                .collect(Collectors.toCollection(() -> EnumSet.noneOf(InMemoryLoginModule.Role.class)));
 
                 //We check to login/password
-                if (!UserDatabase.USER_DATABASE.checkPassword(username, password)) {
+                if (!InMemoryLoginModule.USER_DATABASE.login(username, password)) {
                     requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                             .entity("Wrong username or password").build());
                     return;
                 }
                 //We check if the role is allowed
-                if (Collections.disjoint(rolesSet, UserDatabase.USER_DATABASE.getUserRoles(username))) {
+                if (!InMemoryLoginModule.isInRoles(rolesSet, username))
                     requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                             .entity("Roles not allowed").build());
-                    return;
-                }
-
-                //We build a new securitycontext to transmit the security data to JAX-RS
-                requestContext.setSecurityContext(new SecurityContext() {
-
-                    @Override
-                    public Principal getUserPrincipal() {
-                        return UserDatabase.USER_DATABASE.getUser(username);
-                    }
-
-                    @Override
-                    public boolean isUserInRole(String role) {
-                        return UserDatabase.USER_DATABASE.getUserRoles(username).contains(UserDatabase.Role.valueOf(role));
-                    }
-
-                    @Override
-                    public boolean isSecure() {
-                        return true;
-                    }
-
-                    @Override
-                    public String getAuthenticationScheme() {
-                        return AUTHENTICATION_SCHEME;
-                    }
-                });
 
+                //We build a new SecurityContext Class to transmit the security data
+                // for this login attempt to JAX-RS
+                requestContext.setSecurityContext(MySecurityContext.newInstance(AUTHENTICATION_SCHEME, username));
 
             }
         }
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java
new file mode 100644
index 0000000..088b2b2
--- /dev/null
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java
@@ -0,0 +1,110 @@
+package fr.univtln.bruno.samples.jaxrs.security.filter;
+
+import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule;
+import fr.univtln.bruno.samples.jaxrs.security.MySecurityContext;
+import fr.univtln.bruno.samples.jaxrs.security.annotations.JWTAuth;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import jakarta.annotation.Priority;
+import jakarta.annotation.security.DenyAll;
+import jakarta.annotation.security.PermitAll;
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.ws.rs.Priorities;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.java.Log;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.stream.Collectors;
+
+/**
+ * This class if a filter for JAX-RS to perform authentication via JWT.
+ */
+@JWTAuth
+@Provider
+@Priority(Priorities.AUTHENTICATION)
+@Log
+public class JsonWebTokenFilter implements ContainerRequestFilter {
+    private static final String AUTHORIZATION_PROPERTY = "Authorization";
+    private static final String AUTHENTICATION_SCHEME = "Bearer";
+
+    //We inject the data from the acceded resource.
+    @Context
+    private ResourceInfo resourceInfo;
+
+    @Override
+    public void filter(ContainerRequestContext requestContext) {
+        //We use reflection on the acceded method to look for security annotations.
+        Method method = resourceInfo.getResourceMethod();
+
+        //if its PermitAll access is granted (without specific security context)
+        if (method.isAnnotationPresent(PermitAll.class)) return;
+
+        //otherwise if its DenyAll the access is refused
+        if (method.isAnnotationPresent(DenyAll.class)) {
+            requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
+                    .entity("Access denied to all users").build());
+            return;
+        }
+
+        //We get the authorization header from the request
+        final String authorization = requestContext.getHeaderString(AUTHORIZATION_PROPERTY);
+
+        //We check the credentials presence
+        if (authorization == null || authorization.isEmpty()) {
+            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
+                    .entity("Please provide your credentials").build());
+            return;
+        }
+
+        //We get the token
+        final String compactJwt = authorization.substring(AUTHENTICATION_SCHEME.length()).trim();
+        if (!authorization.contains(AUTHENTICATION_SCHEME) || compactJwt.isEmpty()) {
+            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
+                    .entity("Please provide correct credentials").build());
+            return;
+        }
+
+        String username = null;
+
+        //We check the validity of the token
+        try {
+            Jws<Claims> jws = Jwts.parserBuilder()
+                    .requireIssuer("sample-jaxrs")
+                    .setSigningKey(InMemoryLoginModule.KEY)
+                    .build()
+                    .parseClaimsJws(compactJwt);
+            username = jws.getBody().getSubject();
+
+            //We build a new securitycontext to transmit the security data to JAX-RS
+            requestContext.setSecurityContext(MySecurityContext.newInstance(AUTHENTICATION_SCHEME, username));
+        } catch (JwtException e) {
+            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
+                    .entity("Wrong JWT token. " + e.getLocalizedMessage()).build());
+        }
+
+
+        //If present we extract the allowed roles annotation.
+        if (method.isAnnotationPresent(RolesAllowed.class)) {
+            RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
+            EnumSet<InMemoryLoginModule.Role> rolesSet =
+                    Arrays.stream(rolesAnnotation.value())
+                            .map(InMemoryLoginModule.Role::valueOf)
+                            .collect(Collectors.toCollection(() -> EnumSet.noneOf(InMemoryLoginModule.Role.class)));
+
+            //We check if the role is allowed
+            if (!InMemoryLoginModule.isInRoles(rolesSet, username))
+                requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
+                        .entity("Roles not allowed").build());
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/fr/univtln/bruno/samples/jaxrs/ServerIT.java b/src/test/java/fr/univtln/bruno/samples/jaxrs/ServerIT.java
index 2a67e62..aca5882 100644
--- a/src/test/java/fr/univtln/bruno/samples/jaxrs/ServerIT.java
+++ b/src/test/java/fr/univtln/bruno/samples/jaxrs/ServerIT.java
@@ -1,7 +1,13 @@
 package fr.univtln.bruno.samples.jaxrs;
 
 import fr.univtln.bruno.samples.jaxrs.model.BiblioModel.Auteur;
+import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule;
 import fr.univtln.bruno.samples.jaxrs.server.BiblioServer;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
 import jakarta.ws.rs.client.Client;
 import jakarta.ws.rs.client.ClientBuilder;
 import jakarta.ws.rs.client.Entity;
@@ -13,7 +19,11 @@ import org.glassfish.grizzly.http.server.HttpServer;
 import org.glassfish.jersey.message.internal.MediaTypes;
 import org.junit.*;
 
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
 import static org.junit.Assert.*;
@@ -205,4 +215,72 @@ public class ServerIT {
         assertEquals(1, auteurs.size());
         assertEquals("Marie", auteurs.get(0).getPrenom());
     }
+
+    @Test
+    public void refusedLogin() {
+        Response result = webTarget.path("biblio/login")
+                .request()
+                .get();
+        assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), result.getStatus());
+    }
+
+    @Test
+    public void acceptedLogin() {
+        String email="john.doe@nowhere.com";
+        String password="admin";
+        Response result = webTarget.path("biblio/login")
+                .request()
+                .accept(MediaType.TEXT_PLAIN)
+                .header("Authorization",  "Basic "+java.util.Base64.getEncoder().encodeToString((email+":"+password).getBytes()))
+                .get();
+
+        String entity = result.readEntity(String.class);
+        assertEquals(Response.Status.OK.getStatusCode(), result.getStatus());
+        Jws<Claims> jws = Jwts.parserBuilder()
+                .setSigningKey(InMemoryLoginModule.KEY)
+                .build()
+                .parseClaimsJws(entity);
+        assertEquals(email,jws.getBody().getSubject());
+    }
+
+    @Test
+    public void jwtAccess() {
+        //Log in to get the token
+        String email="john.doe@nowhere.com";
+        String password="admin";
+        String token = webTarget.path("biblio/login")
+                .request()
+                .accept(MediaType.TEXT_PLAIN)
+                .header("Authorization",  "Basic "+java.util.Base64.getEncoder().encodeToString((email+":"+password).getBytes()))
+                .get(String.class);
+
+        //We access a JWT protected URL with the token
+        Response result = webTarget.path("biblio/secured")
+                .request()
+                .header( "Authorization",  "Bearer "+token)
+                .get();
+        assertEquals(Response.Status.OK.getStatusCode(), result.getStatus());
+        assertEquals("Access with JWT ok for Doe, John <john.doe@nowhere.com>",result.readEntity(String.class));
+    }
+
+    @Test
+    public void jwtAccessDenied() {
+        String forgedToken = Jwts.builder()
+                .setIssuer("sample-jaxrs")
+                .setIssuedAt(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()))
+                .setSubject("john.doe@nowhere.com")
+                .claim("firstname", "John")
+                .claim("lastname", "Doe")
+                .setExpiration(Date.from(LocalDateTime.now().plus(15, ChronoUnit.MINUTES).atZone(ZoneId.systemDefault()).toInstant()))
+                //A RANDOM KEY DIFFERENT FROM THE SERVER
+                .signWith( Keys.secretKeyFor(SignatureAlgorithm.HS256)).compact();
+
+        //We access a JWT protected URL with the token
+        Response result = webTarget.path("biblio/secured")
+                .request()
+                .header( "Authorization",  "Bearer "+forgedToken)
+                .get();
+        assertNotEquals(Response.Status.OK.getStatusCode(), result.getStatus());
+    }
+
 }
diff --git a/src/test/java/fr/univtln/bruno/samples/jaxrs/security/UserTest.java b/src/test/java/fr/univtln/bruno/samples/jaxrs/security/UserTest.java
new file mode 100644
index 0000000..49eb303
--- /dev/null
+++ b/src/test/java/fr/univtln/bruno/samples/jaxrs/security/UserTest.java
@@ -0,0 +1,27 @@
+
+package fr.univtln.bruno.samples.jaxrs.security;
+
+import org.junit.Test;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.EnumSet;
+
+import static org.junit.Assert.assertTrue;
+
+public class UserTest {
+
+    @Test
+    public void testBuilder() throws InvalidKeySpecException, NoSuchAlgorithmException {
+        String lastname="Doe", firstname="John", email="j.d@here.com", password="mypass";
+            User user  = User.builder()
+                    .lastName(lastname)
+                    .firstName(firstname)
+                    .email(email)
+                    .password(password)
+                    .roles(EnumSet.of(InMemoryLoginModule.Role.ADMIN))
+                    .build();
+            assertTrue(user.checkPassword(password));
+            assertTrue(user.contains(InMemoryLoginModule.Role.valueOf("ADMIN")));
+    }
+}
\ No newline at end of file
-- 
GitLab