Java EE 7 / JAX-RS 2.0:Simpel REST API-godkendelse og godkendelse med brugerdefineret HTTP-header
REST har gjort en masse bekvemmeligheder, når det kommer til at implementere webtjenester med den allerede tilgængelige HTTP-protokol til sin rådighed. Ved blot at skyde GET, POST og andre HTTP-metoder gennem den udpegede URL, vil du sikker på at få noget gjort gennem et svar ud af en REST-tjeneste. Men uanset hvilke bekvemmeligheder REST har givet udviklerne, bør emnet sikkerhed og adgangskontrol altid behandles. Denne artikel viser dig, hvordan du implementerer simpel brugerbaseret godkendelse med brug af HTTP-headere og JAX-RS 2.0-interceptorer.
Authenticator
Lad os begynde med en godkendelsesklasse. Denne DemoAuthenticator med nedenstående koder giver de nødvendige metoder til at godkende enhver bruger, der anmoder om adgang til REST-webtjenesten. Læs venligst koderne igennem, og kommentarerne er til for at vejlede forståelsen.
Koder til 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." ); } }
Generel kodeforklaring:
Generelt er der kun nogle få vigtige elementer, der udgør godkendelsesværktøjet, og det er:servicenøgle , godkendelsestoken , brugernavn og adgangskode . Brugernavnet og adgangskoden vil normalt gå i par.
Servicenøgle
Tjenestenøglen kan være ny for nogle læsere; i nogle offentlige REST API-tjenester genereres en servicenøgle og nogle gange kendt som API-nøgle af systemet og sendes derefter til brugeren/klienten (enten via e-mail eller på anden måde), som har tilladelse til at få adgang til REST-tjenesten. Så udover at logge ind på REST-tjenesten med blot brugernavn og adgangskode, vil systemet også kontrollere tjenestenøglen, om brugeren/klienten har tilladelse til at få adgang til REST API'erne. Brugernavne, adgangskoder og servicenøgler er alle foruddefinerede i ovenstående koder for nu kun demoformål.
Godkendelsestoken
Efter godkendelse (via login()-metoden), genererer systemet et autorisationstoken for den godkendte bruger. Dette token sendes tilbage til brugeren/klienten gennem HTTP-svar og skal bruges til enhver REST API-indkaldelse senere. Brugeren/klienten skal finde en måde at gemme og bruge det under hele login-sessionen. Det ser vi på senere.
Påkrævet definition af HTTP-headersnavn
Fremadrettet, i stedet for at have servicenøglen og autorisationstokenet til at blive videregivet til server-side-appen som HTTP-parametre (formular eller forespørgsel), får vi det videregivet som HTTP-headere. Dette er for at tillade, at anmodningen først filtreres, før den behandles af den målrettede REST-metode. Navnene på HTTP-headerne er nedenfor:
HTTP-headernavn | Beskrivelse |
---|---|
service_key | Tjenestenøgle, der gør det muligt for en HTTP-klient at få adgang til REST Web Services. Dette er det første lag af godkendelse og godkendelse af HTTP-anmodningen. |
auth_token | Tokenet, der genereres ved brugernavn/adgangskodegodkendelse, som skal bruges til alle REST-webservicekald (undtagen godkendelsesmetoden vist senere). |
REST API-implementering
For nemheds skyld og yderligere reduktion af kodefejl, lad os sætte HTTP-headernavnene ind i en grænseflade som statiske endelige variabler til brug i resten af klasserne.
Koder til 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"; }
Til implementering af godkendelsesprocessen og andre demometoder er metodernes signatur defineret i DemoBusinessRESTResourceProxy sammen med de relevante HTTP-metoder, parametre, og forretningsimplementeringen er defineret i DemoBusinessRESTResource.
Koder til 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 ); }
Koder til 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() metoden er at autentificere brugernavnet, adgangskoden og også den rigtige servicenøgle. Efter login() , vil godkendelsestokenet blive genereret og returneret til klienten. Klienten bliver nødt til at bruge den til enhver anden metodepåkaldelse senere. demoGetMethod() og demoPostMethod() er blot dummy-metoder, som returnerer en JSON-meddelelse til demoformål, men med en særlig betingelse om, at et gyldigt autorisationstoken skal være til stede. logout() metoden er at logge brugeren ud af REST-tjenesten; brugeren identificeres med "auth_token “.
Servicenøglen og godkendelsestokenet vil blive gjort tilgængelige for REST-servicemetoderne gennem:
@Context HttpHeaders httpHeaders
httpHeaders, en forekomst af javax.ws.rs.core.HttpHeaders, er et objekt, der indeholder headernavnet og værdierne til brug af applikationen længere fremme. Men for at få REST-tjenesten til at acceptere HTTP-headeren, skal der først gøres noget gennem både REST-anmodningsinterceptoren og responseinterceptoren.
Godkendelse med HTTP-headere gennem JAX-RS 2.0-interceptorer
På grund af visse sikkerhedsbegrænsninger skal du bare ikke håbe, at nogen HTTP-headere kan sendes ved hjælp af enhver REST-klient, og forvent, at REST-tjenesten accepterer det. Sådan fungerer det bare ikke.
For at få en specifik header til at blive accepteret i REST-tjenesten, skal vi definere accepten af HTTP Header meget specifikt i responsfilterinterceptoren.
Koder til 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 er en JAX-RS 2.0 interceptor, som implementerer ContainerResponseFilter . Glem ikke at annotere det med både @Provide og @PreMatching. For at tillade, at visse specifikke brugerdefinerede HTTP-headere kan accepteres, skal headernavnet "Access-Control-Allow-Headers " efterfulgt af værdien af tilpassede overskrifter med "," da separatoren skal tilføjes som en del af værdien for tilpassede overskrifter. Dette er måden at informere browseren eller REST-klienten om de tilladte tilpassede overskrifter. Resten af overskrifterne er til CORS, som du kan læse mere i en af vores artikler Java EE 7 / JAX-RS 2.0 – CORS på REST (Sådan gør du REST API'er tilgængelige fra et andet domæne).
Dernæst, for at validere og verificere servicenøglen og autorisationstokenet, skal vi udtrække det fra HTTP-headerne og forbehandle det med anmodningsfilteropfangeren.
Koder til 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() ); } } } }
For at få headerværdien påberåber vi getHeaderString()-metoden for objektforekomsten af ContainerRequestContext, for eksempel:
String serviceKey = requestCtx.getHeaderString( "service_key" );
Resten af koderne i DemoRESTRequestFilter er ret ligetil med at validere og verificere servicenøglen og godkendelsestokenet.
REST-tjenesteimplementering
Glem ikke at få web.xml til at aktivere REST-tjenesten til at definere.
Koder til web.xml:
javax.ws.rs.core.Application 1 javax.ws.rs.core.Application /rest-api/*
Til denne demo har jeg pakket de kompilerede koder ind i en krigsfil, der navngiver den RESTSecurityWithHTTPHeaderDemo.war . Jeg har valgt at implementere på Glassfish 4.0 på domænet developerscrappad.com (domænet for denne blog). Hvis du gennemgår alt i denne vejledning, kan du vælge et andet domæne. REST API-URL'erne vil være i formatet:
http://<domain>:<port>/RESTSecurityWithHTTPHeaderDemo/rest-api/path/method-path/
Under alle omstændigheder er oversigten over URL'erne for testklienten, som jeg bruger:
Metode | REST URL | HTTP-metode |
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 |
RESTEN-klienten
For at sætte det hele, her er en REST-klient, som jeg har skrevet for at teste REST API'erne. REST-klienten er kun en HTML-fil (specifikt HTML5, som understøtter weblagring), der udnytter jQuery til REST API-kald. Hvad REST-klienten gør er som følger:
- For det første vil REST-klienten foretage et REST API-kald uden servicenøgle og autorisationstoken. Opkaldet vil blive afvist med HTTP Status 401 (Uautoriseret)
- Dernæst vil den udføre et login med den specifikke tjenestenøgle (hardkodet i øjeblikket i Authenticator.java) for "brugernavn2". Når autorisationstokenet er blevet modtaget, vil det blive gemt i sessionStorage til videre brug.
- Derefter kalder den dummy get- og post-metoderne.
- Derefter vil den udføre et logout
- Når brugeren er logget ud, vil klienten derefter udføre et opkald til dummy get and post-metoden, men adgangen vil blive nægtet med HTTP Status 401 på grund af udløbet af godkendelsestokenet.
Koder til 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>
Resultatet
rest-auth-test.html behøver ikke at være pakket med war-filen, dette er for at adskille påkaldende klientscript fra server-side-appen for at simulere en krydsoprindelsesanmodning. For at køre rest-auth-test.html, er alt hvad du skal gøre at køre det fra en webbrowser. For mig har jeg gjort dette gennem Firefox med Firebug plugin, og nedenstående er resultatet:
Resultat af rest-auth-test.html
Det fungerede ret godt. Den første og den sidste anmodning vil blive afvist som 401 (uautoriseret) HTTP-status, fordi den blev udført før godkendelse og efter logout (ugyldig auth_token ).
Afsluttende ord
Når det kommer til at håndtere tilpassede HTTP-headere i en JAX-RS 2.0-applikation, skal du bare huske at have de tilpassede HTTP-headernavne, der skal inkluderes som en del af "Access-Control-Allow-Headers ” i svarfilteret, f.eks.
Access-Control-Allow-Headers: custom_header_name1, custom_header_name2
Derefter kunne HTTP-headerne nemt hentes i REST Web Service-metoderne ved hjælp af javax.ws.rs.core.HttpHeaders gennem REST-konteksten. Glem ikke begrænsningen og indvirkningen for CORS, som skal passes i både REST Request og Response interceptorer.
Tak fordi du læste og håber denne artikel hjælper.
Relaterede artikler:
- Java EE 7 / JAX-RS 2.0 – CORS på REST (Sådan gør du REST API'er tilgængelige fra et andet domæne)
- http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
- http://www.html5rocks.com/en/tutorials/cors/
- http://www.w3.org/TR/cors/
- https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS