Quantcast
Channel: Planeta Código
Viewing all articles
Browse latest Browse all 2711

Blog Bitix: Servidor Oauth, gateway y servicio REST utilizando tokens JWT con Spring

$
0
0
Spring
Java

Hace unos días encontré un articulo del blog técnido de los desarrolladores de Idealista. En él comentaban que tenían una API para realizar simulaciones hipotecarias usando Spring como framework, Spring Security Oauth como forma de autenticación y autorización y JWT como forma de codificar el token que otorga el servidor Oauth y contiene la información necesaria para que el servidor de recursos permita o no el acceso al recurso que aloja.

Ya había oído mencionar JWT pero este artículo me ha permitido conocer su utilidad, y no es poca. Como se menciona en el artículo JWT tiene la ventaja de que que no es necesario persistirlo en una base de datos y contiene toda la información que el servidor de recursos necesita para realizar la autorización ya que es capaz de cargar con información arbitraria que el servicio desee en el momento de la emisión, la autenticación y comprobación de que ha sido emitido por el servidor Oauth la realiza sabiendo que el token está firmado.

Los tokens son una serie de caracteres aparentemente sin sentido al estar hasheados y firmados con una clave compartida entre servidor Oauth y el servidor de recurso o para mayor seguridad mediante clave privada en el servidor Oauth y su clave pública asociada en el servidor de recursos, con la firma el servidor de recursos el capaz de comprobar la autenticidad del token sin necesidad de comunicarse con él. Los tokens de Oauth son más cortos, los tokens JWT con más largos ya que contienen información adicional. Se componen de tres partes separadas por un punto, una cabecera con el algoritmo hash utilizado y tipo de token, un documento JSON con datos y una firma de verificación.

El hecho de que los tokens JWT no sea necesario persistirlos en base de datos elimina la necesidad de tener su infraestructura, como desventaja es que no es tan fácil de revocar el acceso a un token JWT y por ello se les concede un tiempo de expiración corto. En el articulo se analizaba su infraestructura y hay varios elementos configurables de diferentes formas, son:

  • El servidor Oauth que proporciona los tokens, realiza la autenticación y proporciona las autorizaciones.
  • El servidor del recurso al que se le envía el token, en base a las autorizaciones otorgadas por el servidor Oauth al token y las autorizaciones necesarias para acceder al recurso concedo o no acceso al recurso.
  • En el caso de múltiples servicios con múltiples recursos es conveniente un gateway para que sea el punto de entrada de todos los servicios, de esta forma los clientes solo necesitarán conocer el gateway en vez de los múltiples servicios individuales. El gateway se encarga de hacer de proxy en base a información en la petición como ruta, host, parámetros, cabeceras, … de redirigir la petición al servicio encargado de atenderla y devolver la respuesta. Un ejemplo de gateway es Zuul como ya he mostrado en el artículo Proxy para microservicios con Spring Cloud Netflix y Zuul.

Puede haber más elementos en la infraestructura y quizá sea el caso de un sistema real como sería un servidor de descubrimiento con Eureka o un servidor de configuración con Spring Cloud Config, en la serie de artículos sobre Spring Cloud los muestro. Para este ejemplo obvio estos otros servidores y me centro en los más relacionados con el artículo. Aunque lógicamente son diferentes servicios se puede crear uno que proporcione varios de ellos al mismo tiempo, por ejemplo, un servicio que haga al mismo tiempo de servidor de Oauth y de gateway que es una de las posibles cambios que dejan al final en el artículo de Idealista.

Spring ha creado su propio proyecto de gateway para sustituir a Zuul, Spring Cloud Gateway y será el que use en este artículo. Soporta Spring Boot 2, Spring Framework 5, coincidencia por cualquier parámetro de la petición, filtros y transformaciones o predicados, el patrón circuit breaker, limitación de peticiones y reescritura de rutas.

Los servicios los mantengo separados ya que al combinarlos pueden surgir problemas de integración al usar diferentes versiones de librerías de Spring aún cuando todos los proyectos son de Spring. Por ejemplo, Spring Cloud Gateway utiliza Spring WebFlux que puede ser diferente del lo que utilice Spring Security Oauth y la integración puede no estar exenta de problemas.

OauthJWT

Servidor Oauth

Empezando por el servidor Oauth y las dependencias que necesita, son spring-security-oauth2 y para generar tokens JWT spring-security-jwt, el resto son dependencias necesarias de Spring Boot

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
plugins{id'application'id'org.springframework.boot'version'2.0.8.RELEASE'}mainClassName='io.github.picodotdev.blogbitix.springoauth.oauth.Main'dependencies{implementationplatform('org.springframework.boot:spring-boot-dependencies:2.0.8.RELEASE')implementationplatform('org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2')defexcludeSpringBootStarterLogging={exclude(group:'org.springframework.boot',module:'spring-boot-starter-logging')}compile('org.springframework.boot:spring-boot-starter',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-web',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-security',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-log4j2',excludeSpringBootStarterLogging)compile('org.springframework.security.oauth:spring-security-oauth2:2.3.4.RELEASE',excludeSpringBootStarterLogging)compile('org.springframework.security:spring-security-jwt:1.0.10.RELEASE',excludeSpringBootStarterLogging)runtime('com.google.code.gson:gson:2.8.5')runtime('com.fasterxml.jackson.core:jackson-databind:2.9.6')runtime('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6')runtime('javax.xml.bind:jaxb-api:2.3.0')runtime('com.sun.xml.bind:jaxb-impl:2.3.0')runtime('org.glassfish.jaxb:jaxb-runtime:2.3.0')runtime('javax.activation:activation:1.1.1')}

La clase principal de Spring Boot y que inicia la aplicación no tiene nada especial salvo la necesaria anotación @EnableAuthorizationServer para habilitar el servidor Oauth.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
packageio.github.picodotdev.blogbitix.springoauth.oauth;...@SpringBootApplication@EnableAuthorizationServerpublicclassMain{publicstaticvoidmain(String[]args){SpringApplication.run(Main.class,args);}}

La parte importante está en la clase de configuración. La clase JwtAccessTokenConverter se encarga de codificar el token, la clase TokenStore de generarlos, DefaultTokenServices contiene referencias a ambos, los métodos heredados configure() configuran diferentes aspectos del servicio como los requisitos para acceder a los endpoint para ver el contenido de un token o los clientes Oauth que reconoce. Para cada cliente se necesita proporcionar el identificativo del cliente, su clave privada o secret, identificativo del recurso, que tipos de concesiones, grants, formas o flujos de obtener el token, que autoridades y ámbitos o scopes se le asigna al token.

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
packageio.github.picodotdev.blogbitix.springoauth.oauth;...@Configuration@EnableAuthorizationServerpublicclassAuthorizationServerConfigurationextendsAuthorizationServerConfigurerAdapter{@AutowiredprivateClientDetailsServiceclientDetailsService;@AutowiredprivateJwtAccessTokenConvertertokenConverter;@AutowiredprivateTokenStoretokenStore;@AutowiredprivateDefaultTokenServicestokenServices;@BeanpublicJwtAccessTokenConvertertokenConverter(){JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();converter.setSigningKey("1234567890");returnconverter;}@BeanpublicTokenStoretokenStore(JwtAccessTokenConvertertokenConverter){returnnewJwtTokenStore(tokenConverter);}@BeanDefaultTokenServicestokenServices(TokenStoretokenStore,JwtAccessTokenConvertertokenConverter){DefaultTokenServicestokenServices=newDefaultTokenServices();tokenServices.setTokenStore(tokenStore);tokenServices.setTokenEnhancer(tokenConverter);returntokenServices;}@BeanpublicTokenStoreUserApprovalHandleruserApprovalHandler(TokenStoretokenStore,ClientDetailsServiceclientDetailsService){TokenStoreUserApprovalHandlerhandler=newTokenStoreUserApprovalHandler();handler.setTokenStore(tokenStore);handler.setRequestFactory(newDefaultOAuth2RequestFactory(clientDetailsService));handler.setClientDetailsService(clientDetailsService);returnhandler;}@BeanpublicApprovalStoreapprovalStore(TokenStoretokenStore)throwsException{TokenApprovalStorestore=newTokenApprovalStore();store.setTokenStore(tokenStore);returnstore;}@Overridepublicvoidconfigure(AuthorizationServerSecurityConfigurersecurity)throwsException{security.allowFormAuthenticationForClients().tokenKeyAccess("isAuthenticated()").checkTokenAccess("isAuthenticated()");}@Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurerendpoints)throwsException{endpoints.tokenServices(tokenServices);}@Overridepublicvoidconfigure(ClientDetailsServiceConfigurerclients)throwsException{clients.inMemory().withClient("client").secret("{noop}1234567890").resourceIds("service").authorizedGrantTypes("client_credentials").authorities("CLIENT").scopes("read");}}

El servidor Oauth de ejemplo se inicia con el comando ./gradlew oauth:run. Para obtener un token se realiza con las siguientes peticiones. Por defecto, se solicita autenticación basic pero la invocación al método allowFormAuthenticationForClients() hace que los parámetros de las credenciales se puedan indicar por parámetros.

Con el endpoint/oauth/check_token se decodifica el token. En la página de JWT hay una herramienta para decodificar el token y verificar de la firma introduciendo clave de firma en la casilla.

1
2
3
4
5
6
7
$ curl -X POST -u "client:1234567890" -d "grant_type=client_credentials""http://localhost:8095/oauth/token"{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ0MSwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl0sImp0aSI6IjEwMzE0NTk4LTRjZDctNDRmNi1hMmM4LTNjYjA5MGE1MjUxZSIsImNsaWVudF9pZCI6ImNsaWVudCJ9.n8Dwcd8YTms2Hl0YgTho9QdBWD1hAnOEmkcS-Wefy6c","token_type":"bearer","expires_in":43199,"scope":"read","jti":"10314598-4cd7-44f6-a2c8-3cb090a5251e"}
$ curl -X POST "http://localhost:8095/oauth/token?grant_type=client_credentials&client_id=client&client_secret=1234567890"{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl0sImp0aSI6IjEzYjM1M2Q2LTQwODUtNDdiMS1hYzkyLTRiZDJhNDg3MzFhOCIsImNsaWVudF9pZCI6ImNsaWVudCJ9.CueMcwrD7pTp3pj37_BzzcUODG7PcjCacSa14-l5_Hw","token_type":"bearer","expires_in":43199,"scope":"read","jti":"13b353d6-4085-47b1-ac92-4bd2a48731a8"}
$ curl -X POST -u "client:1234567890" -d "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl0sImp0aSI6IjEzYjM1M2Q2LTQwODUtNDdiMS1hYzkyLTRiZDJhNDg3MzFhOCIsImNsaWVudF9pZCI6ImNsaWVudCJ9.CueMcwrD7pTp3pj37_BzzcUODG7PcjCacSa14-l5_Hw" http://localhost:8095/oauth/check_token
{"aud":["service"],"scope":["read"],"active":true,"exp":1549692458,"authorities":["CLIENT"],"jti":"13b353d6-4085-47b1-ac92-4bd2a48731a8","client_id":"client"}
Token JWT codificado y decodificado

Servidor Gateway

El servidor gateway en realidad no interviene en la lógica de Oauth porque la autorización se delega en cada servicio que contiene el recurso. Como se indicaba en Idealista estaría bien que el gateway librase de la responsabilidad de autorización a los servicios de los recursos para hacerlos más sencillos, creo que Spring Security en el momento del artículo no está soportado en Spring WebFlux que utiliza el gateway.

Lo único necesario par definir el gateway son las dependencias del proyecto, poco más que spring-cloud-starter-gateway, y la configuración de enrutado que matchea peticiones según el parámetro predicates, reescribe la URL hacia el servicio según el filtro RewritePath y finalmente redirige la petición a la ubicación del servicio indicada en uri. Se inicia con ./gradlew gateway:run.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
plugins{id'application'id'org.springframework.boot'version'2.0.8.RELEASE'}mainClassName='io.github.picodotdev.blogbitix.springoauth.gateway.Main'dependencies{implementationplatform('org.springframework.boot:spring-boot-dependencies:2.0.8.RELEASE')implementationplatform('org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2')defexcludeSpringBootStarterLogging={exclude(group:'org.springframework.boot',module:'spring-boot-starter-logging')}compile('org.springframework.boot:spring-boot-starter',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-log4j2',excludeSpringBootStarterLogging)compile('org.springframework.cloud:spring-cloud-starter-gateway',excludeSpringBootStarterLogging)runtime('com.google.code.gson:gson:2.8.5')runtime('com.fasterxml.jackson.core:jackson-databind:2.9.6')runtime('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6')}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server.port:8090spring:cloud:gateway:routes:-id:path_routeuri:http://localhost:8080/predicates:-Path=/service/filters:-RewritePath=/service/,/

Servicio, servidor de recurso

Dado que el servicio interpreta los tokens JWT y aplica reglas de seguridad necesita las mismas dependencias que utiliza el servidor Oauth.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
plugins{id'application'id'org.springframework.boot'version'2.0.8.RELEASE'}mainClassName='io.github.picodotdev.blogbitix.springoauth.service.Main'dependencies{implementationplatform('org.springframework.boot:spring-boot-dependencies:2.0.8.RELEASE')implementationplatform('org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2')defexcludeSpringBootStarterLogging={exclude(group:'org.springframework.boot',module:'spring-boot-starter-logging')}compile('org.springframework.boot:spring-boot-starter',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-web',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-log4j2',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-security',excludeSpringBootStarterLogging)compile('org.springframework.cloud:spring-cloud-starter-oauth2',excludeSpringBootStarterLogging)compile('org.springframework.security.oauth:spring-security-oauth2:2.3.4.RELEASE',excludeSpringBootStarterLogging)compile('org.springframework.security:spring-security-jwt:1.0.10.RELEASE',excludeSpringBootStarterLogging)runtime('com.google.code.gson:gson:2.8.5')runtime('com.fasterxml.jackson.core:jackson-databind:2.9.6')runtime('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6')runtime('javax.xml.bind:jaxb-api:2.3.0')runtime('com.sun.xml.bind:jaxb-impl:2.3.0')runtime('org.glassfish.jaxb:jaxb-runtime:2.3.0')runtime('javax.activation:activation:1.1.1')}

El recurso es muy simple, solo devuelve un mensaje.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
packageio.github.picodotdev.blogbitix.springoauth.service;...@RestControllerpublicclassDefaultController{privateRandomrandom;publicDefaultController(){this.random=newRandom();}@RequestMapping("/")publicStringhome(HttpServletRequestrequest)throwsException{returnString.format("Hello world (%s)",request.getRequestURL());}}

El servicio comparte configuración similar al servidor de Ouath par el JwtAccessTokenConverter, TokenStore y DefaultTokenServices. En el método configure se define que el endpoint/ requiere el rol CLIENT que se obtiene del token JWT enviado. Hay que utilizar la anotación @EnableResourceServer, se inicia con el comando ./gradlew service:run.

Hay que recalcar que el servicio para verificar el token y comprobar la autorización no necesita comunicarse con el servidor Oauth toda la información que necesita está en el token.

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
packageio.github.picodotdev.blogbitix.springoauth.service;...@Configuration@EnableResourceServerpublicclassResourceServerConfigurationextendsResourceServerConfigurerAdapter{@AutowiredprivateJwtAccessTokenConvertertokenConverter;@AutowiredprivateTokenStoretokenStore;@AutowiredprivateDefaultTokenServicestokenServices;@BeanpublicJwtAccessTokenConvertertokenConverter(){JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();converter.setSigningKey("1234567890");returnconverter;}@BeanpublicTokenStoretokenStore(JwtAccessTokenConvertertokenConverter){returnnewJwtTokenStore(tokenConverter);}@BeanDefaultTokenServicestokenServices(TokenStoretokenStore,JwtAccessTokenConvertertokenConverter){DefaultTokenServicestokenServices=newDefaultTokenServices();tokenServices.setTokenStore(tokenStore);tokenServices.setTokenEnhancer(tokenConverter);returntokenServices;}@Overridepublicvoidconfigure(ResourceServerSecurityConfigurerresources)throwsException{resources.tokenServices(tokenServices).resourceId("service");}@Overridepublicvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().antMatchers("/").hasAuthority("CLIENT");}}

Si no se envía el token JWT se produce un error de autenticación con código de error 401 Unauthorized, si se envía un token correcto y la autoridad requerida del recurso la petición se devuelve el mensaje u el código de estado 200 OK, si se envía un token JWT con una autoridad que no corresponde con la necesaria para el recurso, en el ejemplo una autoridad DUMMY, se devuelve un código de estado 403 Forbbiden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
$ curl -v http://localhost:8090/service/
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8090(#0)> GET /service/ HTTP/1.1
> Host: localhost:8090
> User-Agent: curl/7.63.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< transfer-encoding: chunked
< Cache-Control: no-store
< Pragma: no-cache
< WWW-Authenticate: Bearer realm="service", error="unauthorized", error_description="Full authentication is required to access this resource"< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1;mode=block
< X-Frame-Options: DENY
< Content-Type: application/json;charset=UTF-8
< Date: Fri, 08 Feb 201918:58:03 GMT
<
* Connection #0 to host localhost left intact{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl0sImp0aSI6IjEzYjM1M2Q2LTQwODUtNDdiMS1hYzkyLTRiZDJhNDg3MzFhOCIsImNsaWVudF9pZCI6ImNsaWVudCJ9.CueMcwrD7pTp3pj37_BzzcUODG7PcjCacSa14-l5_Hw" http://localhost:8090/service/
Hello world (http://localhost:8080/)
$ curl -X POST -u "client:1234567890" -d "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiRFVNTVkiXSwianRpIjoiMTNiMzUzZDYtNDA4NS00N2IxLWFjOTItNGJkMmE0ODczMWE4IiwiY2xpZW50X2lkIjoiY2xpZW50In0.RaeQYdukn8Xr8S9ld5Vy2UnYboUjPyMkutNgyfVN-Bc" http://localhost:8095/oauth/check_token
{"aud":["service"],"scope":["read"],"active":true,"exp":1549692458,"authorities":["DUMMY"],"jti":"13b353d6-4085-47b1-ac92-4bd2a48731a8","client_id":"client"}
$ curl -v -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiRFVNTVkiXSwianRpIjoiMTNiMzUzZDYtNDA4NS00N2IxLWFjOTItNGJkMmE0ODczMWE4IiwiY2xpZW50X2lkIjoiY2xpZW50In0.RaeQYdukn8Xr8S9ld5Vy2UnYboUjPyMkutNgyfVN-Bc" http://localhost:8090/service/
{"error":"access_denied","error_description":"Access is denied"}
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8090(#0)> GET /service/ HTTP/1.1
> Host: localhost:8090
> User-Agent: curl/7.63.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiRFVNTVkiXSwianRpIjoiMTNiMzUzZDYtNDA4NS00N2IxLWFjOTItNGJkMmE0ODczMWE4IiwiY2xpZW50X2lkIjoiY2x
pZW50In0.RaeQYdukn8Xr8S9ld5Vy2UnYboUjPyMkutNgyfVN-Bc
>
< HTTP/1.1 403 Forbidden
< transfer-encoding: chunked
< Cache-Control: no-store
< Pragma: no-cache
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1;mode=block
< X-Frame-Options: DENY
< Content-Type: application/json;charset=UTF-8
< Date: Fri, 08 Feb 201919:02:14 GMT
<
* Connection #0 to host localhost left intact{"error":"access_denied","error_description":"Access is denied"}

Los tokens JWT además de firmar se pueden cifrar, en el ejemplo se usa una conexión no segura con el protocolo HTTP usando una conexión segura HTTPS ya se proporcionaría confidencialidad para los tokens y es lo recomendado.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew oauth:run, ./gradlew gateway:run, ./gradlew service:run.


Viewing all articles
Browse latest Browse all 2711