Credits to Cássio Mazzochi Molin whose stackoverflow response has helped me to structure this simple example project. See more in:
- Token-based authentication with JAX-RS 2.0
- StackOverflow Response
- JSON Web Token in action with JAX-RS
- JAX-RS Security using JSON Web Tokens (JWT) for Authentication and Authorization
Here below I explain how to test this sample, and then there is a detailed explanation of how to build it from scratch (credits to Cássio Mazzochi Molin response).
This image explain how a JSON Web Token works:
-
In the code I left a single
TODO
inside the classAuthenticationJwt
in the methodauthenticate(String username, String password)
in which you have to deal with an authentication against a database or LDAP or file or whatever to verify the identity and issue a token only if the user is authorized to get it. For now I leaved it blank so that any user which ask a JWT will receive a token. -
Firstly invoke
http://localhost:8080/jaxrs-jws-jwt-web/rest-api/auth/auth
with aPOST
andContent-Type
set toapplication/json
, finally fill the body with a json like this{"username":"client_username", "password":"client_password"}
using postman you should have something like this: -
If you're using postman and switch the view to
Headers
you could see the server-side generatedJSON Web Token
that the client could now use until the expiring time to invoke secured endopoints. Copy that value (in the sceenshot I highlighted the one that I copied) in the clipboard. -
Now invoke
http://localhost:8080/jaxrs-jws-jwt-web/rest-api/test/1
with aDELETE
, andContent-Type
set toapplication/json
add a newHeader
of typeAuthorization
and fill it with the textBearer
+the previous JSON Web Token
and send the request. The response should be something like this.
A token is a piece of data generated by the server which identifies a user.
At a first look, token-based authentication follow these steps:
- The client sends its credentials (username and password) to the server.
- The server authenticates them and generates a token with an expiration date.
- The server stores the previously generated token in some storage with user identifier, such as a database or a map in memory.
- The server sends the generated token to the client.
- In every request, the client sends the token to the server.
- The server, in each request, extracts the token from the incoming request, looks up the user identifier with the token to obtain the user information to do the authentication/authorization.
- If the token is expired, the server generates another token and send it back to the client.
What you can do with JAX-RS 2.0 (Jersey, RESTEasy and Apache CXF)
This solution uses only the JAX-RS 2.0 API, avoiding any vendor specific solution. So, it should work with the most popular JAX-RS 2.0 implementations, such as Jersey, RESTEasy and Apache CXF.
It's important mention that if you are using a token-based authentication, you are not relying on the standard Java EE Web application security mechanisms offered by the Servlet container and configurable via application's web.xml
descriptor.
Authenticate a user with their username and password and issue a token
Create a REST endpoint which receives and validates the credentials (username and password) and issue a token for the user:
@Path("/authentication")
public class AuthenticationEndpoint {
@POST
@Produces("application/json")
@Consumes("application/x-www-form-urlencoded")
public Response authenticateUser(@FormParam("username") String username,
@FormParam("password") String password) {
try {
// Authenticate the user using the credentials provided
authenticate(username, password);
// Issue a token for the user
String token = issueToken(username);
// Return the token on the response
return Response.ok(token).build();
} catch (Exception e) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
private void authenticate(String username, String password) throws Exception {
// Authenticate against a database, LDAP, file or whatever
// Throw an Exception if the credentials are invalid
}
private String issueToken(String username) {
// Issue a token (can be a random String persisted to a database or a JWT token)
// The issued token must be associated to a user
// Return the issued token
}
}
If any exceptions happen when validating the credentials, a response with status 401 UNAUTHORIZED
will be returned.
If the credentials are successfully validated, a response with status 200 OK
will be returned and the issued token is sent to the client on the response. The client must send that token to the server in every request.
Using this approach, you expect your client will send the credentials in the following format in the body of the request:
username=admin&password=123456
Instead of form params, you can wrap the username and the password into a class:
public class Credentials implements Serializable {
private String username;
private String password;
// Getters and setters omitted
}
And consume it as JSON:
@POST
@Produces("application/json")
@Consumes("application/json")
public Response authenticateUser(Credentials credentials) {
String username = credentials.getUsername();
String password = credentials.getPassword();
// Authenticate the user, issue a token and return a response
}
Using this approach, you expect your client will send the credentials in the following format in the body of the request:
{ "username": "admin", "password": "123456" }
Extract the token from the request and validate it
The client should send the token on the standard HTTP Authorization
header of the request. For example:
Authorization: Bearer
Note that the name of the standard HTTP header is unfortunate because it carries authentication information, not authorization.
JAX-RS provides @NameBinding
, a meta-annotation used to create name-binding annotations for filters and interceptors:
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Secured { }
The defined name-binding annotation @Secured
will be used to decorate a filter class, which implements ContainerRequestFilter
, allowing you to handle the request. The ContainerRequestContext
helps you to extract the token from the HTTP request:
@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the HTTP Authorization header from the request
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Check if the HTTP Authorization header is present and formatted correctly
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
throw new NotAuthorizedException("Authorization header must be provided");
}
// Extract the token from the HTTP Authorization header
String token = authorizationHeader.substring("Bearer".length()).trim();
try {
// Validate the token
validateToken(token);
} catch (Exception e) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private void validateToken(String token) throws Exception {
// Check if it was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
}
If any problems happen during the token validation, a response with status 401 UNAUTHORIZED
will be returned.
Otherwise, the request will proceed to an endpoint.
Securing your REST endpoints
Bind the filter to your endpoints methods or classes by annotating them with the @Secured
annotation created above. For the methods and/or classes which are annotated, the filter will be executed. It means that these endpoints only will be reached if the request is performed with a valid token.
If some methods or classes do not need authentication, simply do not annotate them.
@Path("/")
public class MyEndpoint {
@GET
@Path("{id}")
@Produces("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("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
...
}
}
In the example above, the filter will be executed only for mySecuredMethod(Long)
because it's annotated with @Secured
.
It's very likely you will need to know the user who is performing the request within your REST endpoints. The following approaches can be useful to do it:
Overriding the SecurityContext
Within your ContainerRequestFilter.filter(ContainerRequestContext)
method, you can set a new security context information for the current request.
Override the SecurityContext.getUserPrincipal()
, returning a Principal
instance.
The Principal
's name is the username of the user you issued the token for. You will have to know it when validating the token.
requestContext.setSecurityContext(new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return username;
}
};
}
@Override
public boolean isUserInRole(String role) {
return true;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public String getAuthenticationScheme() {
return null;
}
});
Inject a proxy of the SecurityContext
in any REST endpoint class:
@Context
SecurityContext securityContext;
The same can be done in a method:
@GET
@Secured
@Path("{id}")
@Produces("application/json")
public Response myMethod(@PathParam("id") Long id,
@Context SecurityContext securityContext) {
...
}
And get the Principal
:
Principal principal = securityContext.getUserPrincipal(); String username = principal.getName();
Using CDI (Context and Dependency Injection)
If, for some reason, you don't want overriding the SecurityContext
, you can use CDI, which provides useful features such as events and producers.
Create a CDI qualifier which will be used when handling the authentication event and when injecting the authenticated user in your beans:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface AuthenticatedUser { }
In your AuthenticationFilter
created above, inject an Event
:
@Inject
@AuthenticatedUser
Event<String> userAuthenticatedEvent;
When the user authenticates, fire the event passing the username as parameter (remember, your token must be associated to a user and you need to be able to retrieve the username from a token):
userAuthenticatedEvent.fire(username);
Probably you have a class which represents a user in your application. Let's call this class User
.
The piece of code below handles the authentication event, finds a User
instance with the correspondent username and assigns it to the field authenticatedUser
:
@RequestScoped
public class AuthenticatedUserProducer {
@Produces
@RequestScoped
@AuthenticatedUser
private User authenticatedUser;
public void handleAuthenticationEvent(@Observes @AuthenticatedUser String username) {
this.authenticatedUser = findUser(username);
}
private User findUser(String username) {
// Hit the the database or a service to find a user by its username and return it
// Return the User instance
}
}
The authenticatedUser
field produces a User
instance which can be injected in your beans, such as JAX-RS services, CDI beans, servlets and EJBs:
@Inject
@AuthenticatedUser
User authenticatedUser;
Note that the CDI @Produces
annotation is different from the JAX-RS @Produces
annotation:
- CDI:
javax.enterprise.inject.Produces
- JAX-RS:
javax.ws.rs.Produces
Besides authentication you can also support role-based authorization in your REST endpoints.
Create an enumeration and define the roles according to your needs:
public enum Role {
ROLE_1,
ROLE_2,
ROLE_3
}
Change the @Secured
name binding annotation created above to support roles:
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Secured {
Role[] value() default {};
}
Annotate your endpoints to perform role-based authorization.
Note that the @Secured
annotation can the used in classes and/or methods. So let's make the method annotations override the class annotations:
@Path("/example")
@Secured({Role.ROLE_1})
public class MyEndpoint {
@GET
@Path("{id}")
@Produces("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("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
...
}
}
Create a filter with the AUTHORIZATION
priority, which is executed after the AUTHENTICATION
priority filter defined previously.
The ResourceInfo
can be used to get the Method
and Class
which match with the requested URL and extract the annotations from them:
@Secured
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the resource class which matches with the requested URL
// Extract the roles declared by it
Class<?> resourceClass = resourceInfo.getResourceClass();
List<Role> classRoles = extractRoles(resourceClass);
// Get the resource method which matches with the requested URL
// Extract the roles declared by it
Method resourceMethod = resourceInfo.getResourceMethod();
List<Role> methodRoles = extractRoles(resourceMethod);
try {
// Check if the user is allowed to execute the method
// The method annotations override the class annotations
if (methodRoles.isEmpty()) {
checkPermissions(classRoles);
} else {
checkPermissions(methodRoles);
}
} catch (Exception e) {
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).build());
}
}
// Extract the roles from the annotated element
private List<Role> extractRoles(AnnotatedElement annotatedElement) {
if (annotatedElement == null) {
return new ArrayList<Role>();
} else {
Secured secured = annotatedElement.getAnnotation(Secured.class);
if (secured == null) {
return new ArrayList<Role>();
} else {
Role[] allowedRoles = secured.value();
return Arrays.asList(allowedRoles);
}
}
}
private void checkPermissions(List<Role> allowedRoles) throws Exception {
// Check if the user contains one of the allowed roles
// Throw an Exception if the user has not permission to execute the method
}
}
If the user has no permission to execute the method, the request is aborted with a 403 FORBIDDEN
.
To know the user who is performing the request, see the section above. You can get it from the SecurityContext
(which should be already set in the ContainerRequestContext
) or inject it using CDI, depending on the approach you are using.
If a @Secured
annotation has no roles declared, you can assume all authenticated users can access that endpoint, independent the roles the users have.
A token can be opaque which reveals no details other than the value itself (like a random string) or can be self-contained (like JSON Web Token).
Random string
A token can be issued by generating a random string and persisting it to a database with an expiration date and with a user identifier associated to it. A good example of how to generate a random string in Java can be seen here:
Random random = new SecureRandom(); String token = new BigInteger(130, random).toString(32);
JSON Web Token (JWT)
JSON Web Token (JWT) is a standard method for representing claims securely between two parties and is defined by the RFC 7519. It's a self-contained token and enables you to store a user identifier, an expiration date and whatever you want (but don't store passwords) in a payload, which is a JSON encoded as Base64.
The payload can be read by the client and the integrity of the token can be easily checked by verifying its signature on the server.
You won't need to persist JWT tokens if you don't need to track them. Althought, by persisting the tokens, you will have the possibility of invalidating and revoking the access of them. To keep the track of JWT tokens, instead of persisting the whole token, you could persist the token identifier (the jti
claim) and some metadata (the user you issued the token for, the expiration date, etc) if you need.
There are a few Java libraries to issue and validate JWT tokens (have a look here and here). To find some other great resources to work with JWT, have a look at http://jwt.io.
Your application can provide some functionality to revoke the tokens, but it's recommended revoking the tokens when the users change their password.
When persisting tokens, always consider removing the old ones in order to prevent your database from growing indefinitely.
- It doesn't matter which type of authentication you are using. Always use HTTPS to prevent the man-in-the-middle attack.
- Take a look at this question from Information Security for more information about tokens.
- In this article you will find some useful information about token-based authentication.
- Apache DeltaSpike provides portable CDI extensions such as a security module, which can be used to secure REST applications.
- Interested in an OAuth 2.0 protocol implementation in Java? Check the Apache Oltu project.