diff --git a/pom.xml b/pom.xml index d9fc501a209e58467ba08053907a389ec92b728f..edaf75b174b3890292d26699213332ca44c837fb 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,27 @@ <version>3.11</version> <scope>test</scope> </dependency> + + <!-- JWT --> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt-api</artifactId> + <version>0.11.2</version> + </dependency> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt-impl</artifactId> + <scope>runtime</scope> + <version>0.11.2</version> + </dependency> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt-jackson</artifactId> + <version>0.11.2</version> + <scope>compile</scope> + </dependency> + + </dependencies> <build> <plugins> 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 84d030b49f97eeb4ccb3158a9daa15976fbb2f2b..a1386c3e07ed48c6544fc2922e4f23ece80818e7 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,6 +3,9 @@ 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; 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 f346c4d811c898a360346689b6cba256b62b41a3..13596286d94cb7052becf09e9fdb12b7cc8e1f40 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,13 +5,26 @@ 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.User; +import fr.univtln.bruno.samples.jaxrs.security.UserDatabase; import fr.univtln.bruno.samples.jaxrs.status.Status; +import io.jsonwebtoken.Jwts; +import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.*; import lombok.extern.java.Log; +import javax.naming.AuthenticationException; import java.security.SecureRandom; -import java.util.*; +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.List; @Log // The Java class will be hosted at the URI path "/biblio" @@ -128,4 +141,61 @@ public class BiblioResource { public List<Auteur> getAuteursPage(@BeanParam PaginationInfo paginationInfo) { return modeleBibliotheque.getWithFilter(paginationInfo); } + + @GET + @Path("context") + @RolesAllowed("ADMIN") + public String getContext(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders, @Context Request request, @Context SecurityContext securityContext) throws ParseException { + 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() + "] )"; + } + + @GET + @Path("adminsonly") + @RolesAllowed("ADMIN") + @BasicAuth + public String getRestrictedToAdmins() { + return "secret for admins !"; + } + + @GET + @Path("usersonly") + @RolesAllowed("USER") + @BasicAuth + public String getRestrictedToUsers() { + return "secret for users !"; + } + + @GET + @Path("secured") + @RolesAllowed({"USER", "ADMIN"}) + @JWTAuth + public String securedByJWT(@Context SecurityContext securityContext) { + log.info("USER ACCESS :"+securityContext.getUserPrincipal().getName()); + return "Access with JWT ok for "+securityContext.getUserPrincipal().getName(); + } + + @GET + @Path("login") + @RolesAllowed({"USER", "ADMIN"}) + @BasicAuth + public String login(@Context SecurityContext securityContext) { + if (securityContext.isSecure() && securityContext.getUserPrincipal() instanceof User) { + User user = (User) securityContext.getUserPrincipal(); + return Jwts.builder() + .setIssuer("sample-jaxrs") + .setIssuedAt(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant())) + .setSubject(user.getEmail()) + .claim("firstname", user.getFirstName()) + .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(); + } + throw new WebApplicationException(new AuthenticationException()); + } } diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..17d48f3bd0dddcbeb3cd7d9114e592bb56424267 --- /dev/null +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java @@ -0,0 +1,125 @@ +package fr.univtln.bruno.samples.jaxrs.security; + +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.*; +import java.util.stream.Collectors; + +@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 { + private static final String AUTHORIZATION_PROPERTY = "Authorization"; + private static final String AUTHENTICATION_SCHEME = "Basic"; + + //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); + + //We check the presence of the credentials + if (authorization == null || authorization.isEmpty()) { + requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED) + .entity("Please provide your credentials").build()); + return; + } + + //Get encoded username and password + final String encodedUserPassword = authorization.substring(AUTHENTICATION_SCHEME.length()).trim(); + + //Decode username and password + String usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword.getBytes())); + + //Split username and password tokens + final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":"); + final String username = tokenizer.nextToken(); + final String password = tokenizer.nextToken(); + + log.info(username + " tries to log in with " + password); + + //Verify user access + 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 to login/password + if (!UserDatabase.USER_DATABASE.checkPassword(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))) { + 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/BasicAuth.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/BasicAuth.java new file mode 100644 index 0000000000000000000000000000000000000000..458387ed9684cd9a8e37c0ac84d345abae5dd93b --- /dev/null +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/BasicAuth.java @@ -0,0 +1,16 @@ +package fr.univtln.bruno.samples.jaxrs.security; + +import jakarta.ws.rs.NameBinding; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@NameBinding +@Retention(RUNTIME) +@Target({TYPE, METHOD}) +public @interface BasicAuth { +} diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java new file mode 100644 index 0000000000000000000000000000000000000000..00b75227200f20567e1938404897ee454879f9e5 --- /dev/null +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java @@ -0,0 +1,16 @@ +package fr.univtln.bruno.samples.jaxrs.security; + +import jakarta.ws.rs.NameBinding; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@NameBinding +@Retention(RUNTIME) +@Target({TYPE, METHOD}) +public @interface JWTAuth { +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..1d19aeb0fea9a9b2073db51961f3e86f252270ea --- /dev/null +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JsonWebTokenFilter.java @@ -0,0 +1,128 @@ +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/User.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java new file mode 100644 index 0000000000000000000000000000000000000000..a18f358ebe052175de82944a4ba9f18e935b39b4 --- /dev/null +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java @@ -0,0 +1,64 @@ +package fr.univtln.bruno.samples.jaxrs.security; + +import lombok.*; +import lombok.experimental.Delegate; +import lombok.experimental.FieldDefaults; +import lombok.extern.java.Log; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.*; + +@Log +@FieldDefaults(level = AccessLevel.PRIVATE) +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(of = "email") +public class User implements Principal { + UUID uuid = UUID.randomUUID(); + String firstName, lastName, email; + byte[] passwordHash; + byte[] salt = new byte[16]; + + @Delegate + EnumSet<UserDatabase.Role> roles; + + SecureRandom random = new SecureRandom(); + + @Builder + public User(String firstName, String lastName, String email, String password, EnumSet<UserDatabase.Role> roles) + throws NoSuchAlgorithmException, InvalidKeySpecException { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.roles = roles; + + random.nextBytes(salt); + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + passwordHash = factory.generateSecret(spec).getEncoded(); + } + + @Override + public String getName() { + return lastName + ", " + firstName+" <"+email+">"; + } + + public String toString() { + return email + "" + Base64.getEncoder().encodeToString(passwordHash); + } + + public boolean checkPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException { + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + byte[] submittedPasswordHash = factory.generateSecret(spec).getEncoded(); + return Arrays.equals(passwordHash, submittedPasswordHash); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..7e0cc79d4b46dc5011cbf5f295947d4e030f74b7 --- /dev/null +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/UserDatabase.java @@ -0,0 +1,66 @@ +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/server/BiblioServer.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/server/BiblioServer.java index c830df7e2656868cdb1f153a31a62b2e1dcc6d55..5255f7995aba00a93ff64a21461e29119d1f3faa 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/server/BiblioServer.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/server/BiblioServer.java @@ -18,25 +18,6 @@ public class BiblioServer { // Base URI the Grizzly HTTP server will listen on public static final String BASE_URI = "http://0.0.0.0:9998/myapp"; - /** - * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application. - * - * @return Grizzly HTTP server. - */ - public static HttpServer startServer() { - // create a resource config that scans for JAX-RS resources and providers - // in demos package and add a logging feature to the server. - Logger logger = Logger.getLogger(BiblioServer.class.getName()); - - final ResourceConfig rc = new ResourceConfig() - .packages(true, "fr.univtln.bruno.samples.jaxrs") - .register(new LoggingFeature(logger, Level.INFO, null, null)); - - // create and start a new instance of grizzly http server - // exposing the Jersey application at BASE_URI - return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc); - } - /** * Main method. * @@ -50,10 +31,30 @@ public class BiblioServer { Runtime.getRuntime().addShutdownHook(new Thread(server::shutdownNow)); log.info(String.format("Application started.%n" + - "Stop the application using CTRL+C")); + "Stop the application using CTRL+C")); //We wait an infinite time. Thread.currentThread().join(); server.shutdown(); } + + /** + * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application. + * + * @return Grizzly HTTP server. + */ + public static HttpServer startServer() { + // create a resource config that scans for JAX-RS resources and providers + // in demos package and add a logging feature to the server. + Logger logger = Logger.getLogger(BiblioServer.class.getName()); + logger.setLevel(Level.FINE); + + final ResourceConfig rc = new ResourceConfig() + .packages(true, "fr.univtln.bruno.samples.jaxrs") + .register(new LoggingFeature(logger, Level.INFO, LoggingFeature.Verbosity.PAYLOAD_TEXT, null)); + + // create and start a new instance of grizzly http server + // exposing the Jersey application at BASE_URI + return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc); + } }