Java EE 7 / JAX-RS 2.0:具有自定义HTTP标头的简单REST API身份验证和授权

在使用已可用的HTTP协议实施Web服务时,REST带来了很多便利。 通过仅通过指定的URL触发GET,POST和其他HTTP方法,您将确保通过REST服务的响应来完成某些工作。 但是,无论REST给开发人员带来了什么便利,安全性和访问控制的主题都应始终得到解决。 本文将向您展示如何使用HTTP标头和JAX-RS 2.0拦截器来实现基于用户的简单身份验证。



认证者

让我们从一个Authenticator类开始。 具有以下代码的DemoAuthenticator提供了必要的方法,用于对请求访问REST Web服务的所有用户进行身份验证。 请通读代码,并在此处提供注释以指导理解。

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." );
    }
}

通用代码说明:

通常,只有几个重要的项目组成了身份验证器,即: 服务**授权令牌用户名密码 用户名和密码通常成对使用。

服务**

服务**对于某些读者而言可能是新的。 在某些公共REST API服务中,系统会生成服务**(有时也称为API**),然后将其发送到允许访问REST服务的用户/客户端(通过电子邮件或其他方式)。 因此,除了仅使用用户名和密码登录REST服务外,系统还将检查服务**,是否允许用户/客户端访问REST API。 用户名,密码和服务**均已在上面的代码中预定义,仅用于演示目的。

授权令牌

进行身份验证后(通过login()方法),系统将为已身份验证的用户生成授权令牌。 该令牌通过HTTP响应传递回用户/客户端,以后将用于任何REST API调用。 用户/客户端将必须找到一种方法来在整个登录会话中进行存储和使用。 我们稍后再讨论。

必需的HTTP标头名称定义

向前发展,而不是将服务**和授权令牌作为HTTP参数(Form或Query)传递到服务器端应用程序,我们将其作为HTTP Header传递。 这是为了使请求在被目标REST方法处理之前先被过滤。 HTTP标头的名称如下:

HTTP标头名称 描述
service_key 使HTTP客户端能够访问REST Web服务的服务**。 这是认证和授权HTTP请求的第一层。
auth_token 用户名/密码认证时生成的令牌,将用于任何REST Web Service调用(稍后显示的认证方法除外)。

REST API实施

为了方便和进一步减少代码错误,让我们将HTTP标头名称作为静态最终变量放入接口中,以供其余类使用。

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";
}

为了实现身份验证过程和其他演示方法,在DemoBusinessRESTResourceProxy中定义了方法的签名,在适当的HTTP方法,参数中定义了业务签名,并在DemoBusinessRESTResource中定义了业务实现。

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
    );
}

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 );
    }
}

login()方法用于验证用户名,密码以及正确的服务**。 login()之后 ,将生成授权令牌并将其返回给客户端。 客户端稍后将不得不将其用于任何其他方法调用。 demoGetMethod()demoPostMethod()只是伪方法,它们出于演示目的返回JSON消息,但有一个特殊条件,即必须存在有效​​的授权令牌。 logout()方法用于使用户退出REST服务; 用户由“ auth_token ”标识。

服务**和授权令牌将通过以下方式提供给REST服务方法:

@Context HttpHeaders httpHeaders

httpHeaders是javax.ws.rs.core.HttpHeaders的一个实例,是一个包含标题名称和值的对象,供以后使用该应用程序。 但是为了使REST服务接受HTTP标头,首先需要通过REST请求拦截器和响应拦截器来完成某些操作。

通过JAX-RS 2.0拦截器使用HTTP标头进行身份验证

由于某些安全限制,只是不要希望可以使用任何REST客户端传递任何HTTP标头,并希望REST服务接受它。 就是那样行不通。

为了使特定的标头在REST服务中被接受,我们必须非常明确地在响应过滤器拦截器中定义HTTP标头的接受。

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是一个实现ContainerResponseFilter的JAX-RS 2.0拦截器。 不要忘记同时使用@Provide和@PreMatching对其进行注释。 为了允许某些特定的自定义HTTP标头被接受,标头名称“ Access-Control-Allow-Headers ”后跟带有“,”的标头值,因为分隔符必须作为自定义标头值的一部分添加。 这是通知浏览器或REST客户端允许的自定义标头的方法。 其余的标头用于CORS,您可以在我们的文章Java EE 7 / JAX-RS 2.0 – REST上的CORS(如何使REST API从其他域访问)中阅读更多

接下来,要验证和验证服务**和授权令牌,我们需要从HTTP标头中将其提取出来,并使用请求过滤器拦截器对其进行预处理。

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() );
            }
        }
    }
}

要获取标头值,我们调用ContainerRequestContext对象实例的getHeaderString()方法,例如:

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

DemoRESTRequestFilter中的其余代码在验证和验证服务**和授权令牌方面非常简单。

REST服务部署

不要忘记定义用于启用REST服务的web.xml。

web.xml的代码:


 
    
    javax.ws.rs.core.Application1javax.ws.rs.core.Application/rest-api/*
 

对于此演示,我将编译后的代码打包到一个名为RESTSecurityWithHTTPHeaderDemo.war的war文件中。 我选择在开发人员域crappad.com(此博客的域)上的Glassfish 4.0上进行部署。 如果您正在阅读本教程中的所有内容,则可以选择自己的其他域。 REST API URL将采用以下格式:

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

无论如何,我正在使用的测试客户端的URL摘要是:

方法 REST URL HTTP方法
DemoBusinessRESTResourceProxy.login() http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/login/ 开机自检
DemoBusinessRESTResourceProxy。
demoGetMethod()
http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-get-method/ 得到
DemoBusinessRESTResourceProxy。
demoPostMethod()
http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-post-method/ 开机自检
DemoBusinessRESTResourceProxy.logout() http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/logout/ 开机自检

REST客户端

总而言之,这是我编写的用于测试REST API的REST客户端。 REST客户端只是一个HTML文件(特别是HTML5,它支持Web存储),该文件利用jQuery进行REST API调用。 REST客户端的作用如下:

  1. 首先,REST客户端将在没有服务**和授权令牌的情况下进行REST API调用。 呼叫将被拒绝,HTTP状态为401(未授权)
  2. 接下来,它将使用“ username2”的特定服务**(目前在Authenticator.java中进行硬编码)执行登录。 收到授权令牌后,它将被存储在sessionStorage中以备将来使用。
  3. 然后,它将调用虚拟的get和post方法。
  4. 之后,它将执行注销
  5. 一旦用户注销,客户端将执行对虚拟获取和发布方法的调用,但是由于授权令牌的到期,访问将被HTTP状态401拒绝。

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>

结果

rest-auth-test.html不需要与war文件打包在一起,这是为了将调用客户端脚本与服务器端应用程序分开以模拟跨域请求。 要运行rest-auth-test.html,您需要做的就是从Web浏览器执行它。 对我来说,我已经通过Firefox使用Firebug插件完成了此操作,结果如下:

Java EE 7 / JAX-RS 2.0:具有自定义HTTP标头的简单REST API身份验证和授权

rest-auth-test.html的结果

效果很好。 第一个和最后一个请求将被拒绝为401(未经授权)HTTP状态,因为它是在身份验证之前和注销之后执行的(无效auth_token )。

最后的话

在JAX-RS 2.0应用程序中处理自定义HTTP标头时,只需记住在响应过滤器中将自定义HTTP标头名称作为“ Access-Control-Allow-Headers ”的一部分包含在内即可,例如

Access-Control-Allow-Headers: custom_header_name1, custom_header_name2

之后,可以通过REST上下文在javax.ws.rs.core.HttpHeaders的帮助下,在REST Web服务方法中轻松获得HTTP标头。 不要忘记对CORS的限制和影响,在REST请求和响应拦截器中都应注意这一点。

感谢您的阅读,希望本文对您有所帮助。

相关文章:

翻译自: https://www.javacodegeeks.com/2014/10/java-ee-7-jax-rs-2-0-simple-rest-api-authentication-authorization-with-custom-http-header.html