I'm looking for a way to enable token-based authentication in Jersey. I am trying not to use any particular framework. Is that possible?
My plan is: A user signs up for my web service, my web service generates a token, sends it to the client, and the client will retain it. Then the client, for each request, will send the token instead of username and password.
I was thinking of using a custom filter for each request and @PreAuthorize("hasRole('ROLE')")
, but I just thought that this causes a lot of requests to the database to check if the token is valid.
Or not create filter and in each request put a param token? So that each API first checks the token and after executes something to retrieve resource.
Best Answer
How token-based authentication works
In token-based authentication, the client exchanges hard credentials (such as username and password) for a piece of data called token. For each request, instead of sending the hard credentials, the client will send the token to the server to perform authentication and then authorization.
In a few words, an authentication scheme based on tokens follow these steps:
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 JAX-RS 2.0 implementations, such as Jersey, RESTEasy and Apache CXF.
It is worthwhile to mention that if you are using 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. It's a custom authentication.Authenticating a user with their username and password and issuing a token
Create a JAX-RS resource method which receives and validates the credentials (username and password) and issue a token for the user:
If any exceptions are thrown when validating the credentials, a response with the status
403
(Forbidden) will be returned.If the credentials are successfully validated, a response with the status
200
(OK) will be returned and the issued token will be sent to the client in the response payload. The client must send the token to the server in every request.When consuming
application/x-www-form-urlencoded
, the client must send the credentials in the following format in the request payload:Instead of form params, it's possible to wrap the username and the password into a class:
And then consume it as JSON:
Using this approach, the client must to send the credentials in the following format in the payload of the request:
Extracting the token from the request and validating it
The client should send the token in the standard HTTP
Authorization
header of the request. For example:The name of the standard HTTP header is unfortunate because it carries authentication information, not authorization. However, it's the standard HTTP header for sending credentials to the server.
JAX-RS provides
@NameBinding
, a meta-annotation used to create other annotations to bind filters and interceptors to resource classes and methods. Define a@Secured
annotation as following:The above defined name-binding annotation will be used to decorate a filter class, which implements
ContainerRequestFilter
, allowing you to intercept the request before it be handled by a resource method. TheContainerRequestContext
can be used to access the HTTP request headers and then extract the token:If any problems happen during the token validation, a response with the status
401
(Unauthorized) will be returned. Otherwise the request will proceed to a resource method.Securing your REST endpoints
To bind the authentication filter to resource methods or resource classes, annotate them with the
@Secured
annotation created above. For the methods and/or classes that are annotated, the filter will be executed. It means that such endpoints will only 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:
In the example shown above, the filter will be executed only for the
mySecuredMethod(Long)
method because it's annotated with@Secured
.Identifying the current user
It's very likely that you will need to know the user who is performing the request agains your REST API. The following approaches can be used to achieve it:
Overriding the security context of the current request
Within your
ContainerRequestFilter.filter(ContainerRequestContext)
method, a newSecurityContext
instance can be set for the current request. Then override theSecurityContext.getUserPrincipal()
, returning aPrincipal
instance:Use the token to look up the user identifier (username), which will be the
Principal
's name.Inject the
SecurityContext
in any JAX-RS resource class:The same can be done in a JAX-RS resource method:
And then get the
Principal
:Using CDI (Context and Dependency Injection)
If, for some reason, you don't want to override the
SecurityContext
, you can use CDI (Context and Dependency Injection), which provides useful features such as events and producers.Create a CDI qualifier:
In your
AuthenticationFilter
created above, inject anEvent
annotated with@AuthenticatedUser
:If the authentication succeeds, fire the event passing the username as parameter (remember, the token is issued for a user and the token will be used to look up the user identifier):
It's very likely that there's a class that represents a user in your application. Let's call this class
User
.Create a CDI bean to handle the authentication event, find a
User
instance with the correspondent username and assign it to theauthenticatedUser
producer field:The
authenticatedUser
field produces aUser
instance that can be injected into container managed beans, such as JAX-RS services, CDI beans, servlets and EJBs. Use the following piece of code to inject aUser
instance (in fact, it's a CDI proxy):Note that the CDI
@Produces
annotation is different from the JAX-RS@Produces
annotation:javax.enterprise.inject.Produces
javax.ws.rs.Produces
Be sure you use the CDI
@Produces
annotation in yourAuthenticatedUserProducer
bean.The key here is the bean annotated with
@RequestScoped
, allowing you to share data between filters and your beans. If you don't wan't to use events, you can modify the filter to store the authenticated user in a request scoped bean and then read it from your JAX-RS resource classes.Compared to the approach that overrides the
SecurityContext
, the CDI approach allows you to get the authenticated user from beans other than JAX-RS resources and providers.Supporting role-based authorization
Please refer to my other answer for details on how to support role-based authorization.
Issuing tokens
A token can be:
See details below:
Random string as token
A token can be issued by generating a random string and persisting it to a database along with the user identifier and an expiration date. A good example of how to generate a random string in Java can be seen here. You also could use:
JWT (JSON Web Token)
JWT (JSON Web Token) 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 it enables you to store details in claims. These claims are stored in the token payload which is a JSON encoded as Base64. Here are some claims registered in the RFC 7519 and what they mean (read the full RFC for further details):
iss
: Principal that issued the token.sub
: Principal that is the subject of the JWT.exp
: Expiration date for the token.nbf
: Time on which the token will start to be accepted for processing.iat
: Time on which the token was issued.jti
: Unique identifier for the token.Be aware that you must not store sensitive data, such as passwords, in the token.
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. The signature is what prevents the token from being tampered with.
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 on the server, you could persist the token identifier (
jti
claim) along with some other details such as the user you issued the token for, the expiration date, etc.When persisting tokens, always consider removing the old ones in order to prevent your database from growing indefinitely.
Using JWT
There are a few Java libraries to issue and validate JWT tokens such as:
To find some other great resources to work with JWT, have a look at http://jwt.io.
Handling token revocation with JWT
If you want to revoke tokens, you must keep the track of them. You don't need to store the whole token on server side, store only the token identifier (that must be unique) and some metadata if you need. For the token identifier you could use UUID.
The
jti
claim should be used to store the token identifier on the token. When validating the token, ensure that it has not been revoked by checking the value of thejti
claim against the token identifiers you have on server side.For security purposes, revoke all the tokens for a user when they change their password.
Additional information