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 databaseusersStorage.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;@Overridepublic 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();}}@Overridepublic Response demoGetMethod() {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "Executed demoGetMethod" );JsonObject jsonObj = jsonObjBuilder.build();return getNoCacheResponseBuilder( Response.Status.OK ).entity( jsonObj.toString() ).build();}@Overridepublic Response demoPostMethod() {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "Executed demoPostMethod" );JsonObject jsonObj = jsonObjBuilder.build();return getNoCacheResponseBuilder( Response.Status.ACCEPTED ).entity( jsonObj.toString() ).build();}@Overridepublic 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() );@Overridepublic 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() );@Overridepublic 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 keyrequestCtx.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build() );return;}// For any pther methods besides login, the authToken must be verifiedif ( !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插件完成了此操作,结果如下:

rest-auth-test.html的结果

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请求和响应拦截器中都应注意这一点。

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

相关文章:

  • Java EE 7 / JAX-RS 2.0 – REST上的CORS(如何使REST API从其他域访问)
  • http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
  • http://www.html5rocks.com/zh-CN/tutorials/cors/
  • http://www.w3.org/TR/cors/
  • https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/361556.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

easyui树形菜单实现

需求&#xff1a;读取路径配置中的相对路径获取对应的子文件夹及其子文件并形成树形结构&#xff0c;加载xml文件&#xff0c;输入搜索关键字匹配xml里面的value节点的值对应的contact值的集合并进行搜索 例如&#xff1a;输入b&#xff0c;找到xml里面的文本节点等于b的value…

fatal error C1083: 无法打开预编译头文件:“Debug\a.pch”:No such file or directory

一、解决方法 右键点击你创建的项目&#xff0c;选择“属性标签”点击属性&#xff0c;弹出“项目属性页”&#xff0c;在左侧找到以下位置 配置属性 --> C/C --> 预编译头&#xff0c;并选择它&#xff1a;在右边的菜单中选择 “创建/使用预编译头”中的“不使用预编…

Telnet初试(本地测试)

win7下开启Telnet功能&#xff1a; 控制面板-程序和功能- 开启服务 然后回车 这样即可完成一次请求 更多专业前端知识&#xff0c;请上 【猿2048】www.mk2048.com

您是否真的要加快Maven的编译/打包速度? 那么takari生命周期插件就是答案。

像你们中的许多人一样&#xff0c;我正在使用多模块Maven项目 。 与现有的许多系统相比&#xff0c;这不是一个很大的数目&#xff0c;它具有15个模块&#xff0c;3种不同的耳朵部署&#xff0c;带有属性文件的大量参数化以及大约10万行Java代码。 在开发高峰期&#xff0c;由于…

手机MMI体系结构及其实现

摘自&#xff1a;http://blog.csdn.net/zc2007/article/details/2340436 1引言 MMI&#xff08;ManMachineInter-face&#xff09;&#xff0c;即人机界面&#xff0c;它负责和用户的交互&#xff0c;在必要的时候调用其它模块的功能。MMI模块在整个系统中处于最 高层&#x…

Aspose.Words简单生成word文档

Aspose.Words简单生成word文档 Aspose.Words.Document doc new Aspose.Words.Document(); Aspose.Words.DocumentBuilder builder new Aspose.W…

ubuntu下安装JDK和netbeans

我在ubuntu下安装netbeans十分简单&#xff0c;我下载了jdk-7u1-nb-7_0_1-linux-ml.sh&#xff0c;直接在终端输入 sh jdk-7u1-nb-7_0_1-linux-ml.sh安装的向导就会启动&#xff0c;你只要选择JDK和netbeans安装的目录&#xff0c;向导就自动替你安装jdk和netbeans&#xff0c;…

在带有组合框的值列表的下拉列表中显示显示属性的子集

组合框值列表&#xff08;inputComboboxListOfValues&#xff09;应该是使用LOV的非常流行的ADF Faces组件。 坦白说&#xff0c;这是我最喜欢的值列表方法。 在这篇简短的文章中&#xff0c;我将重点介绍ADF开发人员经常忽略的一项功能。 如果默认情况下定义了LOV&#xff0c;…

具有WildFly,Arquillian,Jenkins和OpenShift的Java EE 7部署管道

技术提示&#xff03;54展示了如何Arquillianate&#xff08;Arquillianize&#xff1f;&#xff09;一个现有的Java EE项目并在WildFly在已知主机和端口上运行的远程模式下运行这些测试。 技术提示&#xff03;55展示了当WildFly在OpenShift中运行时如何运行这些测试。 这两个…

css浮动(float)及清除浮动的几种实用方法

CSS浮动是现在网页布局中使用最频繁的效果之一,而浮动可以帮我们解决很多问题,那么就让我们一起来看一看如何使用浮动. 一.css浮动(float) (1)html文档流 自窗体自上而下分成一行一行&#xff0c;并在每行中按从左到右的顺序排放元素。 (2)网页中大部分对象默认是占用文档流…

一台电脑同时添加git和bitbucket两个网站的ssh key

添加第一个ssh key 就不多说了&#xff0c;不懂的可以自己查资料 ssh-keygen -t rsa -C email_1email.com 然后一路enter就好了 假设已经添加好了git的ssh key 。现在要添加bitbucket的ssh key 首先 ssh-keygen -t rsa -C email_2email.com //同一个邮箱也可以 然后指定公钥的…

属性提取器:获取ListView即时更新其元素的最佳方法

这篇文章是关于如何处理JavaFX ListViews和TableViews的&#xff0c;以及这些控件如何得知所包含元素的更改内容。 我想知道为什么在相关书籍中没有找到关于以下模式的任何信息&#xff0c;因为这是一个非常关键的机制。 那里的许多帖子建议通过调用以下命令来强制触发ChangeEv…

MVC详解

模型&#xff0d;视图&#xff0d;控制器&#xff08;Modal View Controler&#xff0c;MVC&#xff09;是Xerox PARC在八十年代为编程语言Smalltalk&#xff0d;80发明的一种软件设计模式&#xff0c;至今已被广泛使用。最近几年被推荐为Sun公司J2EE平台的设计模式&#xff0c…

ES6之命令妙用

很多人都听说过ES6&#xff08;也就是ECMAScript的新一代标准&#xff09;并且对她充满了向往&#xff0c;下面通过一个ES6中小知识点——let命令&#xff0c;来解开她的神秘面纱&#xff0c;让大家初步认识一下ES6的语法规范。let命令属于ES6中的一个基本语法&#xff0c;与原…

VUE-搜索过滤器

先看看效果 首先引入 <script src"https://cdn.jsdelivr.net/npm/vue"></script> HTML部分 <div id"app"><input v-modelsearch /><ul v-if"searchData.length > 0"><li v-for"item in searchData&quo…

使用spring-session外部化Spring-boot应用程序的会话状态

Spring-session是一个非常酷的新项目&#xff0c;旨在提供一种更简单的方法来管理基于Java的Web应用程序中的会话。 我最近在spring-session中探索的功能之一是它支持外部化会话状态的方式&#xff0c;而无需费心诸如Tomcat或Jetty之类的特定Web容器的内部。 为了测试spring-s…

使用纯HTML和OmniFaces构建动态响应的多级菜单

最近&#xff0c;我不得不使用JSF 2.2创建一个响应式多级菜单。 要求&#xff1a;菜单应&#xff1a; 从后端使用动态结构创建 反应灵敏&#xff0c;例如对桌面和移动设备友好 有带有导航链接的子菜单项 支持触摸事件 支持键盘辅助功能 PrimeFaces的菜单不是一个选择。 实…

Membership学习(三)Membership Providers介绍[xgluxv]

本来想在第三篇文章里介绍一下 Membership的类的&#xff0c;不过现在中文msdn也出来了&#xff0c;所以就不写了&#xff0c;&#xff0c;直接到介绍Membership Providers。 Membership Providers提供了Membership数据源和服务之间的所有接口&#xff0c;在Asp.net2.0中…

Servlet技术

----Servlet是用Java语言编写的应用到Web服务器端的扩展技术&#xff0c;它先于jsp产生&#xff0c;可以方便地对Web应用中的HTTP请求进行处理。在Java Web程序开发中&#xff0c;Servlet主要用于处理各种业务逻辑&#xff0c;他比jsp更具有业务逻辑层的意义。 一。Servlet基础…

文件指针创建失败!File *fp失败

问题记录&#xff1a;项目是在所里边的vs2010上创建的&#xff0c;正常&#xff01;在自己的笔记本上使用vs13和19都一直报错&#xff01;指针一直为空&#xff01; 网上查了好久&#xff0c;试了很多方法都没有用&#xff01;最后发现是权限的问题&#xff0c;c盘下边创建不了…