Added Signed JWT support to UserInfo endpoint response, closes #593

pull/604/head
Justin Richer 2014-05-23 19:15:03 -04:00
parent 27e68f1d56
commit ffe1b29906
3 changed files with 131 additions and 13 deletions

View File

@ -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();
}
}
}

View File

@ -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);
}
}
/**

View File

@ -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";
}