update oidc+partey

This commit is contained in:
2022-05-12 09:32:54 +02:00
parent eb829bfa26
commit b876bad3e7
42 changed files with 3061 additions and 440 deletions
@@ -11,7 +11,7 @@ import org.springframework.stereotype.Service;
import com.google.common.collect.Maps;
import de.bstly.we.oidc.model.OidcAuthorizationCode;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationCode;
/**
* The Class OidcAuthorizationCodeManager.
@@ -0,0 +1,141 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationCode;
import de.bstly.we.oidc.model.OidcAuthorization;
import de.bstly.we.oidc.model.QOidcAuthorization;
import de.bstly.we.oidc.repository.OidcAuthorizationRepository;
/**
* The Class OidcAuthorizationManager.
*/
@Service
public class OidcAuthorizationManager {
@Autowired
private OidcAuthorizationRepository oidcAuthorizationRepository;
private QOidcAuthorization qOidcAuthorization = QOidcAuthorization.oidcAuthorization;
/**
* hold codes in memory
*/
private final Map<String, OidcAuthorizationCode> authorizationCodes = Maps.newHashMap();
/**
* Gets the authorization.
*
* @param clientId the client id
* @param subject the subject
* @return the authorization
*/
public OidcAuthorization getAuthorization(Long clientId, Long subject) {
return oidcAuthorizationRepository
.findOne(qOidcAuthorization.client.eq(clientId).and(qOidcAuthorization.subject.eq(subject)))
.orElse(null);
}
/**
* Checks if is authorized.
*
* @param clientId the client id
* @param subject the subject
* @param scopes the scopes
* @return true, if is authorized
*/
public boolean isAuthorized(Long clientId, Long subject, Set<String> scopes) {
OidcAuthorization oidcAuthorization = getAuthorization(clientId, subject);
if (oidcAuthorization == null) {
return false;
}
return scopes == null
|| oidcAuthorization.getScopes() != null && oidcAuthorization.getScopes().containsAll(scopes);
}
/**
* Authorize.
*
* @param clientId the client id
* @param subject the subject
* @param scopes the scopes
* @return the oidc authorization
*/
public OidcAuthorization authorize(Long clientId, Long subject, Set<String> scopes) {
OidcAuthorization oidcAuthorization = getAuthorization(clientId, subject);
if (oidcAuthorization == null) {
oidcAuthorization = new OidcAuthorization();
oidcAuthorization.setClient(clientId);
oidcAuthorization.setSubject(subject);
oidcAuthorization.setScopes(Sets.newHashSet());
}
oidcAuthorization.getScopes().addAll(scopes);
return oidcAuthorizationRepository.save(oidcAuthorization);
}
/**
* Removes the authorization.
*
* @param clientId the client id
* @param subject the subject
*/
public void removeAuthorization(Long clientId, Long subject) {
OidcAuthorization oidcAuthorization = getAuthorization(clientId, subject);
if (oidcAuthorization != null) {
oidcAuthorizationRepository.delete(oidcAuthorization);
}
}
/**
* Creates the code.
*
* @param clientId the client id
* @param redirectUri the redirect uri
* @param scopes the scopes
* @param subject the subject
* @param nonce the nonce
* @param springSession the spring session
* @return the oidc authorization code
*/
public OidcAuthorizationCode createCode(String clientId, URI redirectUri, Set<String> scopes, Long subject,
String nonce, String springSession) {
OidcAuthorizationCode authorizationCode = new OidcAuthorizationCode(clientId, redirectUri, scopes, subject,
nonce);
authorizationCode.setSpringSession(springSession);
authorizationCodes.put(authorizationCode.getCode(), authorizationCode);
return authorizationCode;
}
/**
* Gets the code.
*
* @param code the code
* @return the code
*/
public OidcAuthorizationCode getCode(String code) {
return authorizationCodes.get(code);
}
/**
* Removes the code.
*
* @param code the code
*/
public void removeCode(String code) {
authorizationCodes.remove(code);
}
}
@@ -5,19 +5,26 @@ package de.bstly.we.oidc.businesslogic;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import de.bstly.we.oidc.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.controller.model.OidcClientInfo;
import de.bstly.we.oidc.model.OidcAuthorization;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.model.QOidcClient;
import de.bstly.we.oidc.repository.OidcClientRepository;
@@ -33,8 +40,32 @@ public class OidcClientManager {
@Autowired
private OidcClientRepository oidcClientRepository;
@Autowired
private OidcAuthorizationManager oidcAuthorizationManager;
private QOidcClient qOidcClient = QOidcClient.oidcClient;
@Value("${oidc.provider.issuer:}")
private String oidcIssuer;
/**
* Gets the issuer.
*
* @param request the request
* @return the issuer
*/
public String getIssuer(HttpServletRequest request) {
String issuer = oidcIssuer;
if (!StringUtils.hasText(issuer)) {
issuer = request.getScheme() + "://" + request.getServerName();
if (request.getServerPort() != 443 && request.getServerPort() != 80) {
issuer += ":" + request.getServerPort();
}
}
return issuer;
}
/**
* Creates the client.
*
@@ -112,6 +143,16 @@ public class OidcClientManager {
return oidcClientRepository.save(oidcClient);
}
/**
* Gets the.
*
* @param id the id
* @return the oidc client
*/
public OidcClient get(Long id) {
return oidcClientRepository.findById(id).orElse(null);
}
/**
* Gets the by client id.
*
@@ -180,4 +221,38 @@ public class OidcClientManager {
Sort sort = descending ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending();
return oidcClientRepository.findAll(PageRequest.of(page, size, sort));
}
/**
* Gets the client info.
*
* @param client the client
* @param subject the subject
* @return the client info
*/
public OidcClientInfo getClientInfo(OidcClient client, Long subject) {
OidcClientInfo info = new OidcClientInfo();
info.setClientId(client.getClientId());
info.setName(client.getClientName());
info.setLoginUrl(client.getLoginUrl());
info.setFrontchannelLogoutUri(client.getFrontchannelLogoutUri());
info.setFrontchannelLogoutSessionRequired(client.isFrontchannelLogoutSessionRequired());
info.setBackchannelLogout(StringUtils.hasText(client.getBackchannelLogoutUri()));
info.setAuthorize(client.isAuthorize());
info.setScopes(client.getScopes());
info.setSessions(Lists.newArrayList());
if (info.getScopes() == null) {
info.setScopes(Sets.newHashSet());
}
OidcAuthorization authorization = oidcAuthorizationManager.getAuthorization(client.getId(), subject);
if (authorization != null) {
info.setAuthorizedScopes(authorization.getScopes());
}
return info;
}
}
@@ -0,0 +1,442 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import de.bstly.we.jwt.businesslogic.JwtKeyManager;
import de.bstly.we.jwt.model.JwtKey;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcSession;
import de.bstly.we.oidc.model.QOidcSession;
import de.bstly.we.oidc.repository.OidcSessionRepository;
import reactor.core.publisher.Mono;
/**
* The Class OidcSessionManager.
*/
@Service
public class OidcSessionManager {
private Logger logger = LoggerFactory.getLogger(OidcSessionManager.class);
@Autowired
private OidcSessionRepository oidcSessionRepository;
@Autowired
private OidcClientManager oidcRegisteredClientManager;
@Autowired
private JwtKeyManager jwtKeyManager;
protected WebClient webClient = WebClient.builder().build();
private QOidcSession qOidcSession = QOidcSession.oidcSession;
public static final int SID_LENGTH = 32;
public static final int JWT_ID_LENGTH = 32;
/**
* Creates the sid.
*
* @return the string
*/
public String createSid() {
String sid = new StringBuilder(RandomStringUtils.random(SID_LENGTH, true, true)).insert(8, "-").insert(13, "-")
.insert(18, "-").insert(23, "-").toString();
while (oidcSessionRepository.exists(qOidcSession.sid.eq(sid))) {
sid = new StringBuilder(RandomStringUtils.random(SID_LENGTH, true, true)).insert(8, "-").insert(13, "-")
.insert(18, "-").insert(23, "-").toString();
}
return sid;
}
/**
* Creates the session.
*
* @param clientId the client id
* @param subject the subject
* @param idToken the id token
* @param sid the sid
* @param springSession the spring session
* @return the oidc session
* @throws JOSEException the JOSE exception
*/
public OidcSession createSession(Long clientId, Long subject, String idToken, String sid, String springSession)
throws JOSEException {
OidcSession session = new OidcSession();
session.setClientId(clientId);
session.setSubject(subject);
session.setIdToken(idToken);
session.setSid(sid);
session.setSpringSession(springSession);
return oidcSessionRepository.save(session);
}
/**
* Gets the by sid.
*
* @param sid the sid
* @return the by sid
*/
public OidcSession getBySid(String sid) {
Assert.isTrue(StringUtils.hasText(sid), "No sid defined!");
return oidcSessionRepository.findOne(qOidcSession.sid.eq(sid)).orElse(null);
}
/**
* Gets the by id token.
*
* @param idToken the id token
* @return the by id token
*/
public OidcSession getByIdToken(String idToken) {
return oidcSessionRepository.findOne(qOidcSession.idToken.eq(idToken)).orElse(null);
}
/**
* Gets the all by spring session.
*
* @param springSession the spring session
* @return the all by spring session
*/
public List<OidcSession> getAllBySpringSession(String springSession) {
return Lists.newArrayList(oidcSessionRepository.findAll(qOidcSession.springSession.eq(springSession)));
}
/**
* Gets the all by target.
*
* @param clientId the client id
* @return the all by target
*/
public List<OidcSession> getAllByTarget(Long clientId) {
return Lists.newArrayList(oidcSessionRepository.findAll(qOidcSession.client.eq(clientId)));
}
/**
* Gets the all by subject.
*
* @param subject the subject
* @return the all by subject
*/
public List<OidcSession> getAllBySubject(Long subject) {
return Lists.newArrayList(oidcSessionRepository.findAll(qOidcSession.subject.eq(subject)));
}
/**
* Gets the all by target and subject.
*
* @param clientId the client id
* @param subject the subject
* @return the all by target and subject
*/
public List<OidcSession> getAllByTargetAndSubject(Long clientId, Long subject) {
return Lists.newArrayList(
oidcSessionRepository.findAll(qOidcSession.client.eq(clientId).and(qOidcSession.subject.eq(subject))));
}
/**
* Delete.
*
* @param session the session
*/
public void delete(OidcSession session) {
if (oidcSessionRepository.existsById(session.getId())) {
oidcSessionRepository.delete(session);
}
}
/**
* Delete by sid.
*
* @param sid the sid
*/
public void deleteBySid(String sid) {
OidcSession session = getBySid(sid);
if (session != null) {
delete(session);
}
}
/**
* Delete by id token.
*
* @param idToken the id token
*/
public void deleteByIdToken(String idToken) {
OidcSession session = getByIdToken(idToken);
if (session != null) {
delete(session);
}
}
/**
* Delete all by spring session.
*
* @param springSession the spring session
*/
public void deleteAllBySpringSession(String springSession) {
for (OidcSession token : getAllBySpringSession(springSession)) {
delete(token);
}
}
/**
* Delete all by target.
*
* @param target the target
*/
public void deleteAllByTarget(Long target) {
for (OidcSession token : getAllByTarget(target)) {
delete(token);
}
}
/**
* Delete all by subject.
*
* @param subject the subject
*/
public void deleteAllBySubject(Long subject) {
for (OidcSession token : getAllBySubject(subject)) {
delete(token);
}
}
/**
* Backchannel logout.
*
* @param sid the sid
* @param issuer the issuer
*/
public void backchannelLogout(String sid, String issuer) {
OidcSession session = getBySid(sid);
Assert.notNull(session, "invalid sid!");
OidcClient client = oidcRegisteredClientManager.get(session.getClientId());
if (client == null) {
logger.error("OidcSession '" + session.getId() + "' (subject: " + session.getSubject()
+ ") with invalid OidcRegisteredClient: " + session.getClientId());
return;
}
try {
sendBackchannelLogout(session.getSubject(), sid, issuer, client);
oidcSessionRepository.delete(session);
} catch (JOSEException e) {
logger.warn("JOSEException on backchannelLogout!", e);
}
}
/**
* Backchannel logout.
*
* @param clientId the client id
* @param subject the subject
* @param issuer the issuer
*/
public void backchannelLogout(Long clientId, Long subject, String issuer) {
List<OidcSession> sessions = getAllByTargetAndSubject(clientId, subject);
if (!sessions.isEmpty()) {
OidcSession session = sessions.get(0);
OidcClient client = oidcRegisteredClientManager.get(session.getClientId());
if (client != null) {
try {
sendBackchannelLogout(subject, null, issuer, client);
} catch (JOSEException e) {
logger.warn("JOSEException on backchannelLogout!", e);
}
} else {
logger.error("OidcSession '" + session.getId() + "' (subject: " + session.getSubject()
+ ") with invalid OidcRegisteredClient: " + session.getClientId());
}
oidcSessionRepository.deleteAll(sessions);
}
}
/**
* Backchannel logout.
*
* @param subject the subject
* @param issuer the issuer
*/
public void backchannelLogout(Long subject, String issuer) {
Set<Long> clients = Sets.newHashSet();
for (OidcSession session : getAllBySubject(subject)) {
OidcClient client = oidcRegisteredClientManager.get(session.getClientId());
if (client == null) {
logger.error("OidcSession '" + session.getId() + "' (subject: " + session.getSubject()
+ ") with invalid OidcRegisteredClient: " + session.getClientId());
continue;
}
if (!clients.contains(client.getId())) {
try {
sendBackchannelLogout(subject, null, issuer, client);
clients.add(client.getId());
} catch (JOSEException e) {
logger.warn("JOSEException on backchannelLogout!", e);
}
}
oidcSessionRepository.delete(session);
}
}
/**
* Backchannel logout spring.
*
* @param springSessionId the spring session id
* @param issuer the issuer
*/
public void backchannelLogoutSpring(String springSessionId, String issuer) {
for (OidcSession session : getAllBySpringSession(springSessionId)) {
OidcClient client = oidcRegisteredClientManager.get(session.getClientId());
if (client == null) {
logger.error("OidcSession '" + session.getId() + "' (subject: " + session.getSubject()
+ ") with invalid OidcRegisteredClient: " + session.getClientId());
continue;
}
try {
sendBackchannelLogout(session.getSubject(), session.getSid(), issuer, client);
oidcSessionRepository.delete(session);
} catch (JOSEException e) {
logger.warn("JOSEException on backchannelLogout!", e);
}
}
}
/**
* Send backchannel logout.
*
* @param subject the subject
* @param sid the sid
* @param issuer the issuer
* @param client the client
* @throws JOSEException the JOSE exception
*/
// @Async("threadPoolTaskExecutor")
public void sendBackchannelLogout(Long subject, String sid, String issuer, OidcClient client) throws JOSEException {
if (!StringUtils.hasText(client.getBackchannelLogoutUri())) {
logger.warn("Could not send backchannel logout for '" + subject
+ "' without backchannel_logout_uri of OidcRegisteredClient '" + client.getId()
+ "' not possible!");
return;
}
SignedJWT logoutToken = createBackchannelLogoutToken(subject, sid, issuer, client);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("logout_token", logoutToken.serialize());
ResponseEntity<?> response = null;
try {
response = webClient.method(HttpMethod.POST).uri(client.getBackchannelLogoutUri())
.contentType(MediaType.APPLICATION_FORM_URLENCODED).body(BodyInserters.fromFormData(formData))
.retrieve().onStatus(HttpStatus::isError, error -> Mono.empty()).toBodilessEntity().block();
} catch (Exception e) {
logger.warn("backchannelLogout POST '" + client.getBackchannelLogoutUri() + "' failed", e);
}
if (response != null && !response.getStatusCode().equals(HttpStatus.OK)) {
logger.info("backchannelLogout for '" + subject + "' failed with status: " + response.getStatusCode()
+ " from " + client.getBackchannelLogoutUri() + "(client: " + client.getId() + ")");
}
}
/**
* Creates the backchannel logout token.
*
* @param subject the subject
* @param sid the sid
* @param issuer the issuer
* @param client the client
* @return the signed JWT
* @throws JOSEException the JOSE exception
*/
protected SignedJWT createBackchannelLogoutToken(Long subject, String sid, String issuer, OidcClient client)
throws JOSEException {
if (!StringUtils.hasText(client.getBackchannelLogoutUri())) {
logger.warn("Creation of logout token '" + subject
+ "' without backchannel_logout_uri of OidcRegisteredClient '" + client.getId()
+ "' not possible!");
return null;
}
JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder();
claimsSetBuilder.subject(String.valueOf(subject));
claimsSetBuilder.issuer(issuer);
claimsSetBuilder.audience(client.getClientId());
claimsSetBuilder.issueTime(new Date());
claimsSetBuilder.jwtID(RandomStringUtils.random(JWT_ID_LENGTH, true, true));
if (StringUtils.hasText(sid)) {
claimsSetBuilder.claim("sid", sid);
}
Map<String, Object> events = Maps.newHashMap();
events.put("http://schemas.openid.net/event/backchannel-logout", Maps.newHashMap());
claimsSetBuilder.claim("events", events);
JwtKey jwtKey = jwtKeyManager.getLatest(OidcTokenManager.OIDC_JWT_KEY_NAME, false);
if (jwtKey == null) {
throw new JOSEException("No jwtKey found!");
}
JWK jwk = jwtKeyManager.parseKey(jwtKey);
JWSSigner jwsSigner = jwtKeyManager.createSigner(jwtKey);
if (jwsSigner == null) {
throw new JOSEException("Signer could not be created!");
}
JWSAlgorithm algorithm = jwtKeyManager.getJwsAlgorithm(jwtKey);
if (algorithm == null) {
algorithm = JWSAlgorithm.HS512;
}
JWSHeader.Builder headerBuilder = new JWSHeader.Builder(algorithm);
headerBuilder.keyID(jwk.getKeyID());
SignedJWT jwt = new SignedJWT(headerBuilder.build(), claimsSetBuilder.build());
jwt.sign(jwsSigner);
return jwt;
}
}
@@ -7,26 +7,35 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.beust.jcommander.internal.Maps;
import com.google.common.collect.Lists;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
import com.nimbusds.jwt.SignedJWT;
import com.querydsl.core.BooleanBuilder;
import de.bstly.we.businesslogic.PermissionManager;
import de.bstly.we.businesslogic.QuotaManager;
@@ -39,6 +48,7 @@ import de.bstly.we.model.Permission;
import de.bstly.we.model.Quota;
import de.bstly.we.model.User;
import de.bstly.we.model.UserProfileField;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcToken;
import de.bstly.we.oidc.model.QOidcToken;
@@ -50,12 +60,16 @@ import de.bstly.we.oidc.repository.OidcTokenRepository;
@Service
public class OidcTokenManager implements SmartInitializingSingleton {
private Logger logger = LoggerFactory.getLogger(OidcTokenManager.class);
public static final int ACCESS_TOKEN_LENGTH = 64;
public static final int REFRESH_TOKEN_LENGTH = 64;
public static final String OIDC_JWT_KEY_NAME = "oidc";
public static final String BEARER_TOKEN_TYPE = "Bearer";
@Autowired
private OidcTokenRepository tokenRepository;
private OidcTokenRepository oidcTokenRepository;
@Autowired
private UserManager userManager;
@Autowired
@@ -104,11 +118,40 @@ public class OidcTokenManager implements SmartInitializingSingleton {
* @return the oidc token
*/
public OidcToken createToken(OidcClient client, Long userId) {
return createToken(client, userId, null);
}
/**
* Creates the token.
*
* @param client the client
* @param userId the user id
* @param refreshToken the refresh token
* @return the oidc token
*/
public OidcToken createToken(OidcClient client, Long userId, boolean refreshToken) {
return createToken(client, userId,
refreshToken ? RandomStringUtils.random(REFRESH_TOKEN_LENGTH, true, true) : null);
}
/**
* Creates the token.
*
* @param client the client
* @param userId the user id
* @param refreshToken the refresh token
* @return the oidc token
*/
public OidcToken createToken(OidcClient client, Long userId, String refreshToken) {
OidcToken token = new OidcToken();
token.setUserId(userId);
token.setAccessToken(RandomStringUtils.random(ACCESS_TOKEN_LENGTH, true, true));
if (StringUtils.hasText(refreshToken)) {
token.setRefreshToken(refreshToken);
}
token.setExpiresIn(client.getTokenLifetime());
return tokenRepository.save(token);
token.setCreated(Instant.now());
return oidcTokenRepository.save(token);
}
/**
@@ -119,18 +162,18 @@ public class OidcTokenManager implements SmartInitializingSingleton {
* @param nonce the nonce
* @param scopes the scopes
* @param issuer the issuer
* @param sid the sid
* @return the oidc token
* @throws JOSEException the JOSE exception
*/
public OidcToken createTokenWithIdToken(OidcClient client, Long userId, String nonce, Set<String> scopes,
String issuer) throws JOSEException {
OidcToken token = new OidcToken();
token.setClient(client.getId());
String issuer, String sid) throws JOSEException {
User user = userManager.get(userId);
Assert.notNull(user, "User does not exist!");
OidcToken token = createToken(client, client.getId(),
client.getAuthorizationGrantTypes().contains(OidcAuthorizationGrantType.refresh_token));
token.setUserId(user.getId());
token.setAccessToken(RandomStringUtils.random(ACCESS_TOKEN_LENGTH, true, true));
token.setExpiresIn(client.getTokenLifetime());
@@ -147,6 +190,10 @@ public class OidcTokenManager implements SmartInitializingSingleton {
claimsSetBuilder.claim("nonce", nonce);
}
if (StringUtils.hasText(sid)) {
claimsSetBuilder.claim("sid", sid);
}
JwtKey jwtKey = jwtKeyManager.getLatest(OIDC_JWT_KEY_NAME, false);
if (jwtKey == null) {
@@ -162,7 +209,7 @@ public class OidcTokenManager implements SmartInitializingSingleton {
token.setIdToken(jwt.serialize());
return tokenRepository.save(token);
return oidcTokenRepository.save(token);
}
/**
@@ -234,15 +281,112 @@ public class OidcTokenManager implements SmartInitializingSingleton {
* @return the by access token
*/
public OidcToken getByAccessToken(String accessToken) {
return tokenRepository.findOne(qOidcToken.accessToken.eq(accessToken)).orElse(null);
return oidcTokenRepository.findOne(qOidcToken.accessToken.eq(accessToken)).orElse(null);
}
/**
* Gets the jwk set.
* Gets the last by refresh token.
*
* @return the jwk set
* @param refreshToken the refresh token
* @return the last by refresh token
*/
public JWKSet getJwkSet() {
return jwtKeyManager.getJwkSet(OIDC_JWT_KEY_NAME, false);
public OidcToken getLastByRefreshToken(String refreshToken) {
List<OidcToken> token = getAllByRefreshToken(refreshToken);
if (token.isEmpty()) {
return null;
}
return token.get(0);
}
/**
* Gets the all by refresh token.
*
* @param refreshToken the refresh token
* @return the all by refresh token
*/
public List<OidcToken> getAllByRefreshToken(String refreshToken) {
return Lists.newArrayList(oidcTokenRepository.findAll(qOidcToken.refreshToken.eq(refreshToken),
Sort.by(Sort.Direction.DESC, "created")));
}
/**
* Delete.
*
* @param token the token
*/
public void delete(OidcToken token) {
if (oidcTokenRepository.existsById(token.getId())) {
oidcTokenRepository.delete(token);
}
}
/**
* Delete by access token.
*
* @param accessToken the access token
*/
public void deleteByAccessToken(String accessToken) {
OidcToken token = getByAccessToken(accessToken);
if (token != null) {
delete(token);
}
}
/**
* Delete all by refresh token.
*
* @param refreshToken the refresh token
*/
public void deleteAllByRefreshToken(String refreshToken) {
for (OidcToken token : oidcTokenRepository.findAll(qOidcToken.refreshToken.eq(refreshToken))) {
delete(token);
}
}
/**
* Delete all by target.
*
* @param userId the user id
*/
public void deleteAllByTarget(Long userId) {
for (OidcToken token : oidcTokenRepository.findAll(qOidcToken.userId.eq(userId))) {
delete(token);
}
}
/**
* Cron.
*/
@Scheduled(cron = "${openid-provider.token.cleanup.cron:0 0 0,12 * * * }")
public void cron() {
deleteExpired(false);
}
/**
* Delete expired.
*
* @param includeRefreshToken the include refresh token
*/
public void deleteExpired(boolean includeRefreshToken) {
BooleanBuilder predicate = new BooleanBuilder();
predicate.and(qOidcToken.expiresIn.gt(0L));
if (!includeRefreshToken) {
predicate.and(qOidcToken.refreshToken.isEmpty().or(qOidcToken.refreshToken.isNull()));
}
Pageable pageable = PageRequest.of(0, 500, Sort.by("id"));
Page<OidcToken> page;
do {
page = oidcTokenRepository.findAll(predicate.getValue(), pageable);
for (OidcToken oidcToken : page.getContent()) {
if (oidcToken.getCreated()
.isBefore(Instant.now().minus(oidcToken.getExpiresIn(), ChronoUnit.SECONDS))) {
logger.debug(
"delete expired OidcToken: " + oidcToken.getId() + " [" + oidcToken.getAccessToken() + "]");
delete(oidcToken);
}
}
pageable = page.nextPageable();
} while (page.hasNext());
}
}
@@ -0,0 +1,113 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.exception;
import java.net.URI;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationErrorCode;
/**
* The Class InvalidAuthorizationRequestException.
*/
public class InvalidAuthorizationRequestException extends RuntimeException {
/**
* default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private URI redirectUri;
private OidcAuthorizationErrorCode errorCode;
private String errorDescription;
private String state;
/**
* Instantiates a new invalid authorization request exception.
*
* @param redirectUri the redirect uri
* @param errorCode the error code
* @param errorDescription the error description
* @param state the state
*/
public InvalidAuthorizationRequestException(URI redirectUri, OidcAuthorizationErrorCode errorCode,
String errorDescription, String state) {
super(errorDescription);
this.redirectUri = redirectUri;
this.errorCode = errorCode;
this.errorDescription = errorDescription;
this.state = state;
}
/**
* Gets the redirect uri.
*
* @return the redirect uri
*/
public URI getRedirectUri() {
return redirectUri;
}
/**
* Sets the redirect uri.
*
* @param redirectUri the new redirect uri
*/
public void setRedirectUri(URI redirectUri) {
this.redirectUri = redirectUri;
}
/**
* Gets the error code.
*
* @return the error code
*/
public OidcAuthorizationErrorCode getErrorCode() {
return errorCode;
}
/**
* Sets the error code.
*
* @param errorCode the new error code
*/
public void setErrorCode(OidcAuthorizationErrorCode errorCode) {
this.errorCode = errorCode;
}
/**
* Gets the error description.
*
* @return the error description
*/
public String getErrorDescription() {
return errorDescription;
}
/**
* Sets the error description.
*
* @param errorDescription the new error description
*/
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
/**
* Gets the state.
*
* @return the state
*/
public String getState() {
return state;
}
/**
* Sets the state.
*
* @param state the new state
*/
public void setState(String state) {
this.state = state;
}
}
@@ -0,0 +1,69 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.exception;
import de.bstly.we.oidc.businesslogic.model.OidcTokenErrorCode;
/**
* The Class InvalidTokenRequestException.
*/
public class InvalidTokenRequestException extends RuntimeException {
/**
* default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private OidcTokenErrorCode errorCode;
private String errorDescription;
/**
* Instantiates a new invalid token request exception.
*
* @param errorCode the error code
* @param errorDescription the error description
*/
public InvalidTokenRequestException(OidcTokenErrorCode errorCode, String errorDescription) {
super(errorDescription);
this.errorCode = errorCode;
this.errorDescription = errorDescription;
}
/**
* Gets the error code.
*
* @return the error code
*/
public OidcTokenErrorCode getErrorCode() {
return errorCode;
}
/**
* Sets the error code.
*
* @param errorCode the new error code
*/
public void setErrorCode(OidcTokenErrorCode errorCode) {
this.errorCode = errorCode;
}
/**
* Gets the error description.
*
* @return the error description
*/
public String getErrorDescription() {
return errorDescription;
}
/**
* Sets the error description.
*
* @param errorDescription the new error description
*/
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
}
@@ -1,7 +1,7 @@
/**
*
*/
package de.bstly.we.oidc.model;
package de.bstly.we.oidc.businesslogic.model;
import java.net.URI;
import java.time.Instant;
@@ -25,6 +25,7 @@ public class OidcAuthorizationCode {
private final Instant expiry;
private final Long userId;
private final String nonce;
private String springSession;
/**
* Instantiates a new oidc authorization code.
@@ -108,4 +109,22 @@ public class OidcAuthorizationCode {
return nonce;
}
/**
* Gets the spring session.
*
* @return the spring session
*/
public String getSpringSession() {
return springSession;
}
/**
* Sets the spring session.
*
* @param springSession the new spring session
*/
public void setSpringSession(String springSession) {
this.springSession = springSession;
}
}
@@ -1,7 +1,7 @@
/**
*
*/
package de.bstly.we.oidc.model;
package de.bstly.we.oidc.businesslogic.model;
/**
* The Enum OidcAuthorizationErrorCode.
@@ -1,11 +1,11 @@
/**
*
*/
package de.bstly.we.oidc.model;
package de.bstly.we.oidc.businesslogic.model;
/**
* The Enum OidcAuthorizationGrantType.
*/
public enum OidcAuthorizationGrantType {
authorization_code, client_credentials
authorization_code, client_credentials, refresh_token
}
@@ -0,0 +1,282 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.model;
import java.net.URI;
/**
* The Class OidcAuthorizationRequest.
*/
public class OidcAuthorizationRequest {
private String code;
private final String client_id;
private final String response_type;
private final URI redirect_uri;
private final String scope;
private String state;
private String nonce;
private String display;
private String prompt;
private String max_age;
private String ui_locales;
private String id_token_hint;
private String login_hint;
private String acr_values;
/**
* Instantiates a new oidc authorization request.
*
* @param code the code
* @param client_id the client id
* @param response_type the response type
* @param redirect_uri the redirect uri
* @param scope the scope
* @param state the state
* @param nonce the nonce
* @param display the display
* @param prompt the prompt
* @param max_age the max age
* @param ui_locales the ui locales
* @param id_token_hint the id token hint
* @param login_hint the login hint
* @param acr_values the acr values
*/
public OidcAuthorizationRequest(String code, String client_id, String response_type, URI redirect_uri, String scope,
String state, String nonce, String display, String prompt, String max_age, String ui_locales,
String id_token_hint, String login_hint, String acr_values) {
super();
this.code = code;
this.client_id = client_id;
this.response_type = response_type;
this.redirect_uri = redirect_uri;
this.scope = scope;
this.state = state;
this.nonce = nonce;
this.display = display;
this.prompt = prompt;
this.max_age = max_age;
this.ui_locales = ui_locales;
this.id_token_hint = id_token_hint;
this.login_hint = login_hint;
this.acr_values = acr_values;
}
/**
* Gets the code.
*
* @return the code
*/
public String getCode() {
return code;
}
/**
* Sets the code.
*
* @param code the new code
*/
public void setCode(String code) {
this.code = code;
}
/**
* Gets the client id.
*
* @return the client id
*/
public String getClient_id() {
return client_id;
}
/**
* Gets the response type.
*
* @return the response type
*/
public String getResponse_type() {
return response_type;
}
/**
* Gets the redirect uri.
*
* @return the redirect uri
*/
public URI getRedirect_uri() {
return redirect_uri;
}
/**
* Gets the scope.
*
* @return the scope
*/
public String getScope() {
return scope;
}
/**
* Gets the state.
*
* @return the state
*/
public String getState() {
return state;
}
/**
* Sets the state.
*
* @param state the new state
*/
public void setState(String state) {
this.state = state;
}
/**
* Gets the display.
*
* @return the display
*/
public String getDisplay() {
return display;
}
/**
* Sets the display.
*
* @param display the new display
*/
public void setDisplay(String display) {
this.display = display;
}
/**
* Gets the nonce.
*
* @return the nonce
*/
public String getNonce() {
return nonce;
}
/**
* Sets the nonce.
*
* @param nonce the new nonce
*/
public void setNonce(String nonce) {
this.nonce = nonce;
}
/**
* Gets the prompt.
*
* @return the prompt
*/
public String getPrompt() {
return prompt;
}
/**
* Sets the prompt.
*
* @param prompt the new prompt
*/
public void setPrompt(String prompt) {
this.prompt = prompt;
}
/**
* Gets the max age.
*
* @return the max age
*/
public String getMax_age() {
return max_age;
}
/**
* Sets the max age.
*
* @param max_age the new max age
*/
public void setMax_age(String max_age) {
this.max_age = max_age;
}
/**
* Gets the ui locales.
*
* @return the ui locales
*/
public String getUi_locales() {
return ui_locales;
}
/**
* Sets the ui locales.
*
* @param ui_locales the new ui locales
*/
public void setUi_locales(String ui_locales) {
this.ui_locales = ui_locales;
}
/**
* Gets the id token hint.
*
* @return the id token hint
*/
public String getId_token_hint() {
return id_token_hint;
}
/**
* Sets the id token hint.
*
* @param id_token_hint the new id token hint
*/
public void setId_token_hint(String id_token_hint) {
this.id_token_hint = id_token_hint;
}
/**
* Gets the login hint.
*
* @return the login hint
*/
public String getLogin_hint() {
return login_hint;
}
/**
* Sets the login hint.
*
* @param login_hint the new login hint
*/
public void setLogin_hint(String login_hint) {
this.login_hint = login_hint;
}
/**
* Gets the acr values.
*
* @return the acr values
*/
public String getAcr_values() {
return acr_values;
}
/**
* Sets the acr values.
*
* @param acr_values the new acr values
*/
public void setAcr_values(String acr_values) {
this.acr_values = acr_values;
}
}
@@ -0,0 +1,31 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.model;
/**
* The Enum OidcAuthorizationResponseType.
*/
public enum OidcAuthorizationResponseType {
AUTHORIZATION_CODE("code");
private final String authorizationResponseType;
/**
* Instantiates a new oidc authorization response type.
*
* @param authorizationGrantType the authorization grant type
*/
OidcAuthorizationResponseType(String authorizationGrantType) {
this.authorizationResponseType = authorizationGrantType;
}
/**
* Gets the authorization response type.
*
* @return the authorization response type
*/
public String getAuthorizationResponseType() {
return authorizationResponseType;
}
}
@@ -1,7 +1,7 @@
/**
*
*/
package de.bstly.we.oidc.model;
package de.bstly.we.oidc.businesslogic.model;
/**
* The Enum OidcClientAuthenticationMethod.
@@ -1,7 +1,7 @@
/**
*
*/
package de.bstly.we.oidc.model;
package de.bstly.we.oidc.businesslogic.model;
/**
* The Enum OidcTokenErrorCode.
@@ -1,7 +1,7 @@
/**
*
*/
package de.bstly.we.oidc.model;
package de.bstly.we.oidc.businesslogic.model;
import java.net.URI;
@@ -14,6 +14,7 @@ public class OidcTokenRequest {
private final OidcAuthorizationGrantType grant_type;
private String client_id;
private String client_secret;
private String refresh_token;
private final URI redirect_uri;
private final String scope;
@@ -24,16 +25,18 @@ public class OidcTokenRequest {
* @param grant_type the grant type
* @param client_id the client id
* @param client_secret the client secret
* @param refresh_token the refresh token
* @param redirect_uri the redirect uri
* @param scope the scope
*/
public OidcTokenRequest(String code, OidcAuthorizationGrantType grant_type, String client_id, String client_secret,
URI redirect_uri, String scope) {
String refresh_token, URI redirect_uri, String scope) {
super();
this.code = code;
this.grant_type = grant_type;
this.client_id = client_id;
this.client_secret = client_secret;
this.refresh_token = refresh_token;
this.redirect_uri = redirect_uri;
this.scope = scope;
}
@@ -74,6 +77,24 @@ public class OidcTokenRequest {
this.client_secret = client_secret;
}
/**
* Gets the refresh token.
*
* @return the refresh token
*/
public String getRefresh_token() {
return refresh_token;
}
/**
* Sets the refresh token.
*
* @param refresh_token the new refresh token
*/
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
/**
* Gets the code.
*
@@ -1,7 +1,7 @@
/**
*
*/
package de.bstly.we.oidc.model;
package de.bstly.we.oidc.businesslogic.model;
/**
* The Class OidcTokenResponse.
@@ -0,0 +1,50 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.model;
/**
* The Class OidcTokenRevokeRequest.
*/
public class OidcTokenRevokeRequest {
private String token;
private String token_type_hint;
/**
* Gets the token.
*
* @return the token
*/
public String getToken() {
return token;
}
/**
* Sets the token.
*
* @param token the new token
*/
public void setToken(String token) {
this.token = token;
}
/**
* Gets the token type hint.
*
* @return the token type hint
*/
public String getToken_type_hint() {
return token_type_hint;
}
/**
* Sets the token type hint.
*
* @param token_type_hint the new token type hint
*/
public void setToken_type_hint(String token_type_hint) {
this.token_type_hint = token_type_hint;
}
}
@@ -24,10 +24,10 @@ import com.google.common.collect.Sets;
import de.bstly.we.controller.BaseController;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.controller.model.OidcClientModel;
import de.bstly.we.oidc.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.repository.OidcClientRepository;
/**
@@ -13,25 +13,31 @@ import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import com.google.common.collect.Sets;
import de.bstly.we.businesslogic.PermissionManager;
import de.bstly.we.businesslogic.Permissions;
import de.bstly.we.oidc.businesslogic.OidcAuthorizationCodeManager;
import de.bstly.we.oidc.businesslogic.OidcAuthorizationManager;
import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.model.OidcAuthorizationCode;
import de.bstly.we.oidc.model.OidcAuthorizationErrorCode;
import de.bstly.we.oidc.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.model.OidcAuthorizationResponseType;
import de.bstly.we.oidc.businesslogic.exception.InvalidAuthorizationRequestException;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationCode;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationErrorCode;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationRequest;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationResponseType;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.security.model.LocalUserDetails;
@@ -49,7 +55,10 @@ public class OidcAuthorizationController {
@Autowired
private OidcClientManager oidcClientManager;
@Autowired
private OidcAuthorizationCodeManager oidcAuthorizationCodeManager;
private OidcAuthorizationManager oidcAuthorizationManager;
@Value("${oidcAuthorizationFormUrl:/oidc/authorize/form}")
private String authorizationFormUrl;
/**
* Authorization request.
@@ -60,36 +69,21 @@ public class OidcAuthorizationController {
* @param redirectUri the redirect uri
* @param state the state
* @param nonce the nonce
* @param prompt the prompt
* @param login_hint the login hint
* @param code the code
* @param principal the principal
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("isAuthenticated()")
@GetMapping
void authorizationRequest(
// for OIDC scope must contain "openid"
@RequestParam(name = "scope") String scope,
// for now only "code" is available
@RequestParam(name = "response_type") String responseType,
// client id
@RequestParam(name = "client_id") String clientId,
// redirect url
@RequestParam(name = "redirect_uri") URI redirectUri,
// optional state
@RequestParam(name = "state", required = false) String state,
// optional state
@RequestParam(name = "nonce", required = false) String nonce,
// authentication details
@AuthenticationPrincipal LocalUserDetails principal,
// the request
HttpServletRequest request,
// the response
HttpServletResponse response) throws IOException {
protected void authorizationRequest(String scope, String responseType, String clientId, URI redirectUri,
String state, String nonce, String prompt, String login_hint, String code, LocalUserDetails principal,
HttpServletRequest request, HttpServletResponse response) throws IOException {
if (!StringUtils.hasText(clientId)) {
logger.debug("missing client_id");
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
"missing client_id", state);
}
@@ -97,14 +91,14 @@ public class OidcAuthorizationController {
if (client == null) {
logger.debug("invalid client_id: " + clientId);
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
"invalid client_id", state);
}
if (!client.getRedirectUris().contains(redirectUri.toString())) {
logger.debug("invalid redirect_uri: " + redirectUri + " allowed: " + client.getRedirectUris());
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
"invalid redirect_uri", state);
}
@@ -112,23 +106,22 @@ public class OidcAuthorizationController {
&& !permissionManager.hasPermission(principal.getUserId(), client.getClientName())
&& !permissionManager.hasPermission(principal.getUserId(), Permissions.ROLE_ADMIN)) {
logger.debug("user not allowed: " + principal.getUserId() + " - " + client.getClientName());
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.ACCESS_DENIED,
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.ACCESS_DENIED,
"user not allowed", state);
}
if (!client.getAuthorizationGrantTypes().contains(OidcAuthorizationGrantType.authorization_code)) {
logger.debug("authorization grant type not allowed: " + OidcAuthorizationGrantType.authorization_code
+ " - " + client.getClientName());
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.UNAUTHORIZED_CLIENT,
"authorization grant type not allowed", state);
throw new InvalidAuthorizationRequestException(redirectUri,
OidcAuthorizationErrorCode.INVALID_REQUEST_OBJECT, "authorization grant type not allowed", state);
}
if (!responseType.equals(OidcAuthorizationResponseType.code.toString())) {
logger.debug("response type not allowed: " + OidcAuthorizationResponseType.code
+ " - " + client.getClientName());
throw new InvalidAuthorizationRequestError(redirectUri,
if (!responseType.equals(OidcAuthorizationResponseType.AUTHORIZATION_CODE.getAuthorizationResponseType())) {
logger.debug("response type not allowed: "
+ OidcAuthorizationResponseType.AUTHORIZATION_CODE.getAuthorizationResponseType() + " - "
+ client.getClientName());
throw new InvalidAuthorizationRequestException(redirectUri,
OidcAuthorizationErrorCode.UNSUPPORTED_RESPONSE_TYPE, "response type not allowed", state);
}
@@ -137,27 +130,151 @@ public class OidcAuthorizationController {
if (!scopes.contains("openid")) {
logger.debug("missing openid scope: " + scopes + " - " + client.getClientName());
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.INVALID_SCOPE,
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_SCOPE,
"missing openid scope", state);
}
OidcAuthorizationCode authorizationCode = oidcAuthorizationCodeManager.create(clientId, redirectUri, scopes,
principal.getUserId(), nonce);
if (!client.getScopes().containsAll(scopes)) {
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_SCOPE,
"some scopes not allowed", state);
}
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(redirectUri);
String uri = redirectUri.toString();
Set<String> prompts = Sets.newHashSet();
if (StringUtils.hasText(redirectUri.getQuery())) {
uri += "&code=" + authorizationCode.getCode();
if (StringUtils.hasText(prompt)) {
prompts = Sets.newHashSet(prompt.split(" "));
}
if (prompts.contains("none") && prompts.size() > 1) {
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
"prompt contains more than 'none'", state);
} else if (prompts.contains("none") && client.isAuthorize()
&& !oidcAuthorizationManager.isAuthorized(client.getId(), principal.getUserId(), scopes)) {
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INTERACTION_REQUIRED,
"prompt 'consent' required beforehand", state);
}
OidcAuthorizationCode authorizationCode;
if (StringUtils.hasText(code)) {
authorizationCode = oidcAuthorizationManager.getCode(code);
if (authorizationCode == null) {
throw new InvalidAuthorizationRequestException(redirectUri,
OidcAuthorizationErrorCode.INVALID_REQUEST_OBJECT, "invalid code", state);
}
if (!authorizationCode.getUserId().equals(principal.getUserId())) {
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.LOGIN_REQUIRED,
"code does not match user", state);
}
oidcAuthorizationManager.authorize(client.getId(), principal.getUserId(), scopes);
} else {
uri += "?code=" + authorizationCode.getCode();
if (client.isAuthorize()
&& !oidcAuthorizationManager.isAuthorized(client.getId(), principal.getUserId(), scopes)
|| prompts.contains("consent")) {
uriBuilder = UriComponentsBuilder.fromUriString(authorizationFormUrl);
uriBuilder.queryParam("client_id", clientId);
uriBuilder.queryParam("response_type", responseType);
uriBuilder.queryParam("redirect_uri", redirectUri);
uriBuilder.queryParam("scope", scope);
if (StringUtils.hasText(nonce)) {
uriBuilder.queryParam("nonce", nonce);
}
if (StringUtils.hasText(prompt)) {
uriBuilder.queryParam("prompt", prompt);
}
}
authorizationCode = oidcAuthorizationManager.createCode(clientId, redirectUri, scopes,
principal.getUserId(), nonce, request.getSession().getId());
}
uriBuilder.queryParam("code", authorizationCode.getCode());
if (StringUtils.hasText(state)) {
uri += "&state=" + state;
uriBuilder.queryParam("state", state);
}
response.sendRedirect(uri);
if (StringUtils.hasText(login_hint)) {
uriBuilder.queryParam("login_hint", login_hint);
}
response.sendRedirect(uriBuilder.build().toUriString());
}
/**
* Authorization get request.
*
* @param scope the scope
* @param responseType the response type
* @param clientId the client id
* @param redirectUri the redirect uri
* @param state the state
* @param nonce the nonce
* @param prompt the prompt
* @param login_hint the login hint
* @param principal the principal
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@GetMapping
public void authorizationGetRequest(
// for OIDC scope must contain "openid"
@RequestParam(name = "scope") String scope,
// response type
@RequestParam(name = "response_type") String responseType,
// client id
@RequestParam(name = "client_id") String clientId,
// redirect url
@RequestParam(name = "redirect_uri") URI redirectUri,
// optional state
@RequestParam(name = "state", required = false) String state,
// optional nonce
@RequestParam(name = "nonce", required = false) String nonce,
// optional prompt
@RequestParam(name = "prompt", required = false) String prompt,
// optional login_hint
@RequestParam(name = "login_hint", required = false) String login_hint,
// authentication details
@AuthenticationPrincipal LocalUserDetails principal,
// the request
HttpServletRequest request,
// the response
HttpServletResponse response) throws IOException {
authorizationRequest(scope, responseType, clientId, redirectUri, state, nonce, prompt, login_hint, null,
principal, request, response);
}
/**
* Authorization post request.
*
* @param authorizationRequest the authorization request
* @param principal the principal
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PostMapping
@PreAuthorize("authentication.authenticated")
public void authorizationPostRequest(
// authorization request
@ModelAttribute("authorizationRequest") OidcAuthorizationRequest authorizationRequest,
// authentication details
@AuthenticationPrincipal LocalUserDetails principal,
// the request
HttpServletRequest request,
// the response
HttpServletResponse response) throws IOException {
authorizationRequest(authorizationRequest.getScope(), authorizationRequest.getResponse_type(),
authorizationRequest.getClient_id(), authorizationRequest.getRedirect_uri(),
authorizationRequest.getState(), authorizationRequest.getNonce(), authorizationRequest.getPrompt(),
authorizationRequest.getLogin_hint(), authorizationRequest.getCode(), principal, request, response);
}
/**
@@ -167,126 +284,21 @@ public class OidcAuthorizationController {
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@ExceptionHandler(InvalidAuthorizationRequestError.class)
public void handle(InvalidAuthorizationRequestError exception, HttpServletResponse response) throws IOException {
String uri = exception.getRedirectUri().toString();
@ExceptionHandler(InvalidAuthorizationRequestException.class)
public void handle(InvalidAuthorizationRequestException exception, HttpServletResponse response)
throws IOException {
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(exception.getRedirectUri());
uri += "?error=" + exception.getErrorCode().getAuthorizationErrorCode();
uriBuilder.queryParam("error", exception.getErrorCode().getAuthorizationErrorCode());
if (StringUtils.hasText(exception.getErrorDescription())) {
uri += "&error_description=" + exception.getErrorDescription();
uriBuilder.queryParam("error_description", exception.getErrorDescription());
}
if (StringUtils.hasText(exception.getState())) {
uri += "&state=" + exception.getState();
uriBuilder.queryParam("state", exception.getState());
}
response.sendRedirect(uri);
response.sendRedirect(uriBuilder.build().toUriString());
}
/**
* The Class InvalidAuthorizationRequestError.
*/
static class InvalidAuthorizationRequestError extends RuntimeException {
/**
* default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private URI redirectUri;
private OidcAuthorizationErrorCode errorCode;
private String errorDescription;
private String state;
/**
* Instantiates a new invalid authorization request error.
*
* @param redirectUri the redirect uri
* @param errorCode the error code
* @param errorDescription the error description
* @param state the state
*/
InvalidAuthorizationRequestError(URI redirectUri, OidcAuthorizationErrorCode errorCode, String errorDescription,
String state) {
super(errorDescription);
this.redirectUri = redirectUri;
this.errorCode = errorCode;
this.errorDescription = errorDescription;
this.state = state;
}
/**
* Gets the redirect uri.
*
* @return the redirect uri
*/
public URI getRedirectUri() {
return redirectUri;
}
/**
* Sets the redirect uri.
*
* @param redirectUri the new redirect uri
*/
public void setRedirectUri(URI redirectUri) {
this.redirectUri = redirectUri;
}
/**
* Gets the error code.
*
* @return the error code
*/
public OidcAuthorizationErrorCode getErrorCode() {
return errorCode;
}
/**
* Sets the error code.
*
* @param errorCode the new error code
*/
public void setErrorCode(OidcAuthorizationErrorCode errorCode) {
this.errorCode = errorCode;
}
/**
* Gets the error description.
*
* @return the error description
*/
public String getErrorDescription() {
return errorDescription;
}
/**
* Sets the error description.
*
* @param errorDescription the new error description
*/
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
/**
* Gets the state.
*
* @return the state
*/
public String getState() {
return state;
}
/**
* Sets the state.
*
* @param state the new state
*/
public void setState(String state) {
this.state = state;
}
}
}
@@ -9,9 +9,8 @@ import java.net.URISyntaxException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -19,7 +18,8 @@ import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Sets;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.oidc.model.OidcConfiguration;
import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.controller.model.OidcConfiguration;
/**
* The Class OidcDiscoveryController.
@@ -28,8 +28,8 @@ import de.bstly.we.oidc.model.OidcConfiguration;
@RestController
public class OidcDiscoveryController {
@Value("${oidc.provider.issuer:}")
private String oidcIssuer;
@Autowired
private OidcClientManager oidcClientManager;
/**
* Gets the configuration.
@@ -42,14 +42,7 @@ public class OidcDiscoveryController {
public OidcConfiguration getConfiguration(HttpServletRequest request, HttpServletResponse response) {
OidcConfiguration config = new OidcConfiguration();
String issuer = oidcIssuer;
if (!StringUtils.hasText(issuer)) {
issuer = request.getScheme() + "://" + request.getServerName();
if (request.getServerPort() != 443 && request.getServerPort() != 80) {
issuer += ":" + request.getServerPort();
}
}
String issuer = oidcClientManager.getIssuer(request);
config.setIssuer(issuer);
config.setScopes_supported(Sets.newHashSet("openid"));
@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.bstly.we.jwt.businesslogic.JwtKeyManager;
import de.bstly.we.oidc.businesslogic.OidcTokenManager;
/**
@@ -20,7 +21,7 @@ import de.bstly.we.oidc.businesslogic.OidcTokenManager;
public class OidcJwksController {
@Autowired
private OidcTokenManager oidcTokenManager;
private JwtKeyManager jwtKeyManager;
/**
* Gets the jwks.
@@ -29,6 +30,6 @@ public class OidcJwksController {
*/
@GetMapping
public Map<String, Object> getJwks() {
return oidcTokenManager.getJwkSet().toJSONObject();
return jwtKeyManager.getJwkSet(OidcTokenManager.OIDC_JWT_KEY_NAME, false).toJSONObject();
}
}
@@ -0,0 +1,234 @@
/**
*
*/
package de.bstly.we.oidc.controller;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import de.bstly.we.businesslogic.PermissionManager;
import de.bstly.we.businesslogic.Permissions;
import de.bstly.we.businesslogic.SessionManager;
import de.bstly.we.businesslogic.UserManager;
import de.bstly.we.controller.BaseController;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.model.User;
import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.businesslogic.OidcSessionManager;
import de.bstly.we.oidc.controller.model.OidcClientInfo;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcSession;
/**
* The Class OidcSessionController.
*/
@RestController
@RequestMapping("/oidc/session")
public class OidcSessionController extends BaseController {
@Autowired
private OidcClientManager oidcClientManager;
@Autowired
private OidcSessionManager oidcSessionManager;
@Autowired
private SessionManager sessionManager;
@Autowired
private RememberMeServices rememberMeServices;
@Autowired
private UserManager userManager;
@Autowired
private OidcClientManager oidcRegisteredClientManager;
@Autowired
private PermissionManager permissionManager;
@Value("${loginUrl:/login}")
private String loginUrl;
/**
* Gets the sessions.
*
* @return the sessions
*/
@PreAuthorize("authentication.authenticated")
@GetMapping
public List<OidcClientInfo> getSessions() {
Map<Long, OidcClientInfo> clients = Maps.newHashMap();
for (OidcSession session : oidcSessionManager.getAllBySubject(getCurrentUserId())) {
Long oidcClientId = session.getClientId();
OidcClientInfo clientInfo = clients.get(oidcClientId);
if (clientInfo == null) {
OidcClient client = oidcRegisteredClientManager.get(oidcClientId);
if (client == null || !client.isAlwaysPermitted()
&& (!permissionManager.hasPermission(getCurrentUserId(), client.getClientName())
|| !permissionManager.hasPermission(getCurrentUserId(), Permissions.ROLE_ADMIN))) {
continue;
}
clientInfo = oidcRegisteredClientManager.getClientInfo(client, getCurrentUserId());
}
clientInfo.getSessions().add(session);
clients.put(session.getClientId(), clientInfo);
}
return Lists.newArrayList(clients.values());
}
/**
* Gets the sessions for target.
*
* @param target the target
* @return the sessions for target
*/
@PreAuthorize("authentication.authenticated")
@GetMapping("/{target}")
public OidcClientInfo getSessionsForTarget(@PathVariable("target") Long target) {
OidcClient client = oidcRegisteredClientManager.get(target);
if (client == null || !client.isAlwaysPermitted()
&& (!permissionManager.hasPermission(getCurrentUserId(), client.getClientName())
|| !permissionManager.hasPermission(getCurrentUserId(), Permissions.ROLE_ADMIN))) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
OidcClientInfo clientInfo = oidcRegisteredClientManager.getClientInfo(client, getCurrentUserId());
clientInfo.getSessions().addAll(oidcSessionManager.getAllByTargetAndSubject(target, getCurrentUserId()));
return clientInfo;
}
/**
* Logout session.
*
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@RequestMapping(value = "/logout", method = { RequestMethod.GET, RequestMethod.POST })
public void logoutSession(@RequestParam("redirect_url") Optional<String> redirectUrlParam,
HttpServletRequest request, HttpServletResponse response) throws IOException {
String issuer = oidcClientManager.getIssuer(request);
oidcSessionManager.backchannelLogout(request.getSession().getId(), issuer);
internalLogout(redirectUrlParam, request, response);
}
/**
* Logout all.
*
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@RequestMapping(value = "/logout/all", method = { RequestMethod.GET, RequestMethod.POST })
public void logoutAll(@RequestParam("redirect_url") Optional<String> redirectUrlParam, HttpServletRequest request,
HttpServletResponse response) throws IOException {
String issuer = oidcClientManager.getIssuer(request);
oidcSessionManager.backchannelLogout(getCurrentUserId(), issuer);
User user = userManager.get(getCurrentUserId());
sessionManager.deleteSessionsForUser(user);
internalLogout(redirectUrlParam, request, response);
}
/**
* Logout by sid.
*
* @param sid the sid
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@RequestMapping(value = "/logout/sid/{sid}", method = { RequestMethod.GET, RequestMethod.POST })
public void logoutBySid(@PathVariable("sid") String sid,
@RequestParam("redirect_url") Optional<String> redirectUrlParam, HttpServletRequest request,
HttpServletResponse response) throws IOException {
String issuer = oidcClientManager.getIssuer(request);
oidcSessionManager.backchannelLogout(sid, issuer);
redirect(redirectUrlParam, request, response, false);
}
/**
* Logout all for target.
*
* @param target the target
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@RequestMapping(value = "/logout/all/{target}", method = { RequestMethod.GET, RequestMethod.POST })
public void logoutAllForTarget(@PathVariable("target") Long target,
@RequestParam("redirect_url") Optional<String> redirectUrlParam, HttpServletRequest request,
HttpServletResponse response) throws IOException {
String issuer = oidcClientManager.getIssuer(request);
oidcSessionManager.backchannelLogout(target, getCurrentUserId(), issuer);
redirect(redirectUrlParam, request, response, false);
}
/**
* Internal logout.
*
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
protected void internalLogout(Optional<String> redirectUrlParam, HttpServletRequest request,
HttpServletResponse response) throws IOException {
new SecurityContextLogoutHandler().logout(request, response,
SecurityContextHolder.getContext().getAuthentication());
rememberMeServices.loginFail(request, response);
redirect(redirectUrlParam, request, response, true);
}
/**
* Redirect.
*
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @param fallback the fallback
* @throws IOException Signals that an I/O exception has occurred.
*/
protected void redirect(Optional<String> redirectUrlParam, HttpServletRequest request, HttpServletResponse response,
boolean fallback) throws IOException {
String redirectUrl = redirectUrlParam.orElse(null);
if (!StringUtils.hasText(redirectUrl) && fallback) {
redirectUrl = loginUrl;
}
if (StringUtils.hasText(redirectUrl)) {
response.sendRedirect(redirectUrl);
}
}
}
@@ -11,15 +11,16 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -32,16 +33,19 @@ import org.springframework.web.server.ResponseStatusException;
import com.google.common.collect.Sets;
import com.nimbusds.jose.JOSEException;
import de.bstly.we.oidc.businesslogic.OidcAuthorizationCodeManager;
import de.bstly.we.oidc.businesslogic.OidcAuthorizationManager;
import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.businesslogic.OidcSessionManager;
import de.bstly.we.oidc.businesslogic.OidcTokenManager;
import de.bstly.we.oidc.model.OidcAuthorizationCode;
import de.bstly.we.oidc.businesslogic.exception.InvalidTokenRequestException;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationCode;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.businesslogic.model.OidcTokenErrorCode;
import de.bstly.we.oidc.businesslogic.model.OidcTokenRequest;
import de.bstly.we.oidc.businesslogic.model.OidcTokenResponse;
import de.bstly.we.oidc.businesslogic.model.OidcTokenRevokeRequest;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.model.OidcToken;
import de.bstly.we.oidc.model.OidcTokenErrorCode;
import de.bstly.we.oidc.model.OidcTokenRequest;
import de.bstly.we.oidc.model.OidcTokenResponse;
/**
* The Class OidcTokenController.
@@ -57,12 +61,11 @@ public class OidcTokenController {
@Autowired
private OidcClientManager oidcClientManager;
@Autowired
private OidcAuthorizationCodeManager oidcAuthorizationCodeManager;
private OidcAuthorizationManager oidcAuthorizationManager;
@Autowired
private OidcTokenManager oidcTokenManager;
@Value("${oidc.provider.issuer:}")
private String oidcIssuer;
@Autowired
private OidcSessionManager oidcSessionManager;
/**
* Gets the token.
@@ -74,6 +77,7 @@ public class OidcTokenController {
* @return the token
*/
@PostMapping
@Transactional
public OidcTokenResponse getToken(
// Authorization header for BASIC client authentication method
@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String authorizationHeader,
@@ -84,6 +88,8 @@ public class OidcTokenController {
// the response
HttpServletResponse response) {
String issuer = oidcClientManager.getIssuer(request);
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
@@ -101,52 +107,61 @@ public class OidcTokenController {
clientAuthenticationMethod = OidcClientAuthenticationMethod.basic;
} else {
logger.debug("invalid_basic_authentication: " + decoded);
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_CLIENT, "invalid_basic_authentication");
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_CLIENT,
"invalid_basic_authentication");
}
}
if (!StringUtils.hasText(tokenRequest.getClient_id())) {
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_REQUEST, "missing_client_id");
}
if (!StringUtils.hasText(tokenRequest.getClient_secret())) {
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_REQUEST, "missing_client_secret");
}
OidcClient client = oidcClientManager.getByClientIdAndSecret(tokenRequest.getClient_id(),
tokenRequest.getClient_secret());
if (client == null) {
logger.debug("client not found: " + tokenRequest.getClient_id());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_CLIENT, "invalid_client");
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_CLIENT, "invalid_client");
}
if (!client.getClientAuthenticationMethods().contains(clientAuthenticationMethod)) {
logger.debug("invalid_authentication_method: " + clientAuthenticationMethod);
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_REQUEST, "invalid_authentication_method");
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_REQUEST, "invalid_authentication_method");
}
if (!client.getAuthorizationGrantTypes().contains(tokenRequest.getGrant_type())) {
logger.debug("invalid_grant_type: " + tokenRequest.getGrant_type());
throw new InvalidTokenRequestError(OidcTokenErrorCode.UNAUTHORIZED_CLIENT, "invalid_grant_type");
throw new InvalidTokenRequestException(OidcTokenErrorCode.UNAUTHORIZED_CLIENT, "invalid_grant_type");
}
if (tokenRequest.getRedirect_uri() != null
&& !client.getRedirectUris().contains(tokenRequest.getRedirect_uri().toString())) {
logger.debug("invalid redirect_uri: " + tokenRequest.getRedirect_uri().toString() + " allowed: "
+ client.getRedirectUris());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_REQUEST, "invalid_redirect_uri");
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_REQUEST, "invalid_redirect_uri");
}
OidcToken token = null;
switch (tokenRequest.getGrant_type()) {
case authorization_code:
OidcAuthorizationCode authorizationCode = oidcAuthorizationCodeManager.getByCode(tokenRequest.getCode());
OidcAuthorizationCode authorizationCode = oidcAuthorizationManager.getCode(tokenRequest.getCode());
if (authorizationCode == null) {
logger.debug("invalid authorization code: " + tokenRequest.getCode());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_GRANT, "invalid_authorization_code");
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_GRANT, "invalid_authorization_code");
}
if (Instant.now().isAfter(authorizationCode.getExpiry())) {
logger.debug("authorization code expired: " + authorizationCode.getExpiry());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_GRANT, "invalid_authorization_code");
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_GRANT, "invalid_authorization_code");
}
if (!tokenRequest.getClient_id().equals(authorizationCode.getClientId())) {
logger.debug("invalid client for authorization code, expected: " + authorizationCode.getClientId()
+ " got: " + tokenRequest.getClient_id());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_CLIENT, "invalid_client");
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_CLIENT, "invalid_client");
}
Set<String> scopes = StringUtils.hasText(tokenRequest.getScope())
@@ -155,46 +170,80 @@ public class OidcTokenController {
if (!scopes.contains("openid") || !client.getScopes().containsAll(scopes)) {
logger.debug("missing openid scope: " + scopes + " - " + client.getClientName());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_SCOPE, "invalid scopes");
}
String issuer = oidcIssuer;
if (!StringUtils.hasText(issuer)) {
issuer = request.getScheme() + "://" + request.getServerName();
if (request.getServerPort() != 443 && request.getServerPort() != 80) {
issuer += ":" + request.getServerPort();
}
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_SCOPE, "invalid scopes");
}
try {
String sid = oidcSessionManager.createSid();
token = oidcTokenManager.createTokenWithIdToken(client, authorizationCode.getUserId(),
authorizationCode.getNonce(), scopes, issuer);
authorizationCode.getNonce(), scopes, issuer, sid);
oidcSessionManager.createSession(client.getId(), authorizationCode.getUserId(), token.getIdToken(), sid,
authorizationCode.getSpringSession());
} catch (JOSEException e) {
logger.error("error creating token", client, authorizationCode);
e.printStackTrace();
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
oidcAuthorizationCodeManager.removeByCode(tokenRequest.getCode());
oidcAuthorizationManager.removeCode(tokenRequest.getCode());
break;
case client_credentials:
token = oidcTokenManager.createToken(client, client.getId());
token = oidcTokenManager.createToken(client, client.getId(), false);
break;
case refresh_token:
if (!StringUtils.hasText(tokenRequest.getRefresh_token())) {
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_GRANT, "invalid_refresh_token");
}
OidcToken refreshToken = oidcTokenManager.getLastByRefreshToken(tokenRequest.getRefresh_token());
if (refreshToken == null || !(client.getId().equals(refreshToken.getClient()))) {
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_GRANT, "invalid_refresh_token");
}
token = oidcTokenManager.createToken(client, client.getId(), tokenRequest.getRefresh_token());
token.setRefreshToken(null);
break;
}
if (token == null) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
OidcTokenResponse tokenResponse = new OidcTokenResponse();
tokenResponse.setAccess_token(token.getAccessToken());
tokenResponse.setId_token(token.getIdToken());
if (StringUtils.hasText(token.getRefreshToken())) {
tokenResponse.setRefresh_token(token.getRefreshToken());
}
if (StringUtils.hasText(token.getIdToken())) {
tokenResponse.setId_token(token.getIdToken());
}
tokenResponse.setToken_type(OidcTokenManager.BEARER_TOKEN_TYPE);
tokenResponse.setExpires_in(client.getTokenLifetime());
oidcAuthorizationCodeManager.removeByCode(tokenRequest.getCode());
tokenResponse.setExpires_in(token.getExpiresIn());
return tokenResponse;
}
/**
* Revoke.
*
* @param oidcTokenRevokeRequest the oidc token revoke request
*/
@PreAuthorize("authentication.authenticated")
@PostMapping("revoke")
@Transactional
public void revoke(@ModelAttribute("oidcTokenRevokeRequest") OidcTokenRevokeRequest oidcTokenRevokeRequest) {
if (!StringUtils.hasText(oidcTokenRevokeRequest.getToken())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
if ("refresh_token".equals(oidcTokenRevokeRequest.getToken_type_hint())) {
oidcTokenManager.deleteAllByRefreshToken(oidcTokenRevokeRequest.getToken());
} else if ("access_token".equals(oidcTokenRevokeRequest.getToken_type_hint())
|| oidcTokenRevokeRequest.getToken_type_hint() == null) {
oidcTokenManager.deleteByAccessToken(oidcTokenRevokeRequest.getToken());
}
}
/**
* Handle.
*
@@ -203,74 +252,11 @@ public class OidcTokenController {
* @return the response entity
* @throws IOException Signals that an I/O exception has occurred.
*/
@ExceptionHandler(InvalidTokenRequestError.class)
public ResponseEntity<String> handle(InvalidTokenRequestError exception, HttpServletResponse response)
@ExceptionHandler(InvalidTokenRequestException.class)
public ResponseEntity<String> handle(InvalidTokenRequestException exception, HttpServletResponse response)
throws IOException {
// response.sendError(400, "redirect uri mismatch");
return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON)
.body(" {\"error\": \"" + exception.getMessage() + "\"}");
}
/**
* The Class InvalidTokenRequestError.
*/
static class InvalidTokenRequestError extends RuntimeException {
/**
* default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private OidcTokenErrorCode errorCode;
private String errorDescription;
/**
* Instantiates a new invalid token request error.
*
* @param errorCode the error code
* @param errorDescription the error description
*/
InvalidTokenRequestError(OidcTokenErrorCode errorCode, String errorDescription) {
super(errorDescription);
this.errorCode = errorCode;
this.errorDescription = errorDescription;
}
/**
* Gets the error code.
*
* @return the error code
*/
public OidcTokenErrorCode getErrorCode() {
return errorCode;
}
/**
* Sets the error code.
*
* @param errorCode the new error code
*/
public void setErrorCode(OidcTokenErrorCode errorCode) {
this.errorCode = errorCode;
}
/**
* Gets the error description.
*
* @return the error description
*/
public String getErrorDescription() {
return errorDescription;
}
/**
* Sets the error description.
*
* @param errorDescription the new error description
*/
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
}
}
@@ -0,0 +1,266 @@
/**
*
*/
package de.bstly.we.oidc.controller.model;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.bstly.we.oidc.model.OidcSession;
/**
* The Class OidcClientInfo.
*/
public class OidcClientInfo {
private String clientId;
private String name;
private String description;
private String loginUrl;
private String frontchannelLogoutUri;
private boolean frontchannelLogoutSessionRequired;
private boolean backchannelLogout;
private boolean authorize;
private Set<String> scopes;
private Map<String, Set<String>> claimMapping;
private Set<String> authorizedScopes;
private Map<String, Object> claims;
private List<OidcSession> sessions;
/**
* Gets the client id.
*
* @return the client id
*/
public String getClientId() {
return clientId;
}
/**
* Sets the client id.
*
* @param clientId the new client id
*/
public void setClientId(String clientId) {
this.clientId = clientId;
}
/**
* Gets the name.
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Sets the name.
*
* @param name the new name
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the description.
*
* @return the description
*/
public String getDescription() {
return description;
}
/**
* Sets the description.
*
* @param description the new description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Gets the login url.
*
* @return the login url
*/
public String getLoginUrl() {
return loginUrl;
}
/**
* Sets the login url.
*
* @param loginUrl the new login url
*/
public void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
/**
* Gets the frontchannel logout uri.
*
* @return the frontchannel logout uri
*/
public String getFrontchannelLogoutUri() {
return frontchannelLogoutUri;
}
/**
* Sets the frontchannel logout uri.
*
* @param frontchannelLogoutUri the new frontchannel logout uri
*/
public void setFrontchannelLogoutUri(String frontchannelLogoutUri) {
this.frontchannelLogoutUri = frontchannelLogoutUri;
}
/**
* Checks if is frontchannel logout session required.
*
* @return true, if is frontchannel logout session required
*/
public boolean isFrontchannelLogoutSessionRequired() {
return frontchannelLogoutSessionRequired;
}
/**
* Sets the frontchannel logout session required.
*
* @param frontchannelLogoutSessionRequired the new frontchannel logout session
* required
*/
public void setFrontchannelLogoutSessionRequired(boolean frontchannelLogoutSessionRequired) {
this.frontchannelLogoutSessionRequired = frontchannelLogoutSessionRequired;
}
/**
* Checks if is backchannel logout.
*
* @return true, if is backchannel logout
*/
public boolean isBackchannelLogout() {
return backchannelLogout;
}
/**
* Sets the backchannel logout.
*
* @param backchannelLogout the new backchannel logout
*/
public void setBackchannelLogout(boolean backchannelLogout) {
this.backchannelLogout = backchannelLogout;
}
/**
* Checks if is authorize.
*
* @return true, if is authorize
*/
public boolean isAuthorize() {
return authorize;
}
/**
* Sets the authorize.
*
* @param authorize the new authorize
*/
public void setAuthorize(boolean authorize) {
this.authorize = authorize;
}
/**
* Gets the scopes.
*
* @return the scopes
*/
public Set<String> getScopes() {
return scopes;
}
/**
* Sets the scopes.
*
* @param scopes the new scopes
*/
public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
/**
* Gets the claim mapping.
*
* @return the claim mapping
*/
public Map<String, Set<String>> getClaimMapping() {
return claimMapping;
}
/**
* Sets the claim mapping.
*
* @param claimMapping the claim mapping
*/
public void setClaimMapping(Map<String, Set<String>> claimMapping) {
this.claimMapping = claimMapping;
}
/**
* Gets the authorized scopes.
*
* @return the authorized scopes
*/
public Set<String> getAuthorizedScopes() {
return authorizedScopes;
}
/**
* Sets the authorized scopes.
*
* @param authorizedScopes the new authorized scopes
*/
public void setAuthorizedScopes(Set<String> authorizedScopes) {
this.authorizedScopes = authorizedScopes;
}
/**
* Gets the claims.
*
* @return the claims
*/
public Map<String, Object> getClaims() {
return claims;
}
/**
* Sets the claims.
*
* @param claims the claims
*/
public void setClaims(Map<String, Object> claims) {
this.claims = claims;
}
/**
* Gets the sessions.
*
* @return the sessions
*/
public List<OidcSession> getSessions() {
return sessions;
}
/**
* Sets the sessions.
*
* @param sessions the new sessions
*/
public void setSessions(List<OidcSession> sessions) {
this.sessions = sessions;
}
}
@@ -5,8 +5,8 @@ package de.bstly.we.oidc.controller.model;
import java.util.Set;
import de.bstly.we.oidc.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
/**
* The Class OidcClientModel.
@@ -1,7 +1,7 @@
/**
*
*/
package de.bstly.we.oidc.model;
package de.bstly.we.oidc.controller.model;
import java.net.URI;
import java.util.Set;
@@ -0,0 +1,94 @@
/**
*
*/
package de.bstly.we.oidc.model;
import java.util.Set;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
/**
* The Class OidcAuthorization.
*/
@Entity
@Table(name = "oidc_authorizations")
public class OidcAuthorization {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "client_id")
private Long client;
@Column(name = "subject")
private Long subject;
@ElementCollection
@LazyCollection(LazyCollectionOption.FALSE)
@CollectionTable(name = "oidc_authorizations_scopes")
private Set<String> scopes;
/**
* Gets the client.
*
* @return the client
*/
public Long getClient() {
return client;
}
/**
* Sets the client.
*
* @param client the new client
*/
public void setClient(Long client) {
this.client = client;
}
/**
* Gets the subject.
*
* @return the subject
*/
public Long getSubject() {
return subject;
}
/**
* Sets the subject.
*
* @param subject the new subject
*/
public void setSubject(Long subject) {
this.subject = subject;
}
/**
* Gets the scopes.
*
* @return the scopes
*/
public Set<String> getScopes() {
return scopes;
}
/**
* Sets the scopes.
*
* @param scopes the new scopes
*/
public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
}
@@ -1,11 +0,0 @@
/**
*
*/
package de.bstly.we.oidc.model;
/**
* The Enum OidcAuthorizationResponseType.
*/
public enum OidcAuthorizationResponseType {
code
}
@@ -19,6 +19,9 @@ import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
/**
* The Class OidcClient.
*/
@@ -58,6 +61,16 @@ public class OidcClient {
private Long tokenLifetime;
@Column(name = "login_url", length = 1024)
private String loginUrl;
@Column(name = "frontchannel_logout_uri")
private String frontchannelLogoutUri;
@Column(name = "frontchannel_logout_session_required", columnDefinition = "boolean default false")
private boolean frontchannelLogoutSessionRequired;
@Column(name = "backchannel_logout_uri")
private String backchannelLogoutUri;
@Column(name = "backchannel_logout_session_required", columnDefinition = "boolean default false")
private boolean backchannelLogoutSessionRequired;
@Column(name = "authorize", columnDefinition = "boolean default false")
private boolean authorize;
@Column(name = "always_permitted", columnDefinition = "boolean default false")
private boolean alwaysPermitted;
@Column(name = "category")
@@ -243,6 +256,98 @@ public class OidcClient {
this.loginUrl = loginUrl;
}
/**
* Gets the frontchannel logout uri.
*
* @return the frontchannel logout uri
*/
public String getFrontchannelLogoutUri() {
return frontchannelLogoutUri;
}
/**
* Sets the frontchannel logout uri.
*
* @param frontchannelLogoutUri the new frontchannel logout uri
*/
public void setFrontchannelLogoutUri(String frontchannelLogoutUri) {
this.frontchannelLogoutUri = frontchannelLogoutUri;
}
/**
* Checks if is frontchannel logout session required.
*
* @return true, if is frontchannel logout session required
*/
public boolean isFrontchannelLogoutSessionRequired() {
return frontchannelLogoutSessionRequired;
}
/**
* Sets the frontchannel logout session required.
*
* @param frontchannelLogoutSessionRequired the new frontchannel logout session
* required
*/
public void setFrontchannelLogoutSessionRequired(boolean frontchannelLogoutSessionRequired) {
this.frontchannelLogoutSessionRequired = frontchannelLogoutSessionRequired;
}
/**
* Gets the backchannel logout uri.
*
* @return the backchannel logout uri
*/
public String getBackchannelLogoutUri() {
return backchannelLogoutUri;
}
/**
* Sets the backchannel logout uri.
*
* @param backchannelLogoutUri the new backchannel logout uri
*/
public void setBackchannelLogoutUri(String backchannelLogoutUri) {
this.backchannelLogoutUri = backchannelLogoutUri;
}
/**
* Checks if is backchannel logout session required.
*
* @return true, if is backchannel logout session required
*/
public boolean isBackchannelLogoutSessionRequired() {
return backchannelLogoutSessionRequired;
}
/**
* Sets the backchannel logout session required.
*
* @param backchannelLogoutSessionRequired the new backchannel logout session
* required
*/
public void setBackchannelLogoutSessionRequired(boolean backchannelLogoutSessionRequired) {
this.backchannelLogoutSessionRequired = backchannelLogoutSessionRequired;
}
/**
* Checks if is authorize.
*
* @return true, if is authorize
*/
public boolean isAuthorize() {
return authorize;
}
/**
* Sets the authorize.
*
* @param authorize the new authorize
*/
public void setAuthorize(boolean authorize) {
this.authorize = authorize;
}
/**
* Checks if is always permitted.
*
@@ -0,0 +1,143 @@
/**
*
*/
package de.bstly.we.oidc.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* The Class OidcSession.
*/
@Entity
@Table(name = "oidc_sessions")
public class OidcSession {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "client_id")
private Long client;
@Column(name = "subject")
private Long subject;
@Column(name = "sid")
private String sid;
@Column(name = "id_token", length = 4000)
private String idToken;
@Column(name = "spring_session_id")
private String springSession;
/**
* Gets the id.
*
* @return the id
*/
public Long getId() {
return id;
}
/**
* Sets the id.
*
* @param id the new id
*/
public void setId(Long id) {
this.id = id;
}
/**
* Gets the client id.
*
* @return the client id
*/
public Long getClientId() {
return client;
}
/**
* Sets the client id.
*
* @param clientId the new client id
*/
public void setClientId(Long clientId) {
this.client = clientId;
}
/**
* Gets the subject.
*
* @return the subject
*/
public Long getSubject() {
return subject;
}
/**
* Sets the subject.
*
* @param subject the new subject
*/
public void setSubject(Long subject) {
this.subject = subject;
}
/**
* Gets the sid.
*
* @return the sid
*/
public String getSid() {
return sid;
}
/**
* Sets the sid.
*
* @param sid the new sid
*/
public void setSid(String sid) {
this.sid = sid;
}
/**
* Gets the id token.
*
* @return the id token
*/
public String getIdToken() {
return idToken;
}
/**
* Sets the id token.
*
* @param idToken the new id token
*/
public void setIdToken(String idToken) {
this.idToken = idToken;
}
/**
* Gets the spring session.
*
* @return the spring session
*/
public String getSpringSession() {
return springSession;
}
/**
* Sets the spring session.
*
* @param springSession the new spring session
*/
public void setSpringSession(String springSession) {
this.springSession = springSession;
}
}
@@ -3,6 +3,7 @@
*/
package de.bstly.we.oidc.model;
import java.time.Instant;
import java.util.Set;
import javax.persistence.Column;
@@ -29,6 +30,8 @@ public class OidcToken {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "created")
private Instant created;
@Column(name = "user_id")
private Long userId;
@Column(name = "client_id")
@@ -64,6 +67,24 @@ public class OidcToken {
this.id = id;
}
/**
* Gets the created.
*
* @return the created
*/
public Instant getCreated() {
return created;
}
/**
* Sets the created.
*
* @param created the new created
*/
public void setCreated(Instant created) {
this.created = created;
}
/**
* Gets the user id.
*
@@ -0,0 +1,18 @@
/**
*
*/
package de.bstly.we.oidc.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;
import de.bstly.we.oidc.model.OidcAuthorization;
/**
* The Interface OidcAuthorizationRepository.
*/
@Repository
public interface OidcAuthorizationRepository
extends JpaRepository<OidcAuthorization, Long>, QuerydslPredicateExecutor<OidcAuthorization> {
}
@@ -0,0 +1,18 @@
/**
*
*/
package de.bstly.we.oidc.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;
import de.bstly.we.oidc.model.OidcSession;
/**
* The Interface OidcSessionRepository.
*/
@Repository
public interface OidcSessionRepository
extends JpaRepository<OidcSession, Long>, QuerydslPredicateExecutor<OidcSession> {
}