Added Signed JWT support to UserInfo endpoint response, closes #593
parent
27e68f1d56
commit
ffe1b29906
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.mitre.openid.connect.view;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||||
|
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||||
|
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.nimbusds.jose.JWSAlgorithm;
|
||||||
|
import com.nimbusds.jose.JWSHeader;
|
||||||
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jricher
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component("userInfoJwtView")
|
||||||
|
public class UserInfoJwtView extends UserInfoView {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(UserInfoJwtView.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtSigningAndValidationService jwtService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationPropertiesBean config;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeOut(JsonObject json, Map<String, Object> model,
|
||||||
|
HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientDetailsEntity client = (ClientDetailsEntity)model.get("client");
|
||||||
|
|
||||||
|
// use the parser to import the user claims into the object
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
gson.toJson(json, writer);
|
||||||
|
|
||||||
|
JWTClaimsSet claims = JWTClaimsSet.parse(writer.toString());
|
||||||
|
|
||||||
|
claims.setAudience(Lists.newArrayList(client.getClientId()));
|
||||||
|
|
||||||
|
claims.setIssuer(config.getIssuer());
|
||||||
|
|
||||||
|
claims.setIssueTime(new Date());
|
||||||
|
|
||||||
|
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||||
|
|
||||||
|
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
||||||
|
if (client.getUserInfoSignedResponseAlg() != null) {
|
||||||
|
signingAlg = client.getUserInfoSignedResponseAlg();
|
||||||
|
}
|
||||||
|
|
||||||
|
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
|
||||||
|
|
||||||
|
jwtService.signJwt(signed);
|
||||||
|
|
||||||
|
Writer out = response.getWriter();
|
||||||
|
out.write(signed.serialize());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("IO Exception in UserInfoJwtView", e);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ public class UserInfoView extends AbstractView {
|
||||||
@Autowired
|
@Autowired
|
||||||
private ScopeClaimTranslationService translator;
|
private ScopeClaimTranslationService translator;
|
||||||
|
|
||||||
private Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
|
protected Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldSkipField(FieldAttributes f) {
|
public boolean shouldSkipField(FieldAttributes f) {
|
||||||
|
@ -89,11 +89,6 @@ public class UserInfoView extends AbstractView {
|
||||||
|
|
||||||
response.setContentType("application/json");
|
response.setContentType("application/json");
|
||||||
|
|
||||||
Writer out;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
out = response.getWriter();
|
|
||||||
|
|
||||||
JsonObject authorizedClaims = null;
|
JsonObject authorizedClaims = null;
|
||||||
JsonObject requestedClaims = null;
|
JsonObject requestedClaims = null;
|
||||||
|
@ -103,8 +98,15 @@ public class UserInfoView extends AbstractView {
|
||||||
if (model.get("requestedClaims") != null) {
|
if (model.get("requestedClaims") != null) {
|
||||||
requestedClaims = jsonParser.parse((String) model.get("requestedClaims")).getAsJsonObject();
|
requestedClaims = jsonParser.parse((String) model.get("requestedClaims")).getAsJsonObject();
|
||||||
}
|
}
|
||||||
gson.toJson(toJsonFromRequestObj(userInfo, scope, authorizedClaims, requestedClaims), out);
|
JsonObject json = toJsonFromRequestObj(userInfo, scope, authorizedClaims, requestedClaims);
|
||||||
|
|
||||||
|
writeOut(json, model, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void writeOut(JsonObject json, Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
try {
|
||||||
|
Writer out = response.getWriter();
|
||||||
|
gson.toJson(json, out);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
||||||
logger.error("IOException in UserInfoView.java: ", e);
|
logger.error("IOException in UserInfoView.java: ", e);
|
||||||
|
|
|
@ -16,16 +16,23 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.mitre.openid.connect.web;
|
package org.mitre.openid.connect.web;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||||
|
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||||
import org.mitre.openid.connect.model.UserInfo;
|
import org.mitre.openid.connect.model.UserInfo;
|
||||||
import org.mitre.openid.connect.service.UserInfoService;
|
import org.mitre.openid.connect.service.UserInfoService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
@ -44,14 +51,19 @@ public class UserInfoEndpoint {
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserInfoService userInfoService;
|
private UserInfoService userInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientDetailsEntityService clientService;
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(UserInfoEndpoint.class);
|
private static Logger logger = LoggerFactory.getLogger(UserInfoEndpoint.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information about the user as specified in the accessToken included in this request
|
* Get information about the user as specified in the accessToken included in this request
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("hasRole('ROLE_USER') and #oauth2.hasScope('openid')")
|
@PreAuthorize("hasRole('ROLE_USER') and #oauth2.hasScope('openid')")
|
||||||
@RequestMapping(value="/userinfo", method= {RequestMethod.GET, RequestMethod.POST}, produces = "application/json")
|
@RequestMapping(value="/userinfo", method= {RequestMethod.GET, RequestMethod.POST}, produces = {"application/json", "application/jwt"})
|
||||||
public String getInfo(@RequestParam(value="claims", required=false) String claimsRequestJsonString, OAuth2Authentication auth, Model model) {
|
public String getInfo(@RequestParam(value="claims", required=false) String claimsRequestJsonString,
|
||||||
|
@RequestHeader(value="Accept") String acceptHeader,
|
||||||
|
OAuth2Authentication auth, Model model) {
|
||||||
|
|
||||||
if (auth == null) {
|
if (auth == null) {
|
||||||
logger.error("getInfo failed; no principal. Requester is not authorized.");
|
logger.error("getInfo failed; no principal. Requester is not authorized.");
|
||||||
|
@ -78,6 +90,21 @@ public class UserInfoEndpoint {
|
||||||
|
|
||||||
model.addAttribute("userInfo", userInfo);
|
model.addAttribute("userInfo", userInfo);
|
||||||
|
|
||||||
|
// content negotiation
|
||||||
|
List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);
|
||||||
|
MediaType.sortBySpecificityAndQuality(mediaTypes);
|
||||||
|
|
||||||
|
MediaType jose = new MediaType("application", "jwt");
|
||||||
|
|
||||||
|
for (MediaType m : mediaTypes) {
|
||||||
|
if (!m.isWildcardType() && m.isCompatibleWith(jose)) {
|
||||||
|
ClientDetailsEntity client = clientService.loadClientByClientId(auth.getOAuth2Request().getClientId());
|
||||||
|
model.addAttribute("client", client);
|
||||||
|
|
||||||
|
return "userInfoJwtView";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return "userInfoView";
|
return "userInfoView";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue