Статьи

Java EE 7 / JAX-RS 2.0: простая аутентификация и авторизация API REST с настраиваемым заголовком HTTP

REST имеет много удобств, когда речь идет о реализации веб-сервисов с уже имеющимся протоколом HTTP. Просто запустив GET, POST и другие методы HTTP через назначенный URL-адрес, вы наверняка получите что-то с помощью ответа из службы REST. Но каковы бы ни были удобства, которые REST предоставил разработчикам, тема безопасности и контроля доступа всегда должна решаться. Эта статья покажет вам, как реализовать простую аутентификацию на основе пользователя с использованием заголовков HTTP и перехватчиков JAX-RS 2.0.

Authenticator

Давайте начнем с класса аутентификатора. Этот DemoAuthenticator с кодами ниже предоставляет необходимые методы для аутентификации любых пользователей, которые запрашивают доступ к веб-службе REST. Пожалуйста, ознакомьтесь с кодами и комментариями для понимания.

Коды для DemoAuthenticator:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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." );
    }
}

Общее объяснение кода:

Как правило, есть только несколько важных элементов, из которых состоит аутентификатор: сервисный ключ , токен авторизации , имя пользователя и пароль . Имя пользователя и пароль обычно идут парами.

Сервисный ключ

Сервисный ключ может быть новым для некоторых читателей; в некоторой общедоступной службе API REST служебный ключ, иногда называемый ключом API, генерируется системой и затем отправляет пользователю / клиенту (по электронной почте или иным способом), которому разрешен доступ к службе REST. Таким образом, помимо входа в службу REST только с помощью имени пользователя и пароля, система также проверит ключ службы, если пользователю / клиенту разрешен доступ к API REST. Имена пользователей, пароли и сервисные ключи предварительно определены в приведенных выше кодах для демонстрационных целей.

Токен авторизации

После аутентификации (через метод login ()) система сгенерирует токен авторизации для аутентифицированного пользователя. Этот токен передается обратно пользователю / клиенту по HTTP-ответу и должен использоваться для любого вызова REST API позже. Пользователь / клиент должен будет найти способ сохранить и использовать его в течение сеанса входа в систему. Мы рассмотрим это позже.

Обязательное определение имени заголовка HTTP

В дальнейшем, вместо того, чтобы служебный ключ и токен авторизации передавались в серверное приложение в качестве параметров HTTP (форма или запрос), мы будем передавать его как заголовки HTTP. Это делается для того, чтобы запрос был сначала отфильтрован перед обработкой целевым методом REST. Имена для заголовков HTTP приведены ниже:

Имя заголовка HTTP Описание
ключ_обслуживани Сервисный ключ, который позволяет HTTP-клиенту получать доступ к веб-службам REST. Это первый уровень аутентификации и авторизации HTTP-запроса.
auth_token Токен, сгенерированный при аутентификации по имени пользователя / паролю, который должен использоваться для любых вызовов веб-службы REST (за исключением метода аутентификации, показанного ниже).

Реализация API REST

Для удобства и дальнейшего уменьшения ошибок кода давайте поместим имена заголовков HTTP в интерфейс в качестве статических конечных переменных для использования в остальных классах.

Коды для DemoHTTPHeaderNames.java:

1
2
3
4
5
6
7
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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 () предназначен для аутентификации имени пользователя, пароля, а также правильного служебного ключа. После входа в систему () токен авторизации будет сгенерирован и возвращен клиенту. Клиент должен будет использовать его для любых других вызовов методов позже. DemoGetMethod () и demoPostMethod () являются просто фиктивными методами, которые возвращают сообщение JSON для демонстрационной цели, но с особым условием, что должен присутствовать действительный токен авторизации. Метод logout () предназначен для выхода пользователя из службы REST; пользователь идентифицируется по « auth_token ».

Служебный ключ и токен авторизации будут доступны для методов обслуживания REST посредством:

1
@Context HttpHeaders httpHeaders

HttpHeaders, экземпляр javax.ws.rs.core.HttpHeaders , является объектом, который содержит имя заголовка и значения для дальнейшего использования приложения. Но чтобы служба REST могла принимать заголовок HTTP, сначала нужно что-то сделать как с помощью перехватчика запросов REST, так и перехватчика ответов.

Аутентификация с заголовками HTTP через перехватчики JAX-RS 2.0

Из-за определенных ограничений безопасности просто не надейтесь, что любые HTTP-заголовки могут быть переданы с использованием любого клиента REST, и ожидайте, что служба REST примет это. Просто так не работает.

Чтобы сделать определенный заголовок, который будет принят в службе REST, мы должны очень точно определить принятие заголовка HTTP в перехватчике фильтра ответа.

Коды для DemoRESTResponseFilter.java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 — это перехватчик JAX-RS 2.0, который реализует ContainerResponseFilter . Не забудьте аннотировать это как @Provide, так и @PreMatching. Чтобы разрешить прием определенных определенных пользовательских заголовков HTTP, за именем заголовка « Access-Control-Allow-Headers » следует значение пользовательских заголовков с «,», поскольку разделитель должен быть добавлен как часть значения пользовательских заголовков. Это способ информирования браузера или клиента REST о допустимых пользовательских заголовках. Остальные заголовки предназначены для CORS, о ​​чем вы можете прочитать в одной из наших статей Java EE 7 / JAX-RS 2.0 — CORS на REST (Как сделать REST API доступными из другого домена) .

Затем, чтобы проверить и проверить сервисный ключ и токен авторизации, нам нужно извлечь его из заголовков HTTP и предварительно обработать с помощью перехватчика фильтра запросов.

Коды для DemoRESTRequestFilter:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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() );
            }
        }
    }
}

Чтобы получить значение заголовка, мы вызываем метод getHeaderString () экземпляра объекта ContainerRequestContext, например:

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

Остальные коды в DemoRESTRequestFilter довольно просты при проверке и проверке служебного ключа и токена авторизации.

Развертывание службы REST

Не забудьте указать web.xml для включения службы REST.

Коды для web.xml:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<!--?xml version="1.0" encoding="UTF-8"?-->
  
     
    <servlet>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/rest-api/*</url-pattern>
    </servlet-mapping>
  
</web-app>

Для этой демонстрации я упаковал скомпилированные коды в военный файл с именем RESTSecurityWithHTTPHeaderDemo.war . Я выбрал развертывание на Glassfish 4.0 на домене developerscrappad.com (домен этого блога). Если вы изучаете все в этом руководстве, вы можете выбрать другой собственный домен. URL-адреса REST API будут иметь формат:

1
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, который я написал для тестирования API REST. Клиент REST — это просто HTML-файл (в частности, HTML5, который поддерживает веб-хранилище), который использует jQuery для вызовов REST API. Клиент REST выполняет следующие действия:

  1. Сначала клиент REST выполняет вызов API REST без служебного ключа и токена авторизации. Вызов будет отклонен с HTTP Status 401 (неавторизовано)
  2. Затем он выполнит вход в систему с определенным служебным ключом (жестко заданным на данный момент в Authenticator.java) для «username2». После получения токена авторизации он будет сохранен в sessionStorage для дальнейшего использования.
  3. Затем он вызовет фиктивные методы get и post.
  4. После этого будет выполнен выход из системы.
  5. Как только пользователь вышел из системы, клиент затем выполнит вызов фиктивного метода get и post, но в доступе будет отказано с HTTP Status 401 из-за истечения срока действия токена авторизации.

Коды для rest-auth-test.html:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<html>
    <head>
        <title>REST Authentication Tester</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <div id="logMsgDiv"></div>
  
        <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,
                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",
                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",
                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",
                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
                },
                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
                },
                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, все, что вам нужно сделать, это запустить его из веб-браузера. Для меня я сделал это через Firefox с плагином Firebug, и результат ниже:

Результат rest-auth-test.html

Результат rest-auth-test.html

Это сработало довольно хорошо. Первый и последний запрос будут отклонены как 401 (неавторизованный) статус HTTP, потому что он был выполнен до аутентификации и после выхода из системы (недопустимый auth_token ).

Заключительные слова

Когда дело доходит до работы с настраиваемыми заголовками HTTP в приложении JAX-RS 2.0, просто не забывайте включать настраиваемые имена заголовков HTTP как часть « Access-Control-Allow-Headers » в фильтр ответов, например:

1
Access-Control-Allow-Headers: custom_header_name1, custom_header_name2

После этого получение заголовков HTTP может быть легко выполнено в методах веб-службы REST с помощью javax.ws.rs.core.HttpHeaders через контекст REST. Не забывайте об ограничениях и последствиях для CORS, о ​​которых следует позаботиться как в перехватчиках запросов REST, так и в ответах.

Спасибо за чтение и надеюсь, что эта статья поможет.

Статьи по Теме: