Skip to content

Commit fc87ebb

Browse files
authored
fixes #268 Add a UnifiedSecurityHandler for light-gateway (#269)
1 parent dda2aba commit fc87ebb

18 files changed

+1309
-49
lines changed

openapi-security/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@
5454
<groupId>com.networknt</groupId>
5555
<artifactId>status</artifactId>
5656
</dependency>
57+
<dependency>
58+
<groupId>com.networknt</groupId>
59+
<artifactId>basic-auth</artifactId>
60+
</dependency>
61+
<dependency>
62+
<groupId>com.networknt</groupId>
63+
<artifactId>api-key</artifactId>
64+
</dependency>
65+
<dependency>
66+
<groupId>com.networknt</groupId>
67+
<artifactId>server</artifactId>
68+
</dependency>
5769
<dependency>
5870
<groupId>com.networknt</groupId>
5971
<artifactId>json-overlay</artifactId>

openapi-security/src/main/java/com/networknt/openapi/JwtVerifyHandler.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,19 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
105105
logger.debug("JwtVerifyHandler.handleRequest ends.");
106106
return;
107107
}
108+
// only UnifiedSecurityHandler will have the jwkServiceIds as the third parameter.
109+
if(handleJwt(exchange, null, reqPath, null)) {
110+
if(logger.isDebugEnabled()) logger.debug("JwtVerifyHandler.handleRequest ends.");
111+
Handler.next(exchange, next);
112+
}
113+
}
114+
115+
public boolean handleJwt(HttpServerExchange exchange, String pathPrefix, String reqPath, List<String> jwkServiceIds) throws Exception {
108116
Map<String, Object> auditInfo = null;
109117
HeaderMap headerMap = exchange.getRequestHeaders();
110118
String authorization = headerMap.getFirst(Headers.AUTHORIZATION);
111119

112-
if (logger.isTraceEnabled() && authorization != null)
120+
if (logger.isTraceEnabled() && authorization != null && authorization.length() > 10)
113121
logger.trace("Authorization header = " + authorization.substring(0, 10));
114122

115123
authorization = this.getScopeToken(authorization, headerMap);
@@ -125,7 +133,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
125133

126134
try {
127135

128-
JwtClaims claims = jwtVerifier.verifyJwt(jwt, ignoreExpiry, true, reqPath);
136+
JwtClaims claims = jwtVerifier.verifyJwt(jwt, ignoreExpiry, true, pathPrefix, reqPath, jwkServiceIds);
129137

130138
if (logger.isTraceEnabled())
131139
logger.trace("claims = " + claims.toJson());
@@ -156,7 +164,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
156164
if (!config.isEnableH2c() && this.checkForH2CRequest(headerMap)) {
157165
setExchangeStatus(exchange, STATUS_METHOD_NOT_ALLOWED);
158166
if (logger.isDebugEnabled()) logger.debug("JwtVerifyHandler.handleRequest ends with an error.");
159-
return;
167+
return false;
160168
}
161169

162170
String callerId = headerMap.getFirst(HttpStringConstants.CALLER_ID);
@@ -178,19 +186,19 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
178186
} else {
179187
// this will return an error message to the client.
180188
}
181-
return;
189+
return false;
182190
}
183191

184192
/* validate scope from operation */
185193
String scopeHeader = headerMap.getFirst(HttpStringConstants.SCOPE_TOKEN);
186194
String scopeJwt = JwtVerifier.getJwtFromAuthorization(scopeHeader);
187195
List<String> secondaryScopes = new ArrayList<>();
188196

189-
if(!this.hasValidSecondaryScopes(exchange, scopeJwt, secondaryScopes, ignoreExpiry, reqPath, auditInfo)) {
190-
return;
197+
if(!this.hasValidSecondaryScopes(exchange, scopeJwt, secondaryScopes, ignoreExpiry, pathPrefix, reqPath, jwkServiceIds, auditInfo)) {
198+
return false;
191199
}
192200
if(!this.hasValidScope(exchange, scopeHeader, secondaryScopes, claims, operation)) {
193-
return;
201+
return false;
194202
}
195203
}
196204
if (logger.isTraceEnabled())
@@ -199,7 +207,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
199207
if (logger.isDebugEnabled())
200208
logger.debug("JwtVerifyHandler.handleRequest ends.");
201209

202-
Handler.next(exchange, next);
210+
return true;
203211
} catch (InvalidJwtException e) {
204212

205213
// only log it and unauthorized is returned.
@@ -227,8 +235,8 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
227235
setExchangeStatus(exchange, STATUS_MISSING_AUTH_TOKEN);
228236
exchange.endExchange();
229237
}
238+
return true;
230239
}
231-
232240
/**
233241
* Get authToken from X-Scope-Token header.
234242
* This covers situations where there is a secondary auth token.
@@ -319,13 +327,13 @@ protected Operation getOperation(HttpServerExchange exchange, OpenApiOperation o
319327
* @param reqPath - the request path as string
320328
* @return - return true if the secondary scopes are valid or if there are no secondary scopes.
321329
*/
322-
protected boolean hasValidSecondaryScopes(HttpServerExchange exchange, String scopeJwt, List<String> secondaryScopes, boolean ignoreExpiry, String reqPath, Map<String, Object> auditInfo) {
330+
protected boolean hasValidSecondaryScopes(HttpServerExchange exchange, String scopeJwt, List<String> secondaryScopes, boolean ignoreExpiry, String pathPrefix, String reqPath, List<String> jwkServiceIds, Map<String, Object> auditInfo) {
323331
if (scopeJwt != null) {
324332
if (logger.isTraceEnabled())
325333
logger.trace("start verifying scope token = " + scopeJwt.substring(0, 10));
326334

327335
try {
328-
JwtClaims scopeClaims = jwtVerifier.verifyJwt(scopeJwt, ignoreExpiry, true, reqPath);
336+
JwtClaims scopeClaims = jwtVerifier.verifyJwt(scopeJwt, ignoreExpiry, true, pathPrefix, reqPath, jwkServiceIds);
329337
Object scopeClaim = scopeClaims.getClaimValue(Constants.SCOPE_STRING);
330338

331339
if (scopeClaim instanceof String) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.networknt.openapi;
2+
3+
import java.util.List;
4+
5+
public class UnifiedPathPrefixAuth {
6+
String pathPrefix;
7+
boolean basic;
8+
boolean jwt;
9+
boolean apikey;
10+
List<String> jwkServiceIds;
11+
12+
public String getPathPrefix() {
13+
return pathPrefix;
14+
}
15+
16+
public void setPathPrefix(String pathPrefix) {
17+
this.pathPrefix = pathPrefix;
18+
}
19+
20+
public boolean isBasic() {
21+
return basic;
22+
}
23+
24+
public void setBasic(boolean basic) {
25+
this.basic = basic;
26+
}
27+
28+
public boolean isJwt() {
29+
return jwt;
30+
}
31+
32+
public void setJwt(boolean jwt) {
33+
this.jwt = jwt;
34+
}
35+
36+
public boolean isApikey() {
37+
return apikey;
38+
}
39+
40+
public void setApikey(boolean apikey) {
41+
this.apikey = apikey;
42+
}
43+
44+
public List<String> getJwkServiceIds() {
45+
return jwkServiceIds;
46+
}
47+
48+
public void setJwkServiceIds(List<String> jwkServiceIds) {
49+
this.jwkServiceIds = jwkServiceIds;
50+
}
51+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package com.networknt.openapi;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.networknt.config.Config;
5+
import com.networknt.config.ConfigException;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
public class UnifiedSecurityConfig {
15+
private static final Logger logger = LoggerFactory.getLogger(UnifiedSecurityConfig.class);
16+
public static final String CONFIG_NAME = "unified-security";
17+
public static final String ENABLED = "enabled";
18+
public static final String ANONYMOUS_PREFIXES = "anonymousPrefixes";
19+
public static final String PATH_PREFIX_AUTHS = "pathPrefixAuths";
20+
public static final String PREFIX = "prefix";
21+
public static final String BASIC = "basic";
22+
public static final String JWT = "jwt";
23+
public static final String APIKEY = "apikey";
24+
public static final String JWK_SERVICE_IDS = "jwkServiceIds";
25+
26+
boolean enabled;
27+
List<String> anonymousPrefixes;
28+
List<UnifiedPathPrefixAuth> pathPrefixAuths;
29+
30+
private Config config;
31+
private Map<String, Object> mappedConfig;
32+
33+
private UnifiedSecurityConfig() {
34+
this(CONFIG_NAME);
35+
}
36+
37+
/**
38+
* Please note that this constructor is only for testing to load different config files
39+
* to test different configurations.
40+
* @param configName String
41+
*/
42+
private UnifiedSecurityConfig(String configName) {
43+
config = Config.getInstance();
44+
mappedConfig = config.getJsonMapConfigNoCache(configName);
45+
setConfigData();
46+
setConfigList();
47+
}
48+
public static UnifiedSecurityConfig load() {
49+
return new UnifiedSecurityConfig();
50+
}
51+
52+
public static UnifiedSecurityConfig load(String configName) {
53+
return new UnifiedSecurityConfig(configName);
54+
}
55+
56+
void reload() {
57+
mappedConfig = config.getJsonMapConfigNoCache(CONFIG_NAME);
58+
setConfigData();
59+
setConfigList();
60+
}
61+
62+
public boolean isEnabled() {
63+
return enabled;
64+
}
65+
66+
public void setEnabled(boolean enabled) {
67+
this.enabled = enabled;
68+
}
69+
70+
public List<String> getAnonymousPrefixes() {
71+
return anonymousPrefixes;
72+
}
73+
74+
public void setAnonymousPrefixes(List<String> anonymousPrefixes) {
75+
this.anonymousPrefixes = anonymousPrefixes;
76+
}
77+
78+
public List<UnifiedPathPrefixAuth> getPathPrefixAuths() {
79+
return pathPrefixAuths;
80+
}
81+
82+
public void setPathPrefixAuths(List<UnifiedPathPrefixAuth> pathPrefixAuths) {
83+
this.pathPrefixAuths = pathPrefixAuths;
84+
}
85+
86+
private void setConfigData() {
87+
Object object = mappedConfig.get(ENABLED);
88+
if(object != null && (Boolean) object) {
89+
setEnabled(true);
90+
}
91+
}
92+
93+
private void setConfigList() {
94+
// anonymous prefixes
95+
if (mappedConfig != null && mappedConfig.get(ANONYMOUS_PREFIXES) != null) {
96+
Object object = mappedConfig.get(ANONYMOUS_PREFIXES);
97+
anonymousPrefixes = new ArrayList<>();
98+
if(object instanceof String) {
99+
String s = (String)object;
100+
s = s.trim();
101+
if(logger.isTraceEnabled()) logger.trace("s = " + s);
102+
if(s.startsWith("[")) {
103+
// json format
104+
try {
105+
anonymousPrefixes = Config.getInstance().getMapper().readValue(s, new TypeReference<List<String>>() {});
106+
} catch (Exception e) {
107+
throw new ConfigException("could not parse the anonymousPrefixes json with a list of strings.");
108+
}
109+
} else {
110+
// comma separated
111+
anonymousPrefixes = Arrays.asList(s.split("\\s*,\\s*"));
112+
}
113+
} else if (object instanceof List) {
114+
List prefixes = (List)object;
115+
prefixes.forEach(item -> {
116+
anonymousPrefixes.add((String)item);
117+
});
118+
} else {
119+
throw new ConfigException("anonymousPrefixes must be a string or a list of strings.");
120+
}
121+
}
122+
123+
// path prefix auth mapping
124+
if (mappedConfig.get(PATH_PREFIX_AUTHS) != null) {
125+
Object object = mappedConfig.get(PATH_PREFIX_AUTHS);
126+
pathPrefixAuths = new ArrayList<>();
127+
if(object instanceof String) {
128+
String s = (String)object;
129+
s = s.trim();
130+
if(logger.isTraceEnabled()) logger.trace("pathPrefixAuth s = " + s);
131+
if(s.startsWith("[")) {
132+
// json format
133+
try {
134+
pathPrefixAuths = Config.getInstance().getMapper().readValue(s, new TypeReference<List<UnifiedPathPrefixAuth>>() {});
135+
} catch (Exception e) {
136+
throw new ConfigException("could not parse the pathPrefixAuths json with a list of string and object.");
137+
}
138+
} else {
139+
throw new ConfigException("pathPrefixAuths must be a list of string object map.");
140+
}
141+
} else if (object instanceof List) {
142+
// the object is a list of map, we need convert it to PathPrefixAuth object.
143+
List<Map<String, Object>> values = (List<Map<String, Object>>)object;
144+
for(Map<String, Object> value: values) {
145+
UnifiedPathPrefixAuth unifiedPathPrefixAuth = new UnifiedPathPrefixAuth();
146+
147+
unifiedPathPrefixAuth.setPathPrefix((String)value.get(PREFIX));
148+
unifiedPathPrefixAuth.setBasic(value.get(BASIC) == null ? false : (Boolean)value.get(BASIC));
149+
unifiedPathPrefixAuth.setJwt(value.get(JWT) == null ? false : (Boolean)value.get(JWT));
150+
unifiedPathPrefixAuth.setApikey(value.get(APIKEY) == null ? false : (Boolean)value.get(APIKEY));
151+
Object ids = value.get(JWK_SERVICE_IDS);
152+
if(ids instanceof String) {
153+
String s = (String)value.get(JWK_SERVICE_IDS);
154+
if(s.startsWith("[")) {
155+
// json format
156+
try {
157+
unifiedPathPrefixAuth.setJwkServiceIds(Config.getInstance().getMapper().readValue(s, new TypeReference<List<String>>() {}));
158+
} catch (Exception e) {
159+
throw new ConfigException("could not parse the jwkServiceIds json with a list of strings.");
160+
}
161+
} else {
162+
// comma separated
163+
unifiedPathPrefixAuth.setJwkServiceIds(Arrays.asList(s.split("\\s*,\\s*")));
164+
}
165+
} else if(ids instanceof List ) {
166+
// it must be a json array.
167+
unifiedPathPrefixAuth.setJwkServiceIds((List)ids);
168+
}
169+
pathPrefixAuths.add(unifiedPathPrefixAuth);
170+
}
171+
} else {
172+
throw new ConfigException("pathPrefixAuth must be a list of string object map.");
173+
}
174+
}
175+
}
176+
177+
}

0 commit comments

Comments
 (0)