token introspection now returns user "sub" when available in addition to "user_id", closes #507 (might cause incompatibility problems)
parent
85acfa90db
commit
cdd23df7ee
|
@ -18,119 +18,39 @@ package org.mitre.oauth2.view;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.swing.text.DateFormatter;
|
||||
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
|
||||
import org.mitre.openid.connect.model.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
@Component("tokenIntrospection")
|
||||
public class TokenIntrospectionView extends AbstractView {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(TokenIntrospectionView.class);
|
||||
|
||||
private static DateFormatter isoDateFormatter = new DateFormatter(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"));
|
||||
|
||||
private Gson gson = new GsonBuilder().create();
|
||||
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
/*
|
||||
if (f.getDeclaringClass().isAssignableFrom(OAuth2AccessTokenEntity.class)) {
|
||||
// we don't want to serialize the whole object, just the scope and timeout
|
||||
if (f.getName().equals("scope")) {
|
||||
return false;
|
||||
} else if (f.getName().equals("expiration")) {
|
||||
return false;
|
||||
} else {
|
||||
// skip everything else on this class
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// serialize other classes without filter (lists and sets and things)
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
// skip the JPA binding wrapper
|
||||
if (clazz.equals(BeanPropertyBindingResult.class)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
.registerTypeAdapter(OAuth2AccessTokenEntity.class, new JsonSerializer<OAuth2AccessTokenEntity>() {
|
||||
@Override
|
||||
public JsonElement serialize(OAuth2AccessTokenEntity src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject token = new JsonObject();
|
||||
|
||||
token.addProperty("active", true);
|
||||
|
||||
token.addProperty("scope", Joiner.on(" ").join(src.getScope()));
|
||||
|
||||
token.add("exp", context.serialize(src.getExpiration()));
|
||||
|
||||
//token.addProperty("audience", src.getAuthenticationHolder().getAuthentication().getAuthorizationRequest().getClientId());
|
||||
|
||||
token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName());
|
||||
|
||||
token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId());
|
||||
|
||||
token.addProperty("token_type", src.getTokenType());
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
})
|
||||
.registerTypeAdapter(OAuth2RefreshTokenEntity.class, new JsonSerializer<OAuth2RefreshTokenEntity>() {
|
||||
@Override
|
||||
public JsonElement serialize(OAuth2RefreshTokenEntity src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject token = new JsonObject();
|
||||
|
||||
token.addProperty("active", true);
|
||||
|
||||
token.addProperty("scope", Joiner.on(" ").join(src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope()));
|
||||
|
||||
token.add("exp", context.serialize(src.getExpiration()));
|
||||
|
||||
//token.addProperty("audience", src.getAuthenticationHolder().getAuthentication().getAuthorizationRequest().getClientId());
|
||||
|
||||
token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName());
|
||||
|
||||
token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId());
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
})
|
||||
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
.create();
|
||||
|
||||
response.setContentType("application/json");
|
||||
|
||||
Writer out;
|
||||
|
@ -138,13 +58,16 @@ public class TokenIntrospectionView extends AbstractView {
|
|||
try {
|
||||
|
||||
out = response.getWriter();
|
||||
Object obj = model.get("entity");
|
||||
if (obj == null) {
|
||||
obj = model;
|
||||
UserInfo user = (UserInfo)model.get("user");
|
||||
Object obj = model.get("token");
|
||||
if (obj instanceof OAuth2AccessTokenEntity) {
|
||||
gson.toJson(renderAccessToken((OAuth2AccessTokenEntity)obj, user), out);
|
||||
} else if (obj instanceof OAuth2RefreshTokenEntity) {
|
||||
gson.toJson(renderRefreshToken((OAuth2RefreshTokenEntity)obj, user), out);
|
||||
} else {
|
||||
throw new IOException("Couldn't find a valid entity to render");
|
||||
}
|
||||
|
||||
gson.toJson(obj, out);
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
logger.error("IOException occurred in TokenIntrospectionView.java: ", e);
|
||||
|
@ -153,4 +76,66 @@ public class TokenIntrospectionView extends AbstractView {
|
|||
|
||||
}
|
||||
|
||||
private JsonObject renderAccessToken(OAuth2AccessTokenEntity src, UserInfo user) {
|
||||
JsonObject token = new JsonObject();
|
||||
|
||||
token.addProperty("active", true);
|
||||
|
||||
token.addProperty("scope", Joiner.on(" ").join(src.getScope()));
|
||||
|
||||
if (src.getExpiration() != null) {
|
||||
try {
|
||||
token.addProperty("exp", isoDateFormatter.valueToString(src.getExpiration()));
|
||||
} catch (ParseException e) {
|
||||
logger.error("Problem formatting expiration date: " + src.getExpiration(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (user != null) {
|
||||
// if we have a UserInfo, use that for the subject
|
||||
token.addProperty("sub", user.getSub());
|
||||
token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName());
|
||||
} else {
|
||||
// otherwise, use the authentication's username
|
||||
token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName());
|
||||
token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName());
|
||||
}
|
||||
|
||||
token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId());
|
||||
|
||||
token.addProperty("token_type", src.getTokenType());
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private JsonObject renderRefreshToken(OAuth2RefreshTokenEntity src, UserInfo user) {
|
||||
JsonObject token = new JsonObject();
|
||||
|
||||
token.addProperty("active", true);
|
||||
|
||||
token.addProperty("scope", Joiner.on(" ").join(src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope()));
|
||||
|
||||
if (src.getExpiration() != null) {
|
||||
try {
|
||||
token.addProperty("exp", isoDateFormatter.valueToString(src.getExpiration()));
|
||||
} catch (ParseException e) {
|
||||
logger.error("Problem formatting expiration date: " + src.getExpiration(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (user != null) {
|
||||
// if we have a UserInfo, use that for the subject
|
||||
token.addProperty("sub", user.getSub());
|
||||
token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName());
|
||||
} else {
|
||||
// otherwise, use the authentication's username
|
||||
token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName());
|
||||
token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName());
|
||||
}
|
||||
|
||||
token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId());
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
|
|||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.mitre.oauth2.service.IntrospectionAuthorizer;
|
||||
import org.mitre.oauth2.service.OAuth2TokenEntityService;
|
||||
import org.mitre.openid.connect.model.UserInfo;
|
||||
import org.mitre.openid.connect.service.UserInfoService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -51,6 +53,9 @@ public class IntrospectionEndpoint {
|
|||
|
||||
@Autowired
|
||||
private IntrospectionAuthorizer introspectionAuthorizer;
|
||||
|
||||
@Autowired
|
||||
private UserInfoService userInfoService;
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(IntrospectionEndpoint.class);
|
||||
|
||||
|
@ -76,10 +81,14 @@ public class IntrospectionEndpoint {
|
|||
return "jsonEntityView";
|
||||
}
|
||||
|
||||
// clientID is the principal name in the authentication
|
||||
String clientId = p.getName();
|
||||
ClientDetailsEntity authClient = clientService.loadClientByClientId(clientId);
|
||||
|
||||
ClientDetailsEntity tokenClient = null;
|
||||
Set<String> scopes = null;
|
||||
Object token = null;
|
||||
UserInfo user = null;
|
||||
|
||||
try {
|
||||
|
||||
|
@ -91,6 +100,8 @@ public class IntrospectionEndpoint {
|
|||
|
||||
token = access;
|
||||
|
||||
user = userInfoService.getByUsernameAndClientId(access.getAuthenticationHolder().getAuthentication().getName(), tokenClient.getClientId());
|
||||
|
||||
} catch (InvalidTokenException e) {
|
||||
logger.error("Verify failed; Invalid access token. Checking refresh token.", e);
|
||||
try {
|
||||
|
@ -101,6 +112,8 @@ public class IntrospectionEndpoint {
|
|||
tokenClient = refresh.getClient();
|
||||
scopes = refresh.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope();
|
||||
|
||||
user = userInfoService.getByUsernameAndClientId(refresh.getAuthenticationHolder().getAuthentication().getName(), tokenClient.getClientId());
|
||||
|
||||
token = refresh;
|
||||
|
||||
} catch (InvalidTokenException e2) {
|
||||
|
@ -111,15 +124,12 @@ public class IntrospectionEndpoint {
|
|||
}
|
||||
}
|
||||
|
||||
// clientID is the principal name in the authentication
|
||||
String clientId = p.getName();
|
||||
ClientDetailsEntity authClient = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (tokenClient != null && authClient != null) {
|
||||
if (authClient.isAllowIntrospection()) {
|
||||
if (introspectionAuthorizer.isIntrospectionPermitted(authClient, tokenClient, scopes)) {
|
||||
// if it's a valid token, we'll print out information on it
|
||||
model.addAttribute("entity", token);
|
||||
model.addAttribute("token", token);
|
||||
model.addAttribute("user", user);
|
||||
return "tokenIntrospection";
|
||||
} else {
|
||||
logger.error("Verify failed; client configuration or scope don't permit token introspection");
|
||||
|
|
Loading…
Reference in New Issue