Skip to content

Commit d49cfb7

Browse files
authored
Add support for "JWT Introspection Response". (#49)
1 parent d82676d commit d49cfb7

6 files changed

Lines changed: 411 additions & 44 deletions

File tree

src/main/java/com/authlete/jaxrs/server/api/IntrospectionEndpoint.java

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2017 Authlete, Inc.
2+
* Copyright (C) 2017-2023 Authlete, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,9 @@
2929
import com.authlete.common.api.AuthleteApiFactory;
3030
import com.authlete.common.web.BasicCredentials;
3131
import com.authlete.jaxrs.BaseIntrospectionEndpoint;
32+
import com.authlete.jaxrs.IntrospectionRequestHandler.Params;
33+
import com.authlete.jaxrs.server.db.ResourceServerDao;
34+
import com.authlete.jaxrs.server.db.ResourceServerEntity;
3235

3336

3437
/**
@@ -39,6 +42,7 @@
3942
* >RFC 7662, OAuth 2.0 Token Introspection</a>
4043
*
4144
* @author Takahiko Kawasaki
45+
* @author Hideki Ikeda
4246
*/
4347
@Path("/api/introspection")
4448
public class IntrospectionEndpoint extends BaseIntrospectionEndpoint
@@ -53,6 +57,7 @@ public class IntrospectionEndpoint extends BaseIntrospectionEndpoint
5357
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
5458
public Response post(
5559
@HeaderParam(HttpHeaders.AUTHORIZATION) String authorization,
60+
@HeaderParam(HttpHeaders.ACCEPT) String accept,
5661
MultivaluedMap<String, String> parameters)
5762
{
5863
// "2.1. Introspection Request" in RFC 7662 says as follows:
@@ -68,46 +73,47 @@ public Response post(
6873
// Therefore, this API must be protected in some way or other.
6974
// Basic Authentication and Bearer Token are typical means, and
7075
// both use the value of the 'Authorization' header.
71-
//
72-
// Authenticate the API caller.
73-
boolean authenticated = authenticateApiCaller(authorization);
7476

75-
// If the API caller does not have necessary privileges to call this API.
76-
if (authenticated == false)
77+
BasicCredentials credentials = BasicCredentials.parse(authorization);
78+
79+
// Fetch the information about the resource server from DB.
80+
ResourceServerEntity rsEntity = ResourceServerDao.get(credentials.getUserId());
81+
82+
// If failed to authenticate the resource server.
83+
if (authenticateResourceServer(rsEntity, credentials) == false)
7784
{
7885
// Return "401 Unauthorized".
7986
return Response.status(Status.UNAUTHORIZED).build();
8087
}
8188

89+
// Build a Param object to call the request handler.
90+
Params params = buildParams(parameters, accept, rsEntity);
91+
8292
// Handle the introspection request.
83-
return handle(AuthleteApiFactory.getDefaultApi(), parameters);
93+
return handle(AuthleteApiFactory.getDefaultApi(), params);
8494
}
8595

8696

87-
/**
88-
* Authenticate the API caller.
89-
*
90-
* @param authorization
91-
* The value of the {@code Authorization} header of the API call.
92-
*
93-
* @return
94-
* True if the API caller has necessary privileges to access
95-
* the introspection endpoint.
96-
*/
97-
private boolean authenticateApiCaller(String authorization)
97+
private Params buildParams(
98+
MultivaluedMap<String, String> parameters, String accept, ResourceServerEntity rsEntity)
9899
{
99-
// TODO: This implementation is for demonstration purpose only.
100+
return new Params()
101+
.setParameters(parameters)
102+
.setHttpAcceptHeader(accept)
103+
.setRsUri(rsEntity.getUri())
104+
.setIntrospectionSignAlg(rsEntity.getIntrospectionSignAlg())
105+
.setIntrospectionEncryptionAlg(rsEntity.getIntrospectionEncryptionAlg())
106+
.setIntrospectionEncryptionEnc(rsEntity.getIntrospectionEncryptionEnc())
107+
.setPublicKeyForEncryption(rsEntity.getPublicKeyForIntrospectionResponseEncryption())
108+
.setSharedKeyForSign(rsEntity.getSharedKeyForIntrospectionResponseSign())
109+
.setSharedKeyForEncryption(rsEntity.getSharedKeyForIntrospectionResponseEncryption());
110+
}
100111

101-
// If the Authorization header contains "Basic Authentication" and
102-
// if the user part is "nobody".
103-
BasicCredentials credentials = BasicCredentials.parse(authorization);
104-
if (credentials != null && "nobody".equals(credentials.getUserId()))
105-
{
106-
// Reject the introspection request by "nobody".
107-
return false;
108-
}
109112

110-
// Accept anybody except "nobody".
111-
return true;
113+
private boolean authenticateResourceServer(
114+
ResourceServerEntity rsEntity, BasicCredentials credentials)
115+
{
116+
return rsEntity != null &&
117+
rsEntity.getSecret().equals(credentials.getPassword());
112118
}
113119
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (C) 2023 Authlete, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the
15+
* License.
16+
*/
17+
package com.authlete.jaxrs.server.db;
18+
19+
20+
import java.io.InputStreamReader;
21+
import java.io.Reader;
22+
import java.nio.charset.StandardCharsets;
23+
24+
25+
public class BaseDao
26+
{
27+
/**
28+
* Create a Reader instance that reads the specified resource.
29+
*/
30+
protected static Reader createReader(Class<?> clazz, String resource)
31+
{
32+
return new InputStreamReader(
33+
clazz.getResourceAsStream(resource), StandardCharsets.UTF_8);
34+
}
35+
}

src/main/java/com/authlete/jaxrs/server/db/DatasetDao.java

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818

1919

2020
import java.io.IOException;
21-
import java.io.InputStreamReader;
2221
import java.io.Reader;
23-
import java.nio.charset.StandardCharsets;
2422
import java.util.Arrays;
2523
import java.util.Collections;
2624
import java.util.HashMap;
@@ -33,7 +31,7 @@
3331
/**
3432
* Sources of datasets (contents of "verified_claims").
3533
*/
36-
public class DatasetDao
34+
public class DatasetDao extends BaseDao
3735
{
3836
// Sources of datasets. The JSON files have been copied from
3937
// https://bitbucket.org/openid/ekyc-ida/src/master/examples/response/
@@ -100,7 +98,7 @@ private static Map<String, List<Map<String, Object>>> createSubjectDatasetsMap()
10098
private static Map<String, Object> loadDataset(String resource)
10199
{
102100
// Create a Reader to read the resource.
103-
try (Reader reader = createReader(resource))
101+
try ( Reader reader = createReader(DatasetDao.class, resource) )
104102
{
105103
// Convert the JSON in the resource into a Map instance.
106104
Map<String, Object> map = new Gson().fromJson(reader, Map.class);
@@ -118,17 +116,6 @@ private static Map<String, Object> loadDataset(String resource)
118116
}
119117

120118

121-
/**
122-
* Create a Reader instance that reads the specified resource.
123-
*/
124-
private static Reader createReader(String resource)
125-
{
126-
return new InputStreamReader(
127-
DatasetDao.class.getResourceAsStream(resource),
128-
StandardCharsets.UTF_8);
129-
}
130-
131-
132119
/**
133120
* Get the datasets of the subject (user identifier).
134121
*
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (C) 2023 Authlete, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the
15+
* License.
16+
*/
17+
package com.authlete.jaxrs.server.db;
18+
19+
20+
import java.io.IOException;
21+
import java.io.Reader;
22+
import java.lang.reflect.Type;
23+
import java.util.ArrayList;
24+
import java.util.Collections;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.stream.Collectors;
28+
import com.google.gson.Gson;
29+
import com.google.gson.reflect.TypeToken;
30+
31+
32+
/**
33+
* Operations to access the resource server database.
34+
*/
35+
public class ResourceServerDao extends BaseDao
36+
{
37+
private static final String RESOURCE_SERVER = "/resource_servers.json";
38+
39+
40+
/**
41+
* Holder of the cache of resource server entities.
42+
*/
43+
private static final class ResourceServerEntityHolder
44+
{
45+
// Cache of resource server entities. Keys are resource server
46+
// IDs. Values are ResourceServerEntity objects loaded from JSON
47+
// files.
48+
private static final Map<String, ResourceServerEntity> INSTANCE =
49+
createResourceServers();
50+
}
51+
52+
53+
/**
54+
* Create the content of ResourceServersHolder.INSTANCE.
55+
*/
56+
private static Map<String, ResourceServerEntity> createResourceServers()
57+
{
58+
return loadResourceServers(RESOURCE_SERVER)
59+
.stream()
60+
.collect(Collectors.toMap(s -> s.getId(), s -> s));
61+
}
62+
63+
64+
/**
65+
* Load configurations of resource servers from the resource.
66+
*/
67+
private static List<ResourceServerEntity> loadResourceServers(String resource)
68+
{
69+
// Create a Reader to read the resource.
70+
try ( Reader reader = createReader(ResourceServerDao.class, resource) )
71+
{
72+
// The type of the object to be loaded.
73+
Type type = new TypeToken<ArrayList<ResourceServerEntity>>(){}.getType();
74+
75+
// Convert the JSON in the resource into a list of ResourceServerEntity.
76+
return new Gson().fromJson(reader, type);
77+
}
78+
catch (IOException e)
79+
{
80+
// Failed to read the resource.
81+
e.printStackTrace();
82+
83+
return Collections.emptyList();
84+
}
85+
}
86+
87+
88+
/**
89+
* Get a resource server entity.
90+
*
91+
* @param rsId
92+
* The ID of a resource server.
93+
*
94+
* @return
95+
* A resource server entity specified by the ID. null is
96+
* returned when the resource server entity is unavailable.
97+
*/
98+
public static ResourceServerEntity get(String rsId)
99+
{
100+
return ResourceServerEntityHolder.INSTANCE.get(rsId);
101+
}
102+
}

0 commit comments

Comments
 (0)