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
|
||||
private ScopeClaimTranslationService translator;
|
||||
|
||||
private Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
|
||||
protected Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
|
@ -89,11 +89,6 @@ public class UserInfoView extends AbstractView {
|
|||
|
||||
response.setContentType("application/json");
|
||||
|
||||
Writer out;
|
||||
|
||||
try {
|
||||
|
||||
out = response.getWriter();
|
||||
|
||||
JsonObject authorizedClaims = null;
|
||||
JsonObject requestedClaims = null;
|
||||
|
@ -103,14 +98,21 @@ public class UserInfoView extends AbstractView {
|
|||
if (model.get("requestedClaims") != null) {
|
||||
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) {
|
||||
|
||||
|
||||
logger.error("IOException in UserInfoView.java: ", e);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,16 +16,23 @@
|
|||
******************************************************************************/
|
||||
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.service.UserInfoService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
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.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
@ -43,6 +50,9 @@ public class UserInfoEndpoint {
|
|||
|
||||
@Autowired
|
||||
private UserInfoService userInfoService;
|
||||
|
||||
@Autowired
|
||||
private ClientDetailsEntityService clientService;
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(UserInfoEndpoint.class);
|
||||
|
||||
|
@ -50,8 +60,10 @@ public class UserInfoEndpoint {
|
|||
* Get information about the user as specified in the accessToken included in this request
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_USER') and #oauth2.hasScope('openid')")
|
||||
@RequestMapping(value="/userinfo", method= {RequestMethod.GET, RequestMethod.POST}, produces = "application/json")
|
||||
public String getInfo(@RequestParam(value="claims", required=false) String claimsRequestJsonString, OAuth2Authentication auth, Model model) {
|
||||
@RequestMapping(value="/userinfo", method= {RequestMethod.GET, RequestMethod.POST}, produces = {"application/json", "application/jwt"})
|
||||
public String getInfo(@RequestParam(value="claims", required=false) String claimsRequestJsonString,
|
||||
@RequestHeader(value="Accept") String acceptHeader,
|
||||
OAuth2Authentication auth, Model model) {
|
||||
|
||||
if (auth == null) {
|
||||
logger.error("getInfo failed; no principal. Requester is not authorized.");
|
||||
|
@ -78,7 +90,22 @@ public class UserInfoEndpoint {
|
|||
|
||||
model.addAttribute("userInfo", userInfo);
|
||||
|
||||
return "userInfoView";
|
||||
// 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";
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue