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

input发送a.jax

在使用已可用的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. 用户注销后,客户端将执行对虚拟get and post方法的调用,但是由于授权令牌的到期,访问将被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-Header ”的一部分包含在内即可。

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

input发送a.jax

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

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

相关文章

C语言的main函数,究竟有几种写法?

从学习C语言开始就一直写个一个函数&#xff0c;那么你知道它的标准写法什么什么样吗&#xff1f;main函数&#xff0c;又称主函数&#xff0c;是程序执行的起点&#xff0c;我们平时写的main函数是什么样呢&#xff1f;所以说&#xff0c;其他的写法并不符合标准&#xff0c;有…

Linux C语言结构体

前面学习了c语言的基本语法特性&#xff0c;本节进行更深入的学习。预处理程序。 编译指令: 预处理, 宏定义&#xff0c;建立自己的数据类型&#xff1a;结构体&#xff0c;联合体&#xff0c;动态数据结构c语言表达式工具 逻辑运算符&#xff1a; & | ^ ~ << >&g…

Linux C语言编程基本原理与实践

重识C语言C语言是一种通用的, 面向过程的编程语言, 在系统与应用软件的开发应用较广是人类和计算机交流的一种方式ANSI C&#xff1a; 是C语言的标准, 为了避免各开发商用的C语言语法的差异C语言的特点: 简单, 快速, 高性能, 兼容性好, 功能强大, 易于学习C语言适合做什么Linux…

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

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

30分钟了解C 11新特性

什么是C 11C 11是曾经被叫做C 0x&#xff0c;是对目前C 语言的扩展和修正&#xff0c;C 11不仅包含核心语言的新机能&#xff0c;而且扩展了C 的标准程序库&#xff08;STL&#xff09;&#xff0c;并入了大部分的C Technical Report 1&#xff08;TR1&#xff09;程序库(数学的…

外链分享已取消无法下载怎么办_微信升级外链规范:“砍一刀”、“帮我加速吧”将被封禁...

北京商报讯(记者魏蔚)朋友圈、微信群遍布的购物分享链接&#xff0c;再度受到微信严控。10月18日晚间&#xff0c;微信宣布&#xff0c;外链规范即将进行更新升级&#xff0c;新增和细化多项外链规则&#xff0c;包括不可违规使用用户头像&#xff1b;不可诱导、误导下载/跳转&…

ef ddl生成不了脚本_如何使用Hibernate从Play生成DDL脚本! 框架项目

ef ddl生成不了脚本好的&#xff0c;因此您一直在使用hibernate属性名称“ hibernate.hbm2ddl.auto ” value “ 更新 ”来不断更新数据库模式&#xff0c; 但是现在您需要一个完整的DDL脚本吗&#xff1f; 从您的Global Class onStart中使用此方法来导出DDL脚本。 只需为其提…

vue php企业站案例,vue 开发企业微信整合案例分析

本文实例讲述了vue 开发企业微信整合。分享给大家供大家参考&#xff0c;具体如下&#xff1a;概述手机端程序可以和企业微信进行整合&#xff0c;我们也可以使用企业微信JSSDK功能&#xff0c;实现一些原生的功能。整合步骤在整合之前需要阅读 整合步骤。1.引入JSSDKnpm i -S …

Invalid Gradle JDK configuration found_带你了解Gradle编译速度是如何提升70%的

前言Gradle作为一款基于Groovy语言的构建工具&#xff0c;已经吸引众多的ant&#xff0c;maven使用者转投gradle的怀抱&#xff0c;和Gradle相比&#xff0c;ant显得冗余复杂&#xff0c;maven显得有些死板落后&#xff0c;而gradle基于DSL语法&#xff0c;特点明显&#xff1a…

【游戏开发】C 游戏编程实例

网络游戏开发分为&#xff1a;服务器编程、客户端编程、人工智能、数据库管理、游戏策划、美工设计、音乐特效等。大型游戏往往需要团队合作开发&#xff0c;因此面向对象的编程思想在网络游戏中得到了广泛应用。游戏开发基本流程&#xff1a;游戏初始化——游戏实现——游戏结…

linux内核3.14.4,Linux内核4.14.14,4.9.77,4.4.112和3.18.92更新发布

原标题&#xff1a;Linux内核4.14.14&#xff0c;4.9.77&#xff0c;4.4.112和3.18.92更新发布导读正如所承诺的&#xff0c;Linux内核维护者Greg Kroah-Hartman今天发布了针对长期支持的Linux 4.14,4.9,4.4和3.18内核系列的一系列新更新。这些新内核在他们之前发布的一个星期后…

springboot 获取登录浏览器_java项目部署到linux服务器,微信小程序后台springboot项目部署到云服务器(图文详解)...

前面给大家讲了一个点餐系统的开发&#xff0c;包括java点餐后台和微信点餐小程序。可是都是教大家如何在本地把项目跑起来。今天就来教大家如何把这个点餐系统部署到服务器&#xff0c;实现商用。传送门点餐系统的开发&#xff0c;java后台微信小程序&#xff1a;https://blog…

spring体系结构_了解Spring Web应用程序体系结构:经典方法

spring体系结构每个开发人员必须了解两件事&#xff1a; 架构设计是必要的。 花哨的体系结构图没有描述应用程序的真实体系结构。 真正的体系结构是从开发人员编写的代码中找到的&#xff0c;如果不设计应用程序的体系结构&#xff0c;最终将得到一个具有多个体系结构的应用…

C 网络库都干了什么?

虽然市面上已经有很多成熟的网络库&#xff0c;但是编写一个自己的网络库依然让我获益匪浅&#xff0c;这篇文章主要包含&#xff1a;TCP 网络库都干了些什么&#xff1f;编写时需要注意哪些问题&#xff1f;CppNet 是如何解决的。首先&#xff0c;大家都知道操作系统原生的soc…

iphone屏幕录制_iPhone怎么内录声音?怎么录制苹果手机内部声音?

有时我们想要对苹果手机上播放的声音进行录音&#xff0c;却不知道该如何操作。苹果手机上自带的录音软件只可以对手机外部声音进行录制&#xff0c;却无法录制自身播放的声音。其实我们可以先将苹果手机屏幕及声音先投放到电脑上&#xff0c;再通过支持内录的软件进行录音就可…

C 中命名空间的五大常见用法

译者注&#xff1a;可能很多程序员对C 已经非常熟悉&#xff0c;但是对命名空间经常使用到的地方还不是很明白&#xff0c;这篇文章就针对命名空间这一块做了一个叙述。命名空间在1995年被引入到 c 标准中&#xff0c;通常是这样定义的:命名空间定义了新的作用域。它们提供了一…

英伟达TX2烧录系统_英伟达的DPU,是想在数据中心奇袭英特尔?

热点追踪 / 深度探讨 / 实地探访 / 商务合作最近几年&#xff0c;经常关注科技圈的朋友们总会发现&#xff0c;每次遇到厂商有重大发布&#xff0c;就总能看到“颠覆”、“极致”、“革命性”等概念出现在发布会上。上周&#xff0c;iPhone12的发布现场&#xff0c;蒂姆库克就用…

C vector详解

【导读】&#xff1a;vector是一个封装了动态大小数组的顺序容器&#xff08;Sequence Container&#xff09;。跟任意其它类型容器一样&#xff0c;它能够存放各种类型的对象。可以简单的认为&#xff0c;vector是一个能够存放任意类型的动态数组。接下来&#xff0c;请跟随小…

arcgis 出图背景_ArcGIS空间制图分析视频教程(二狮兄出品)含ArcMap

这套教程是二狮兄出的一套ArcGIS地理空间制图数据分析视频教程&#xff0c;含ArcMap/ArcCatalog部分。教程分为上中下三部&#xff0c;已全部录制完毕&#xff0c;全部课程120节。适用人群ArcGIS目前的应用范围非常广泛&#xff0c;包括但不限于从事地理景观、生态环境、规划设…

C 之父:C 的成功属于意料之外,C 11是转折点

C 的起源可以追溯到 40 年前&#xff0c;但它仍然是当今使用最广泛的编程语言之一。到 2020 年 9 月为止&#xff0c;C 是仅次于 C 语言、Java 和 Python&#xff0c;位于全球第四的编程语言。根据最新的 TIOBE 索引&#xff0c;C 也是增长最快的语言。近日&#xff0c;C 之父 …