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.
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:
The client sends their credentials (username and password) to the server.
The server authenticates the credentials and, if they are valid, generate a token for the user.
The server stores the previously generated token in some storage along with the user identifier and an expiration date.
The server sends the generated token to the client.
The client sends the token to the server in each request.
The server, in each request, extracts the token from the incoming request. With the token, the server looks up the user details to perform authentication.
If the token is valid, the server accepts the request.
If the token is invalid, the server refuses the request.
Once the authentication has been performed, the server performs authorization.
The server can provide an endpoint to refresh tokens.
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:
#Path("/authentication")
public class AuthenticationEndpoint {
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_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.FORBIDDEN).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 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:
username=admin&password=123456
Instead of form params, it's possible to 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 then consume it as JSON:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.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, the client must to send the credentials in the following format in the payload of the request:
{
"username": "admin",
"password": "123456"
}
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:
Authorization: Bearer <token-goes-here>
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:
#NameBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Secured { }
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. The ContainerRequestContext can be used to access the HTTP request headers and then extract the token:
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
private static final String REALM = "example";
private static final String AUTHENTICATION_SCHEME = "Bearer";
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the Authorization header from the request
String authorizationHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Validate the Authorization header
if (!isTokenBasedAuthentication(authorizationHeader)) {
abortWithUnauthorized(requestContext);
return;
}
// Extract the token from the Authorization header
String token = authorizationHeader
.substring(AUTHENTICATION_SCHEME.length()).trim();
try {
// Validate the token
validateToken(token);
} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}
private boolean isTokenBasedAuthentication(String authorizationHeader) {
// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
// The authentication scheme comparison must be case-insensitive
return authorizationHeader != null && authorizationHeader.toLowerCase()
.startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
// Abort the filter chain with a 401 status code response
// The WWW-Authenticate header is sent along with the response
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE,
AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"")
.build());
}
private void validateToken(String token) throws Exception {
// Check if the token was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
}
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:
#Path("/example")
public class ExampleResource {
#GET
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response myUnsecuredMethod(#PathParam("id") Long id) {
// This method is not annotated with #Secured
// The authentication filter won't be executed before invoking this method
...
}
#DELETE
#Secured
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response mySecuredMethod(#PathParam("id") Long id) {
// This method is annotated with #Secured
// The authentication filter will be executed before invoking this method
// The HTTP request must be performed with a valid token
...
}
}
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 new SecurityContext instance can be set for the current request. Then override the SecurityContext.getUserPrincipal(), returning a Principal instance:
final SecurityContext currentSecurityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return () -> username;
}
#Override
public boolean isUserInRole(String role) {
return true;
}
#Override
public boolean isSecure() {
return currentSecurityContext.isSecure();
}
#Override
public String getAuthenticationScheme() {
return AUTHENTICATION_SCHEME;
}
});
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:
#Context
SecurityContext securityContext;
The same can be done in a JAX-RS resource method:
#GET
#Secured
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response myMethod(#PathParam("id") Long id,
#Context SecurityContext securityContext) {
...
}
And then 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 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:
#Qualifier
#Retention(RUNTIME)
#Target({ METHOD, FIELD, PARAMETER })
public #interface AuthenticatedUser { }
In your AuthenticationFilter created above, inject an Event annotated with #AuthenticatedUser:
#Inject
#AuthenticatedUser
Event<String> userAuthenticatedEvent;
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):
userAuthenticatedEvent.fire(username);
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 the authenticatedUser producer field:
#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 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 a User instance (in fact, it's a CDI proxy):
#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
Be sure you use the CDI #Produces annotation in your AuthenticatedUserProducer 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:
Opaque: Reveals no details other than the value itself (like a random string)
Self-contained: Contains details about the token itself (like JWT).
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:
Random random = new SecureRandom();
String token = new BigInteger(130, random).toString(32);
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:
jjwt
java-jwt
jose4j
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 the jti 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
It doesn't matter which type of authentication you decide to use. Always do it on the top of a HTTPS connection 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.
This answer is all about authorization and it is a complement of my previous answer about authentication
Why another answer? I attempted to expand my previous answer by adding details on how to support JSR-250 annotations. However the original answer became the way too long and exceeded the maximum length of 30,000 characters. So I moved the whole authorization details to this answer, keeping the other answer focused on performing authentication and issuing tokens.
Supporting role-based authorization with the #Secured annotation
Besides authentication flow shown in the other answer, role-based authorization can be supported in the 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 before to support roles:
#NameBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Secured {
Role[] value() default {};
}
And then annotate the resource classes and methods with #Secured to perform the authorization. The method annotations will override the class annotations:
#Path("/example")
#Secured({Role.ROLE_1})
public class ExampleResource {
#GET
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response myMethod(#PathParam("id") Long id) {
// This method is not annotated with #Secured
// But it's declared within a class annotated with #Secured({Role.ROLE_1})
// So it only can be executed by the users who have the ROLE_1 role
...
}
#DELETE
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
#Secured({Role.ROLE_1, Role.ROLE_2})
public Response myOtherMethod(#PathParam("id") Long id) {
// This method is annotated with #Secured({Role.ROLE_1, Role.ROLE_2})
// The method annotation overrides the class annotation
// So it only can be executed by the users who have the ROLE_1 or ROLE_2 roles
...
}
}
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 resource Method and resource Class that will handle the request and then extract the #Secured 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 operation, the request is aborted with a 403 (Forbidden).
To know the user who is performing the request, see my previous answer. 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 go for.
If a #Secured annotation has no roles declared, you can assume all authenticated users can access that endpoint, disregarding the roles the users have.
Supporting role-based authorization with JSR-250 annotations
Alternatively to defining the roles in the #Secured annotation as shown above, you could consider JSR-250 annotations such as #RolesAllowed, #PermitAll and #DenyAll.
JAX-RS doesn't support such annotations out-of-the-box, but it could be achieved with a filter. Here are a few considerations to keep in mind if you want to support all of them:
#DenyAll on the method takes precedence over #RolesAllowed and #PermitAll on the class.
#RolesAllowed on the method takes precedence over #PermitAll on the class.
#PermitAll on the method takes precedence over #RolesAllowed on the class.
#DenyAll can't be attached to classes.
#RolesAllowed on the class takes precedence over #PermitAll on the class.
So an authorization filter that checks JSR-250 annotations could be like:
#Provider
#Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method method = resourceInfo.getResourceMethod();
// #DenyAll on the method takes precedence over #RolesAllowed and #PermitAll
if (method.isAnnotationPresent(DenyAll.class)) {
refuseRequest();
}
// #RolesAllowed on the method takes precedence over #PermitAll
RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
performAuthorization(rolesAllowed.value(), requestContext);
return;
}
// #PermitAll on the method takes precedence over #RolesAllowed on the class
if (method.isAnnotationPresent(PermitAll.class)) {
// Do nothing
return;
}
// #DenyAll can't be attached to classes
// #RolesAllowed on the class takes precedence over #PermitAll on the class
rolesAllowed =
resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
performAuthorization(rolesAllowed.value(), requestContext);
}
// #PermitAll on the class
if (resourceInfo.getResourceClass().isAnnotationPresent(PermitAll.class)) {
// Do nothing
return;
}
// Authentication is required for non-annotated methods
if (!isAuthenticated(requestContext)) {
refuseRequest();
}
}
/**
* Perform authorization based on roles.
*
* #param rolesAllowed
* #param requestContext
*/
private void performAuthorization(String[] rolesAllowed,
ContainerRequestContext requestContext) {
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
refuseRequest();
}
for (final String role : rolesAllowed) {
if (requestContext.getSecurityContext().isUserInRole(role)) {
return;
}
}
refuseRequest();
}
/**
* Check if the user is authenticated.
*
* #param requestContext
* #return
*/
private boolean isAuthenticated(final ContainerRequestContext requestContext) {
// Return true if the user is authenticated or false otherwise
// An implementation could be like:
// return requestContext.getSecurityContext().getUserPrincipal() != null;
}
/**
* Refuse the request.
*/
private void refuseRequest() {
throw new AccessDeniedException(
"You don't have permissions to perform this action.");
}
}
Note: The above implementation is based on the Jersey RolesAllowedDynamicFeature. If you use Jersey, you don't need to write your own filter, just use the existing implementation.
Related
I am newbie to Java(came from .Net background), I am trying to write a RESTful service using Jersey framework. I referred this link http://www.vogella.com/tutorials/REST/article.html
#GET
#Produces(MediaType.TEXT_PLAIN)
public String sayPlainTextHello() {
return "Hello Jersey";
}
Now I want to get the HTTPContext in the above method. Basically I am looking to get the logged-in user name, I hope that should be available using HttpContext.Request.
I am using windows NT authentication.
Can anybody please tell me how to get the HTTPContext/User information inside the Java RESTful service.
You can use below code to get the HTTPContext in your REST resource.
public class RestResource {
#Context
private HttpServletRequest httpServletRequest;
#GET
#Produces(MediaType.TEXT_PLAIN)
public String sayPlainTextHello() {
RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(RequestConstants.REQUEST_CONTEXT);
// Get whatever the value set in your request context
String userName = requestContext.requestContext.getQueryString("userName");
return userName;
}
}
let me know if you need any help.
Short answer
The SecurityContext injected with the #Context annotation allows you to access security related information. It has a request scope and hold details about the user who is authenticated.
For other types that can be injected with #Context, check this answer.
Long answer
You must mind that REST authentication must be stateless. That is, you must not rely on sessions stored on server side, hence the user credentials must be sent in each request. In other words, each request must be authenticated/authorized. To understand this concept, this answer may be insightful.
See below how the SecurityContext can be used in a couple of approaches to secure your application:
Basic authentication
The Basic authentication scheme described in the RFC 7617 with HTTPS is a common and efficent approach to secure a REST application:
2. The 'Basic' Authentication Scheme
The Basic authentication scheme is based on the model that the client
needs to authenticate itself with a user-id and a password for each
protection space ("realm"). [...] The server will service the request only if it can validate
the user-id and password for the protection space applying to the
requested resource.
[...]
To receive authorization, the client
obtains the user-id and password from the user,
constructs the user-pass by concatenating the user-id, a single
colon (:) character, and the password,
encodes the user-pass into an octet sequence,
and obtains the basic-credentials by encoding this octet sequence
using Base64 into a sequence of US-ASCII
characters.
[...]
If the user agent wishes to send the user-id "Aladdin" and password
"open sesame", it would use the following header field:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
[...]
You could use a ContainerRequestFilter to extract the user credentials from the Authorization request header, authenticate them against your authentication provider and then set a SecurityContext with the username for the request. The AuthenticationService implementation is up to you:
#Provider
#Priority(Priorities.AUTHENTICATION)
class AuthenticationFilter implements ContainerRequestFilter {
#Inject
private AuthenticationService authenticationService;
#Override
public void filter(ContainerRequestFilter requestContext) {
// 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("Basic ")) {
throw new NotAuthorizedException("Authorization header must be provided");
}
// Extract the Basic authentication token from the HTTP Authorization header
String token = authorizationHeader.substring("Basic".length()).trim();
// Decodes the token
String credentials = Base64.getDecoder().decode(token);
String[] split = decoded.split(":");
try {
// Authenticate the credentials against in your authentication provider
authenticationService.authenticate(split[0], split[1]);
} catch (Exception e) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
// Updates the security context for the request
SecurityContext securityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(
new CustomSecurityContext(split[0], securityContext.isSecure()));
}
}
A custom SecurityContext implementation could be like:
public class CustomSecurityContext implements SecurityContext {
private final String username;
private final boolean secure;
public BasicSecurityContext(String username, boolean secure) {
this.username = username;
this.secure = secure;
}
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return username;
}
};
}
#Override
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
}
#Override
public boolean isSecure() {
return secure;
}
#Override
public boolean isUserInRole(String role) {
return true;
}
}
Then the SecurityContext can be injected in any resource class using the #Context annotation:
#Path("/example")
public class MyResource {
#Context
private SecurityContext securityContext;
...
}
It also can be injected in a resource method parameter:
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response myResourceMethod(#PathParam("id") Long id,
#Context SecurityContext securityContext) {
...
}
And then get the Principal from the SecurityContext:
Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();
Token based authentication
In an authentication scheme based on tokens, the token becomes a credential of the user.
Hard credentials such as username and password are exchanged for a token that must be sent in each request then the server can perform authentication/authorization. Tokens can be valid for a short amount of time, can be revoked, can carry scope details (what can be requested with the token), etc.
For more details, have a look at this answer. The SecurityContext can be used in the same way as explained above.
I've created a self hosted Java applicataion and I would like to use Google sign in to log in into. I followed the follwong example:
https://developers.google.com/identity/sign-in/web/
That of course work, but now I'm getting a little confuse on how I can authorize the calls on the server. In the backend I'm using Grizzly+Jersey.
As described on the Google Sig-In documentation, you can use Google API Client Library for Java in order to check the authentication token on server side.
Client side
After a user successfully signs in, get the user's ID token:
function onSignIn(googleUser) {
var idToken = googleUser.getAuthResponse().id_token;
...
}
And send the idToken to the server in every request using the standard HTTP Authorization header.
Server side
You can use a filter to perform authentication and/or authorization.
To bind filters to your REST endpoints, JAX-RS provides the meta-annotation #NameBinding and can be used as following:
#NameBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Secured { }
The #Secured annotation will be used to decorate a filter class, which implements ContainerRequestFilter, allowing you to handle the request, get and validate the token.
The ContainerRequestContext helps you to extract information from the HTTP request.
The #Provider annotation marks an implementation of an extension interface that should be discoverable by JAX-RS/Jersey runtime during a provider scanning phase.
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the token header from the HTTP Authorization request header
String token =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Check if the token is present
if (token == null || token.isEmpty()) {
throw new NotAuthorizedException("Token must be provided");
}
// Validate the token
validateToken(token);
}
private void validateToken(String token) {
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier
.Builder(new NetHttpTransport(), new GsonFactory())
.setAudience(Arrays.asList(CLIENT_ID))
.build();
GoogleIdToken idToken = verifier.verify(token);
if (idToken != null) {
Payload payload = idToken.getPayload();
System.out.println("User ID: " + payload.getSubject());
} else {
throw new NotAuthorizedException("Invalid token.");
}
}
}
To bind the filter to your endpoints methods or classes, annotate them with the #Secured annotation created above. For the methods and/or classes which are annotated, the filter will be executed.
#Path("/example")
public class MyEndpoint {
#GET
#Path("{id}")
#Produces("application/json")
public Response myUnsecuredMethod(#PathParam("id") Long id) {
// This method is not annotated with #Secured
// The security 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 security filter will be executed before invoking this method
...
}
}
In the example above, the security filter will be executed only for mySecuredMethod(Long) because it's annotated with #Secured.
I believe I have basic authentication working but I'm not sure how to protect resources so that they can only be accessed when the user is signed in.
public class SimpleAuthenticator implements Authenticator<BasicCredentials, User> {
UserDAO userDao;
public SimpleAuthenticator(UserDAO userDao) {this.userDao = userDao;}
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException
{
User user = this.userDao.getUserByName(credentials.getUsername());
if (user!=null &&
user.getName().equalsIgnoreCase(credentials.getUsername()) &&
BCrypt.checkpw(credentials.getPassword(), user.getPwhash())) {
return Optional.of(new User(credentials.getUsername()));
}
return Optional.absent();
}
}
My Signin resource is like this:
#Path("/myapp")
#Produces(MediaType.APPLICATION_JSON)
public class UserResource {
#GET
#Path("/signin")
public User signin(#Auth User user) {
return user;
}
}
And I sign the user with:
~/java/myservice $ curl -u "someuser" http://localhost:8080/myapp/signin
Enter host password for user 'someuser':
{"name":"someuser"}
Question
Let's say the user signs in from a browser or native mobile app front end using the /myapp/signin endpoint. How then can I protect another endpoint, say, /myapp/{username}/getstuff which requires a user to be signedin
#GET
#Path("/myapp/{username}/getstuff")
public Stuff getStuff(#PathParam("username") String username) {
//some logic here
return new Stuff();
}
There are 2 things when you are trying to implement REST. One is Authentication (which seems that you have got it working) and other is Authorization (which is what I believe your question is).
The way I have handled it in dropwizard before is, with every user signin, you return some kind of access_token (this proves they authenticated) back to the client which has to be returned by them in EVERY successive call they make as a part of some header (normally this is done through "Authorization" header). On the server side, you will have to save/map this access_token to THAT user before returning it back to the client and when all the successive calls are made with that access_token, you look up the user mapped with that access_token and determine if that user is authorized to access that resource or not. Now an example:
1) User signs in with /myapp/signin
2) You authenticate the user and send back an access_token as a response while saving the same on your side, such as, access_token --> userIdABCD
3) The client comes back to /myapp/{username}/getstuff. If the client does not provided the "Authorization" header with the access_token you gave them, you should return 401 Unauthorized code right away.
4) If the client does provide the access_token, you can look up the user based on that access_token you saved in step # 2 and check if that userId has access to that resource of not. If it does not, return 401 unauthorized code, and if it does have access, return the actual data back.
Now coming ot the "Authorization" header part. You could get access to "Authoroziation" header in all of your calls using the "#Context HttpServletRequest hsr" parameter but does it make sense to add that parameter in each call? No it doesn't. This is where the Security Filters help in dropwizard. Here's an example to how to add security filter.
public class SecurityFilter extends OncePerRequestFilter{
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException{
String accessToken = request.getHeader("Authorization");
// Do stuff here based on the access token (check for user's authorization to the resource ...
}
Now, which resource does this security filter really protects? For that you will need to add this filter to the specific resources you want to protect which can be done as follows:
environment.addFilter(SecurityFilter, "/myapp/*");
Remember on thing here that both your urls /myapp/signin and /myapp/{username}/getstuff, both will go through this security filter, BUT, /myapp/signin will NOT have an access_token, obviously because you haven't given any to the client yet. That wil have to be taken care of in the filter itself such as:
String url = request.getRequestURL().toString();
if(url.endsWith("signin"))
{
// Don't look for authorization header, and let the filter pass without any checks
}
else
{
// DO YOUR NORMAL AUTHORIZATION RELATED STUFF HERE
}
The url that you are protecting will depend on the how your urls are structured and what you want to protect. The better urls you design, the easier it will be to write security filters for their protection With the addition of this security filter the flow will be like this:
1) User goes to /myapp/signin. The call will go through the filter and because of that "if" statement, it will continue to your ACTUAL resource of /myapp/signin and you will assign an access_token based on successful authentication
2) User makes a call to /myapp/{username}/mystuff with the access_token. This call will go through the same security filter and will go through the "else" statement where you actually do your authorization. If the authorization goes through, the call will continue to you actual resource handler, and if not authorized, 401 should be returned.
public class SecurityFilter extends OncePerRequestFilter
{
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
{
String url = request.getRequestURL().toString();
String accessToken = request.getHeader("Authorization");
try
{
if (accessToken == null || accessToken.isEmpty())
{
throw new Exception(Status.UNAUTHORIZED.getStatusCode(), "Provided access token is either null or empty or does not have permissions to access this resource." + accessToken);
}
if (url.endsWith("/signin"))
{
//Don't Do anything
filterChain.doFilter(request, response);
}
else
{
//AUTHORIZE the access_token here. If authorization goes through, continue as normal, OR throw a 401 unaurhtorized exception
filterChain.doFilter(request, response);
}
}
catch (Exception ex)
{
response.setStatus(401);
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON);
response.getWriter().print("Unauthorized");
}
}
}
I hope this helps! Took me about 2 days to figure this out myself!
Sorry for being a simple user . I believe you can protect the resource by using a #Auth User user
public Service1Bean Service1Method1(
#Auth User user,
#QueryParam("name") com.google.common.base.Optional<String> name) {
I am using JBoss AS and JAX-RS for creating REST endpoints.
Lets say my class looks like
#Path("/users")
public class UserResource {
#GET
public Response getAccount() {
return "hello";
}
}
Now getAccount is not authenticated at the moment
Wanted
- I would like to add authentication so that when code hits getAccount the user is authenticated
- I would like the authentication to be driven by annotations instead of XML configurations, if at all possible
- I would like to do the database comparison to see if the user is valid
Problem
- I have never done that so I have no idea how to implement it
- I have googled around a lot and found Jersey examples
UPDATE
- I would like to send authentication credentials with each request and not creating any session
Please guide me with one simple working example and I would try to extend from there
You need is a Stateless Spring Security configuration in front of your JAX RS end points.
I have addressed exact problem you are trying to solve but I don't have my own code to share..
Here is one project which has done the exact thing you are asking, Some wise man has done it all for you ;)
https://github.com/philipsorst/angular-rest-springsecurity
What is the magic ?
You have one unprotected URL which does the Authentication, and set the user roles as well..
Then you return some kind of Token, put it some where in cache which will be expected on every subsequent call..
Upon new request on other protected resources, you will check if the Token is present in your cache/session store ( you need some mechanism to keep track of valid tokens )
If token is resent and valid, you do the programmatic Log-in in Spring Security which ensures that you can use all the Security features spring provides, ( Annotations, JSTL Tags etc.. ) !
Once passed token validation you will get the logged in user details in your controllers ( aka JAX RS resources ) to deal with security further..
If the token was not valid or not present , it would be trapped by failure end point which would return appropriate response ( 401 )
Refer Following Link To Understand How Stateless Spring Security is configured..,
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/resources/context.xml
See how a user is validated for the first time and a token is generated..
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/java/net/dontdrinkandroot/example/angularrestspringsecurity/rest/resources/UserResource.java
Here is the class where programmatic login is performed on every request after token
check..
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/java/net/dontdrinkandroot/example/angularrestspringsecurity/rest/AuthenticationTokenProcessingFilter.java
I solved this with following code.
note Token mechanism will be updated once I do that
I have solved this by modifying the interceptor I have, the following is code
Annotation
#Inherited
#InterceptorBinding
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface SecurityChecked {
}
Resource Class
public class SecureResource {
#GET
#SecurityChecked
public Response getUser() {
return Response.ok("authenticated successfully!").build();
}
}
Interceptor class
#Interceptor
#Provider
#ServerInterceptor
#SecurityChecked
public class SecurityCheckInterceptor implements PreProcessInterceptor, AcceptedByMethod {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityCheckInterceptor.class);
#Nullable
#Override
public ServerResponse preProcess(final HttpRequest request, final ResourceMethod method) throws Failure, WebApplicationException {
final List<String> authToken = request.getHttpHeaders().getRequestHeader("X-AUTH");
if (authToken == null || !isValidToken(authToken.get(0))) {
final ServerResponse serverResponse = new ServerResponse();
serverResponse.setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
return serverResponse;
}
return null;
}
private static boolean isValidToken(#Nonnull final String authToken) {
LOGGER.info("validating token: " + authToken);
return true;
}
#SuppressWarnings("rawtypes")
#Override
public boolean accept(final Class declaring, final Method method) {
// return declaring.isAnnotationPresent(SecurityChecked.class); // if annotation on class
return method.isAnnotationPresent(SecurityChecked.class);
}
}
and then I run my Integration tests by deploying the resource class in JBoss and issuing following commands on command-line
curl --header 'X-AUTH: 1a629d035831feadOOO4uFReLyEW8aTmrCS' http://localhost:8080/market-1.0-SNAPSHOT/rest/login
curl --header 'InvalidHeader: InvalidHeaderValue' http://localhost:8080/market-1.0-SNAPSHOT/rest/login
I am developing REST services with two types.
before login no session token will be passed to HTTP header.
after login session token will be passed in each request.
I dont want to include #HeaderParam in each and every REST method. I want to intercept it first and based on that I want to check the validity of session. Please let me know
how I can intercept based on headers in RESTEasy
How to avoid intercepting few methods
Thanks.
I solved this problem using PreProcessInterceptor
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Securable {
String header() default "session-token";
}
#Provider
#ServerInterceptor
public class ValidationInterceptor implements PreProcessInterceptor, AcceptedByMethod {
#Context
private HttpServletRequest servletRequest;
#Override
public boolean accept(Class clazz, Method method) {
return method.isAnnotationPresent(Securable.class);
}
#Override
public ServerResponse preProcess(HttpRequest httpRequest, ResourceMethod resourceMethod) throws Failure,
WebApplicationException {
Securable securable = resourceMethod.getMethod().getAnnotation(Securable.class);
String headerValue = servletRequest.getHeader(securable.header());
if (headerValue == null){
return (ServerResponse)Response.status(Status.BAD_REQUEST).entity("Invalid Session").build();
}else{
// Validatation logic goes here
}
return null;
}
}
The annotation #Securable will be used on REST service which needs to be validated.
#Securable
#PUT
public Response updateUser(User user)
There are two approaches
Use JAX-RS interceptors - you have access to request object in the interceptor, so you can read headers
Use good old JavaServlet Filters - it is not a problem that you are using JAX-RS, you can filter REST requests as well. Similarly to interceptors, filters have access to request object, which has header information
In both cases you can check if HttpSession exists (request.getSession() method) and it has required attribute.
You can include/exclude requests filtered either in configuration or programatically in Java code, looking at request path.