Java EE 7 / JAX-RS 2.0: Simple REST API Authentication & Authorization with Custom HTTP Header--reference

REST has made a lot of conveniences when it comes to implementing web services with the already available HTTP protocol at its disposal. By just firing GET, POST and other HTTP methods through the designated URL, you’ll sure to get something done through a response out of a REST service. But whatever conveniences which REST has given to the developers, the subject of security and access control should always be addressed. This article will show you how to implement simple user based authentication with the use of HTTP Headers and JAX-RS 2.0 interceptors.

Authenticator

Let’s begin with an authenticator class. This DemoAuthenticator with the codes below provides the necessary methods for authenticating any users which is request access to the REST web service. Please read through the codes and the comments are there to guide the understanding.

Codes for DemoAuthenticator:

package com.developerscrappad.business;
 
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.security.GeneralSecurityException;
import javax.security.auth.login.LoginException;
 
public final class DemoAuthenticator {
 
    private static DemoAuthenticator authenticator = null;
 
    // A user storage which stores <username, password>
    private final Map<String, String> usersStorage = new HashMap();
 
    // A service key storage which stores <service_key, username>
    private final Map<String, String> serviceKeysStorage = new HashMap();
 
    // An authentication token storage which stores <service_key, auth_token>.
    private final Map<String, String> authorizationTokensStorage = new HashMap();
 
    private DemoAuthenticator() {
        // The usersStorage pretty much represents a user table in the database
        usersStorage.put( "username1", "passwordForUser1" );
        usersStorage.put( "username2", "passwordForUser2" );
        usersStorage.put( "username3", "passwordForUser3" );
 
        /**
         * Service keys are pre-generated by the system and is given to the
         * authorized client who wants to have access to the REST API. Here,
         * only username1 and username2 is given the REST service access with
         * their respective service keys.
         */
        serviceKeysStorage.put( "f80ebc87-ad5c-4b29-9366-5359768df5a1", "username1" );
        serviceKeysStorage.put( "3b91cab8-926f-49b6-ba00-920bcf934c2a", "username2" );
    }
 
    public static DemoAuthenticator getInstance() {
        if ( authenticator == null ) {
            authenticator = new DemoAuthenticator();
        }
 
        return authenticator;
    }
 
    public String login( String serviceKey, String username, String password ) throws LoginException {
        if ( serviceKeysStorage.containsKey( serviceKey ) ) {
            String usernameMatch = serviceKeysStorage.get( serviceKey );
 
            if ( usernameMatch.equals( username ) && usersStorage.containsKey( username ) ) {
                String passwordMatch = usersStorage.get( username );
 
                if ( passwordMatch.equals( password ) ) {
 
                    /**
                     * Once all params are matched, the authToken will be
                     * generated and will be stored in the
                     * authorizationTokensStorage. The authToken will be needed
                     * for every REST API invocation and is only valid within
                     * the login session
                     */
                    String authToken = UUID.randomUUID().toString();
                    authorizationTokensStorage.put( authToken, username );
 
                    return authToken;
                }
            }
        }
 
        throw new LoginException( "Don‘t Come Here Again!" );
    }
 
    /**
     * The method that pre-validates if the client which invokes the REST API is
     * from a authorized and authenticated source.
     *
     * @param serviceKey The service key
     * @param authToken The authorization token generated after login
     * @return TRUE for acceptance and FALSE for denied.
     */
    public boolean isAuthTokenValid( String serviceKey, String authToken ) {
        if ( isServiceKeyValid( serviceKey ) ) {
            String usernameMatch1 = serviceKeysStorage.get( serviceKey );
 
            if ( authorizationTokensStorage.containsKey( authToken ) ) {
                String usernameMatch2 = authorizationTokensStorage.get( authToken );
 
                if ( usernameMatch1.equals( usernameMatch2 ) ) {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    /**
     * This method checks is the service key is valid
     *
     * @param serviceKey
     * @return TRUE if service key matches the pre-generated ones in service key
     * storage. FALSE for otherwise.
     */
    public boolean isServiceKeyValid( String serviceKey ) {
        return serviceKeysStorage.containsKey( serviceKey );
    }
 
    public void logout( String serviceKey, String authToken ) throws GeneralSecurityException {
        if ( serviceKeysStorage.containsKey( serviceKey ) ) {
            String usernameMatch1 = serviceKeysStorage.get( serviceKey );
 
            if ( authorizationTokensStorage.containsKey( authToken ) ) {
                String usernameMatch2 = authorizationTokensStorage.get( authToken );
 
                if ( usernameMatch1.equals( usernameMatch2 ) ) {
 
                    /**
                     * When a client logs out, the authentication token will be
                     * remove and will be made invalid.
                     */
                    authorizationTokensStorage.remove( authToken );
                    return;
                }
            }
        }
 
        throw new GeneralSecurityException( "Invalid service key and authorization token match." );
    }
}

General Code Explanation:
Generally, there are only a few important items that makes up the authenticator and that that is: service keyauthorization tokenusername and password. The username and password will commonly go in pairs.

Service Key
The service key may be new to some readers; in some public REST API service, a service key and sometimes known as API key, is generated by the system and then sends to the user/client (either through email or other means) that is permitted to access the REST service. So besides login into the REST service with just mere username and password, the system will also check on the service key if the user/client is permitted to access the REST APIs. The usernames, passwords and service keys are all predefined in the codes above for now only demo purpose.

Authorization Token
Upon authentication (through the login() method), the system will then generate an authorization token for the authenticated user. This token is passed back to the user/client through HTTP response and is to be used for any REST API invocation later. The user/client will have to find a way to store and use it throughout the login session. We’ll look at that later.

Required HTTP Headers Name Definition

Moving forward, instead of having the service key and authorization token to be passed to the server-side app as HTTP parameters (Form or Query), we’ll have it pass as HTTP Headers. This is to allow the request to be first filtered before being processed by the targeted REST method. The names for the HTTP Headers are below:

HTTP Header Name Description
service_key The service key that enables a HTTP client to access the REST Web Services. This is the first layer of authenticating and authorizing the HTTP Request.
auth_token The token generated upon username/password authentication, which is to be used for any REST Web Service calls (except for the authentication method shown later).

REST API Implementation

For convenience and further code error reduction, let’s put the HTTP Header names into an interface as static final variables for the use in the rest of the classes.

Codes for DemoHTTPHeaderNames.java:

package com.developerscrappad.intf;
 
public interface DemoHTTPHeaderNames {
 
    public static final String SERVICE_KEY = "service_key";
    public static final String AUTH_TOKEN = "auth_token";
}

For the implementation of the authentication process and other demo methods, the methods’ signature are defined in DemoBusinessRESTResourceProxy, along with the appropriate HTTP Methods, parameters and the business implementation is defined in DemoBusinessRESTResource.

Codes for DemoBusinessRESTResourceProxy.java:

package com.developerscrappad.intf;
 
import java.io.Serializable;
import javax.ejb.Local;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
 
@Local
@Path( "demo-business-resource" )
public interface DemoBusinessRESTResourceProxy extends Serializable {
 
    @POST
    @Path( "login" )
    @Produces( MediaType.APPLICATION_JSON )
    public Response login(
        @Context HttpHeaders httpHeaders,
        @FormParam( "username" ) String username,
        @FormParam( "password" ) String password );
 
    @GET
    @Path( "demo-get-method" )
    @Produces( MediaType.APPLICATION_JSON )
    public Response demoGetMethod();
 
    @POST
    @Path( "demo-post-method" )
    @Produces( MediaType.APPLICATION_JSON )
    public Response demoPostMethod();
 
    @POST
    @Path( "logout" )
    public Response logout(
        @Context HttpHeaders httpHeaders
    );
}

Codes for DemoBusinessRESTResource.java:

package com.developerscrappad.business;
 
import com.developerscrappad.intf.DemoBusinessRESTResourceProxy;
import com.developerscrappad.intf.DemoHTTPHeaderNames;
import java.security.GeneralSecurityException;
import javax.ejb.Stateless;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.security.auth.login.LoginException;
import javax.ws.rs.FormParam;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
 
@Stateless( name = "DemoBusinessRESTResource", mappedName = "ejb/DemoBusinessRESTResource" )
public class DemoBusinessRESTResource implements DemoBusinessRESTResourceProxy {
 
    private static final long serialVersionUID = -6663599014192066936L;
 
    @Override
    public Response login(
        @Context HttpHeaders httpHeaders,
        @FormParam( "username" ) String username,
        @FormParam( "password" ) String password ) {
 
        DemoAuthenticator demoAuthenticator = DemoAuthenticator.getInstance();
        String serviceKey = httpHeaders.getHeaderString( DemoHTTPHeaderNames.SERVICE_KEY );
 
        try {
            String authToken = demoAuthenticator.login( serviceKey, username, password );
 
            JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();
            jsonObjBuilder.add( "auth_token", authToken );
            JsonObject jsonObj = jsonObjBuilder.build();
 
            return getNoCacheResponseBuilder( Response.Status.OK ).entity( jsonObj.toString() ).build();
 
        } catch ( final LoginException ex ) {
            JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();
            jsonObjBuilder.add( "message", "Problem matching service key, username and password" );
            JsonObject jsonObj = jsonObjBuilder.build();
 
            return getNoCacheResponseBuilder( Response.Status.UNAUTHORIZED ).entity( jsonObj.toString() ).build();
        }
    }
 
    @Override
    public Response demoGetMethod() {
        JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();
        jsonObjBuilder.add( "message", "Executed demoGetMethod" );
        JsonObject jsonObj = jsonObjBuilder.build();
 
        return getNoCacheResponseBuilder( Response.Status.OK ).entity( jsonObj.toString() ).build();
    }
 
    @Override
    public Response demoPostMethod() {
        JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();
        jsonObjBuilder.add( "message", "Executed demoPostMethod" );
        JsonObject jsonObj = jsonObjBuilder.build();
 
        return getNoCacheResponseBuilder( Response.Status.ACCEPTED ).entity( jsonObj.toString() ).build();
    }
 
    @Override
    public Response logout(
        @Context HttpHeaders httpHeaders ) {
        try {
            DemoAuthenticator demoAuthenticator = DemoAuthenticator.getInstance();
            String serviceKey = httpHeaders.getHeaderString( DemoHTTPHeaderNames.SERVICE_KEY );
            String authToken = httpHeaders.getHeaderString( DemoHTTPHeaderNames.AUTH_TOKEN );
 
            demoAuthenticator.logout( serviceKey, authToken );
 
            return getNoCacheResponseBuilder( Response.Status.NO_CONTENT ).build();
        } catch ( final GeneralSecurityException ex ) {
            return getNoCacheResponseBuilder( Response.Status.INTERNAL_SERVER_ERROR ).build();
        }
    }
 
    private Response.ResponseBuilder getNoCacheResponseBuilder( Response.Status status ) {
        CacheControl cc = new CacheControl();
        cc.setNoCache( true );
        cc.setMaxAge( -1 );
        cc.setMustRevalidate( true );
 
        return Response.status( status ).cacheControl( cc );
    }
}

The login() method is to authenticate the username, the password and also the right service key. After login(), the authorization token will be generated and returned to the client. The client will have to use it for any other methods invocation later on. The demoGetMethod() and the demoPostMethod() are just dummy methods which returns a JSON message for demo purpose, but with a special condition that a valid authorization token must be present. The logout() method is to log the user out of the REST service; user is identified by the “auth_token“.

The service key and the authorization token will be made available to the REST service methods through:

@Context HttpHeaders httpHeaders

The httpHeaders, an instance of javax.ws.rs.core.HttpHeaders, is an object that contains the header name and values for the use of the application further on. But in order to get the REST service to accept the HTTP Header, something needs to be done first through both the REST request interceptor and the response interceptor.

Authentication With HTTP Headers Through JAX-RS 2.0 Interceptors

Due to certain security limitation, just don’t hope that any HTTP headers could be passed using any REST client and expect the REST service to accept it. It just doesn’t work that way.

In order to make a specific header to be accepted in the REST service, we have to define the acceptance of HTTP Header very specifically in the response filter interceptor.

Codes for DemoRESTResponseFilter.java:

package com.developerscrappad.interceptors;
 
import com.developerscrappad.intf.DemoHTTPHeaderNames;
import java.io.IOException;
import java.util.logging.Logger;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.ext.Provider;
 
@Provider
@PreMatching
public class DemoRESTResponseFilter implements ContainerResponseFilter {
 
    private final static Logger log = Logger.getLogger( DemoRESTResponseFilter.class.getName() );
 
    @Override
    public void filter( ContainerRequestContext requestCtx, ContainerResponseContext responseCtx ) throws IOException {
 
        log.info( "Filtering REST Response" );
 
        responseCtx.getHeaders().add( "Access-Control-Allow-Origin", "*" );    // You may further limit certain client IPs with Access-Control-Allow-Origin instead of ‘*‘
        responseCtx.getHeaders().add( "Access-Control-Allow-Credentials", "true" );
        responseCtx.getHeaders().add( "Access-Control-Allow-Methods", "GET, POST, DELETE, PUT" );
        responseCtx.getHeaders().add( "Access-Control-Allow-Headers", DemoHTTPHeaderNames.SERVICE_KEY + ", " + DemoHTTPHeaderNames.AUTH_TOKEN );
    }
}

DemoRESTResponseFilter is a JAX-RS 2.0 interceptor which implements ContainerResponseFilter. Don’t forget to annotate it with both @Provide and @PreMatching. In order to allow certain specific custom HTTP headers to be accepted, the header name “Access-Control-Allow-Headers” follow by the value of custom headers with “,” as the separator must be added as part of the custom headers value. This is the way to inform the browser or REST client of the custom headers allowed. The rest of the headers are for CORS, which you can read more in one of our articles Java EE 7 / JAX-RS 2.0 – CORS on REST (How to make REST APIs accessible from a different domain)

Next, to validate and verify the service key and authorization token, we need to extract it out from the HTTP Headers and pre-process it with the request filter interceptor.

Codes for DemoRESTRequestFilter:

package com.developerscrappad.interceptors;
 
import com.developerscrappad.business.DemoAuthenticator;
import com.developerscrappad.intf.DemoHTTPHeaderNames;
import java.io.IOException;
import java.util.logging.Logger;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
 
@Provider
@PreMatching
public class DemoRESTRequestFilter implements ContainerRequestFilter {
 
    private final static Logger log = Logger.getLogger( DemoRESTRequestFilter.class.getName() );
 
    @Override
    public void filter( ContainerRequestContext requestCtx ) throws IOException {
 
        String path = requestCtx.getUriInfo().getPath();
        log.info( "Filtering request path: " + path );
 
        // IMPORTANT!!! First, Acknowledge any pre-flight test from browsers for this case before validating the headers (CORS stuff)
        if ( requestCtx.getRequest().getMethod().equals( "OPTIONS" ) ) {
            requestCtx.abortWith( Response.status( Response.Status.OK ).build() );
 
            return;
        }
 
        // Then check is the service key exists and is valid.
        DemoAuthenticator demoAuthenticator = DemoAuthenticator.getInstance();
        String serviceKey = requestCtx.getHeaderString( DemoHTTPHeaderNames.SERVICE_KEY );
 
        if ( !demoAuthenticator.isServiceKeyValid( serviceKey ) ) {
            // Kick anyone without a valid service key
            requestCtx.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build() );
 
            return;
        }
 
        // For any pther methods besides login, the authToken must be verified
        if ( !path.startsWith( "/demo-business-resource/login/" ) ) {
            String authToken = requestCtx.getHeaderString( DemoHTTPHeaderNames.AUTH_TOKEN );
 
            // if it isn‘t valid, just kick them out.
            if ( !demoAuthenticator.isAuthTokenValid( serviceKey, authToken ) ) {
                requestCtx.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build() );
            }
        }
    }
}

To get the header value, we invoke the getHeaderString() method of the object instance of ContainerRequestContext, for example:

String serviceKey = requestCtx.getHeaderString( "service_key" );

The rest of the codes in DemoRESTRequestFilter is pretty straight forward on validating and verifying the service key and the authorization token.

REST Service Deployment

Don’t forget to have the web.xml for the enablement of REST service define.

Codes for web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
 
    <!-- Common JAX-RS Servlet Definition -->
    <servlet>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/rest-api/*</url-pattern>
    </servlet-mapping>
 
</web-app>

For this demo, I have packaged the compiled codes into a war file naming it RESTSecurityWithHTTPHeaderDemo.war. I have chosen to deploy on Glassfish 4.0 on the domain developerscrappad.com (the domain of this blog). If you are going through everything in this tutorial, you may choose a different domain of your own. The REST API URLs will be in the format of:

http://<domain>:<port>/RESTSecurityWithHTTPHeaderDemo/rest-api/path/method-path/

. Anyway, the summary of the URLs for the test client which I’m using are:

Method REST URL HTTP Method
DemoBusinessRESTResourceProxy.login() http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/login/ POST
DemoBusinessRESTResourceProxy.demoGetMethod() http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-get-method/ GET
DemoBusinessRESTResourceProxy.demoPostMethod() http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-post-method/ POST
DemoBusinessRESTResourceProxy.logout() http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/logout/ POST

THE REST Client

Putting it altogether, here’s a REST client which I’ve wrote to test the REST APIs. The REST Client is just a HTML file (specifically HTML5, which supports web storage) that leverages jQuery for REST API calls. What the REST Client does is as follow:

  1. First, the REST Client will make a REST API call without service key and authorization token. The call will be rejected with HTTP Status 401 (Unauthorized)
  2. Next, it will perform a login with the specific service key (hard coded for now in the Authenticator.java) for “username2″. Once the authorisation token had been received, it will be stored in the sessionStorage for further use.
  3. Then, it will call the dummy get and post methods.
  4. After that, it will pereform a logout
  5. Once the user is logged-out, the client will then perform a call to to the dummy get and post method, but the access will be denied with HTTP Status 401 due to the expiration of the authorization token.

Codes for rest-auth-test.html:

<html>
    <head>
        <title>REST Authentication Tester</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <div id="logMsgDiv"></div>
 
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
        <script type="text/javascript">
            var $ = jQuery.noConflict();
 
            // Disable async
            $.ajaxSetup( { async: false } );
 
            // Using Service Key 3b91cab8-926f-49b6-ba00-920bcf934c2a and username2
 
            // This is what happens when there you call the REST APIs without a service key and authorisation token
            $.ajax( {
                cache: false,
                crossDomain: true,
                url: "http://www.developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-post-method/",
                type: "POST",
                success: function( jsonObj, textStatus, xhr ) {
                    var htmlContent = $( "#logMsgDiv" ).html( ) + "<p style=‘color: red;‘>If this is portion is executed, something must be wrong</p>";
                    $( "#logMsgDiv" ).html( htmlContent );
                },
                error: function( xhr, textStatus, errorThrown ) {
                    var htmlContent = $( "#logMsgDiv" ).html( )
                            + "<p style=‘color: red;‘>This is what happens when there you call the REST APIs without a service key and authorisation token."
                            + "<br />HTTP Status: " + xhr.status + ", Unauthorized access to demo-post-method</p>";
 
                    $( "#logMsgDiv" ).html( htmlContent );
                }
            } );
 
            // Performing login with username2 and passwordForUser2
            $.ajax( {
                cache: false,
                crossDomain: true,
                headers: {
                    "service_key": "3b91cab8-926f-49b6-ba00-920bcf934c2a"
                },
                dataType: "json",
                url: "http://www.developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/login/",
                type: "POST",
                data: {
                    "username": "username2",
                    "password": "passwordForUser2"
                },
                success: function( jsonObj, textStatus, xhr ) {
                    sessionStorage.auth_token = jsonObj.auth_token;
 
                    var htmlContent = $( "#logMsgDiv" ).html( ) + "<p>Perform Login. Gotten auth-token as: " + sessionStorage.auth_token + "</p>";
                    $( "#logMsgDiv" ).html( htmlContent );
                },
                error: function( xhr, textStatus, errorThrown ) {
                    console.log( "HTTP Status: " + xhr.status );
                    console.log( "Error textStatus: " + textStatus );
                    console.log( "Error thrown: " + errorThrown );
                }
            } );
 
            // After login, execute demoteGetMethod with the auth-token obtained
            $.ajax( {
                cache: false,
                crossDomain: true,
                headers: {
                    "service_key": "3b91cab8-926f-49b6-ba00-920bcf934c2a",
                    "auth_token": sessionStorage.auth_token
                },
                dataType: "json",
                url: "http://www.developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-get-method/",
                type: "GET",
                success: function( jsonObj, textStatus, xhr ) {
                    var htmlContent = $( "#logMsgDiv" ).html( ) + "<p>After login, execute demoteGetMethod with the auth-token obtained. JSON Message: " + jsonObj.message + "</p>";
                    $( "#logMsgDiv" ).html( htmlContent );
                },
                error: function( xhr, textStatus, errorThrown ) {
                    console.log( "HTTP Status: " + xhr.status );
                    console.log( "Error textStatus: " + textStatus );
                    console.log( "Error thrown: " + errorThrown );
                }
            } );
 
            // Execute demoPostMethod with the auth-token obtained
            $.ajax( {
                cache: false,
                crossDomain: true,
                headers: {
                    "service_key": "3b91cab8-926f-49b6-ba00-920bcf934c2a",
                    "auth_token": sessionStorage.auth_token
                },
                dataType: "json",
                url: "http://www.developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-post-method/",
                type: "POST",
                success: function( jsonObj, textStatus, xhr ) {
                    var htmlContent = $( "#logMsgDiv" ).html( ) + "<p>Execute demoPostMethod with the auth-token obtained. JSON message: " + jsonObj.message + "</p>";
                    $( "#logMsgDiv" ).html( htmlContent );
                },
                error: function( xhr, textStatus, errorThrown ) {
                    console.log( "HTTP Status: " + xhr.status );
                    console.log( "Error textStatus: " + textStatus );
                    console.log( "Error thrown: " + errorThrown );
                }
            } );
 
            // Let‘s logout after all the above. No content expected
            $.ajax( {
                cache: false,
                crossDomain: true,
                headers: {
                    "service_key": "3b91cab8-926f-49b6-ba00-920bcf934c2a",
                    "auth_token": sessionStorage.auth_token
                },
                url: "http://www.developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/logout/",
                type: "POST",
                success: function( jsonObj, textStatus, xhr ) {
                    var htmlContent = $( "#logMsgDiv" ).html( ) + "<p>Let‘s logout after all the above. No content expected.</p>";
                    $( "#logMsgDiv" ).html( htmlContent );
                },
                error: function( xhr, textStatus, errorThrown ) {
                    console.log( "HTTP Status: " + xhr.status );
                    console.log( "Error textStatus: " + textStatus );
                    console.log( "Error thrown: " + errorThrown );
                }
            } );
 
            // This is what happens when someone reuses the authorisation token after a user had been logged out
            $.ajax( {
                cache: false,
                crossDomain: true,
                headers: {
                    "service_key": "3b91cab8-926f-49b6-ba00-920bcf934c2a",
                    "auth_token": sessionStorage.auth_token
                },
                url: "http://www.developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-get-method/",
                type: "GET",
                success: function( jsonObj, textStatus, xhr ) {
                    var htmlContent = $( "#logMsgDiv" ).html( ) + "<p style=‘color: red;‘>If this is portion is executed, something must be wrong</p>";
                    $( "#logMsgDiv" ).html( htmlContent );
                },
                error: function( xhr, textStatus, errorThrown ) {
                    var htmlContent = $( "#logMsgDiv" ).html( )
                            + "<p style=‘color: red;‘>This is what happens when someone reuses the authorisation token after a user had been logged out"
                            + "<br />HTTP Status: " + xhr.status + ", Unauthorized access to demo-get-method</p>";
 
                    $( "#logMsgDiv" ).html( htmlContent );
                }
            } );
        </script>
    </body>
</html>

The Result

The rest-auth-test.html need not be packaged with the war file, this is to separate invoking client script from the server-side app to simulate a cross-origin request. To run the rest-auth-test.html, all you need to do is to execute it from a web browser. For me, I have done this through Firefox with the Firebug plugin, and the below is the result:

Result of rest-auth-test.html

It worked pretty well. The first and the last request will be rejected as 401 (Unauthorized) HTTP Status because it was executed before authentication and after logout (invalid auth_token).

Final Words

When it comes to dealing with custom HTTP Headers in a JAX-RS 2.0 application, just remember to have the the custom HTTP Header names to be included as part of “Access-Control-Allow-Headers” in the response filter, e.g.

Access-Control-Allow-Headers: custom_header_name1, custom_header_name2

After that, getting the HTTP Headers could be done easily in the REST Web Service methods with the help of javax.ws.rs.core.HttpHeaders through the REST context. Don’t forget the restriction and impact for CORS, which should be taken care in both REST Request and Response interceptors.

Thank you for reading and hope this article helps.

reference from: http://www.developerscrappad.com/1814/java/java-ee/rest-jax-rs/java-ee-7-jax-rs-2-0-simple-rest-api-authentication-authorization-with-custom-http-header/#sthash.AIH5zIyN.dpuf

时间: 2024-12-28 01:30:41

Java EE 7 / JAX-RS 2.0: Simple REST API Authentication & Authorization with Custom HTTP Header--reference的相关文章

java ee spec

Web profile[edit] In an attempt to limit the footprint of web containers, both in physical and in conceptual terms, the web profile was created, a subset of the Java EE specifications. The Java EE web profile comprises the following: Specification Ja

Java EE—最轻量级的企业框架?

确保高效发展进程的建议 很久以前,J2EE,特别是应用程序服务器被认为过于臃肿和"重量级".对于开发人员来说,使用此技术开发应用程序会非常繁琐且令人沮丧.但是,由于 J2EE 框架的名称已更改为Java EE,因此该假设不再适用. Java EE 与其他企业框架相比区别在哪以及框架轻量级的标准是什么? 在选择技术时,需要考虑的最重要方面之一是开发人员在开发过程中的生产力.工程师应该花费尽可能多的时间来实现用例和创收功能,因为这将使公司朝着目标前进. 所选择的技术和方法应该最大限度地缩短

Java EE的13种核心技术

一.内容简介 Java EE的13种核心技术:JDBC.JNDI.EJB.RMI.JSP.Java Servlet.XML.JMS.Java IDL.JTS.JTA.JavaMail和JAF. Java最初在浏览器和客户端机器中粉墨登场,当时很多人质疑它是否适合做服务器端的开发.现在随着对Java EE第三方支持的增多,Java被广泛接纳为开发企业级服务器端解决方案的首选平台之一. Java EE平台由一整套服务(Services).应用程序接口(APIs)和协议构成,它对开发基于Web的多层应

eclipse部署项目时:Tomcat version 6.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5 Web modules

布署项目的时候出现Tomcat version 6.0 only supports J2EE 1.2, 1.3, 1.4,and Java EE 5 Web modules错误;解决方法如下: 在工作空间项目下的.settings下面,有个名为org.eclipse.wst.common.project.facet.core.xml的文件,里面配置有各种版本信息: <?xml version="1.0" encoding="UTF-8"?> <fa

eclise配置tomcat出现服务Tomcat version 6.0 only supports J2EE 1.2, 1.3, 1.4 and Java EE 5 Web modules

当部署项目Tomcat version 6.0 only supports J2EE 1.2, 1.3, 1.4, 1.5 and Java EE 5 Web modules错;解决方案,如下面: 空间项目下的.settings以下,有个名为org.eclipse.wst.common.project.facet.core.xml的文件.里面配置有各种版本号信息: <?xml version="1.0" encoding="UTF-8"?> <fa

Tomcat6.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5 Web modules

使用tomcat6发布项目的时候,报以上错误,servlet版本太高的原因.tomcat6不支持servlet3.0 解决方法有两个: 1.使用高版本的tomcat,如tomcat7.tomcat8 2.修改配置,将项目的servlet版本改成2.5 2.1 找到项目的org.eclipse.wst.common.project.facet.core.xml    2.2 将jst.web的版本改成2.5 Tomcat6.0 only supports J2EE 1.2, 1.3, 1.4, a

Tomcat version 6.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5 Web modules ???报错!!!

tomcat在加载 项目是报错 Tomcat version 6.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5 Web modules 报错 原因就是:需要用到tomcat7.0以上的tomcat服务!!!! 原文地址:https://www.cnblogs.com/healer777/p/12110153.html

Java EE 学习总结

1.Java EE WEB 工程项目文件结构 组成:静态HTML页.Servlet.JSP和其他相关的class: 每个组件在WEB应用中都有固定的存放目录. WEB应用的配置信息存放在web.xml文件中. 每发布一个组件都必须在web.xml文件中添加相应的配置信息Java Web 应用程序必须使用规范的目录结构. 应用程序根目录,可以取任意的名字,所有的HTML.JSP文件都放在这个目录下    1.1 WEB-INF目录: 必须目录         1.1.1 web.xml: Web应

Java EE开发平台随手记1

过完春节以来,一直在负责搭建公司的新Java EE开发平台,所谓新平台,其实并不是什么新技术,不过是将目前业界较为流行的框架整合在一起,做一些简单的封装和扩展,让开发人员更加易用. 和之前负责具体的项目开发不同,不能只是功能实现就可以,还需要考虑更多的非功能性需求,比如性能.安全性.易用性.可维护性.易扩展性.兼容性等等,因此有很多在实际项目中觉得方便易用的功能不得不因种种原因而舍弃:另一方面,也常常会偶尔有一些新想法.新构想,但因缺乏实践论证,也往往没有加入进来:此外,对于平台中已经添加的那些