initial commit
This commit is contained in:
Executable
+26
@@ -0,0 +1,26 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>de.bstly.we</groupId>
|
||||
<artifactId>webstly-main</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>oidc</name>
|
||||
<artifactId>webstly-oidc</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>de.bstly.we</groupId>
|
||||
<artifactId>webstly-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>nimbus-jose-jwt</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.businesslogic;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import de.bstly.we.oidc.model.OidcAuthorizationCode;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
public class OidcAuthorizationCodeManager {
|
||||
|
||||
/**
|
||||
* hold codes in memory
|
||||
*/
|
||||
private final Map<String, OidcAuthorizationCode> oidcAuthorizationCodes = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientId
|
||||
* @param redirectUri
|
||||
* @param scopes
|
||||
* @param subject
|
||||
* @return
|
||||
*/
|
||||
public OidcAuthorizationCode create(String clientId, URI redirectUri, Set<String> scopes, Long userId,
|
||||
String nonce) {
|
||||
OidcAuthorizationCode oidcAuthorizationCode = new OidcAuthorizationCode(clientId, redirectUri, scopes, userId,
|
||||
nonce);
|
||||
oidcAuthorizationCodes.put(oidcAuthorizationCode.getCode(), oidcAuthorizationCode);
|
||||
return oidcAuthorizationCode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public OidcAuthorizationCode getByCode(String code) {
|
||||
return oidcAuthorizationCodes.get(code);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param code
|
||||
*/
|
||||
public void removeByCode(String code) {
|
||||
oidcAuthorizationCodes.remove(code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.businesslogic;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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 com.google.common.collect.Sets;
|
||||
|
||||
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.model.QOidcClient;
|
||||
import de.bstly.we.oidc.repository.OidcClientRepository;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
public class OidcClientManager {
|
||||
|
||||
public static final int OIDC_CLIENT_CLIENT_ID_LENGTH = 24;
|
||||
public static final int OIDC_CLIENT_CLIENT_SECRET_LENGTH = 64;
|
||||
public static final long OIDC_CLIENT_TOKEN_LIFETIME = 120;
|
||||
|
||||
@Autowired
|
||||
private OidcClientRepository oidcClientRepository;
|
||||
private QOidcClient qOidcClient = QOidcClient.oidcClient;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientName
|
||||
* @param redirectUris
|
||||
* @return
|
||||
*/
|
||||
public OidcClient createClient(String clientName, Set<String> redirectUris) {
|
||||
return createClient(clientName,
|
||||
Sets.newHashSet(OidcClientAuthenticationMethod.basic,
|
||||
OidcClientAuthenticationMethod.basic),
|
||||
Sets.newHashSet(OidcAuthorizationGrantType.authorization_code), redirectUris,
|
||||
Sets.newHashSet("openid"), null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientName
|
||||
* @param clientAuthenticationMethods
|
||||
* @param authorizationGrantTypes
|
||||
* @param redirectUris
|
||||
* @param scopes
|
||||
* @return
|
||||
*/
|
||||
public OidcClient createClient(String clientName,
|
||||
Set<OidcClientAuthenticationMethod> clientAuthenticationMethods,
|
||||
Set<OidcAuthorizationGrantType> authorizationGrantTypes, Set<String> redirectUris,
|
||||
Set<String> scopes, String loginUrl, boolean alwaysPermitted) {
|
||||
|
||||
OidcClient oidcClient = new OidcClient();
|
||||
|
||||
oidcClient.setClientName(clientName);
|
||||
oidcClient.setRedirectUris(redirectUris);
|
||||
oidcClient.setClientAuthenticationMethods(clientAuthenticationMethods);
|
||||
oidcClient.setAuthorizationGrantTypes(authorizationGrantTypes);
|
||||
oidcClient.setScopes(scopes);
|
||||
oidcClient.setLoginUrl(loginUrl);
|
||||
oidcClient.setAlwaysPermitted(alwaysPermitted);
|
||||
oidcClient.setTokenLifetime(OIDC_CLIENT_TOKEN_LIFETIME);
|
||||
|
||||
String clientId = RandomStringUtils.random(OIDC_CLIENT_CLIENT_ID_LENGTH, true, true);
|
||||
|
||||
while (oidcClientRepository.findOne(qOidcClient.clientId.eq(clientId)).isPresent()) {
|
||||
clientId = RandomStringUtils.random(OIDC_CLIENT_CLIENT_ID_LENGTH, true, true);
|
||||
}
|
||||
|
||||
oidcClient.setClientId(clientId);
|
||||
oidcClient.setClientSecret(
|
||||
RandomStringUtils.random(OIDC_CLIENT_CLIENT_SECRET_LENGTH, true, true));
|
||||
|
||||
return oidcClientRepository.save(oidcClient);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param oidcClient
|
||||
* @return
|
||||
*/
|
||||
public OidcClient updateClient(OidcClient oidcClient) {
|
||||
return oidcClientRepository.save(oidcClient);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientName
|
||||
* @return
|
||||
*/
|
||||
public OidcClient createNewSecretByClientName(String clientName) {
|
||||
OidcClient oidcClient = getByClientName(clientName);
|
||||
Assert.notNull(oidcClient, "No client found for name '" + clientName + "'");
|
||||
oidcClient.setClientSecret(
|
||||
RandomStringUtils.random(OIDC_CLIENT_CLIENT_SECRET_LENGTH, true, true));
|
||||
|
||||
return oidcClientRepository.save(oidcClient);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientId
|
||||
* @return
|
||||
*/
|
||||
public OidcClient getByClientId(String clientId) {
|
||||
return oidcClientRepository.findOne(qOidcClient.clientId.eq(clientId)).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientName
|
||||
* @return
|
||||
*/
|
||||
public OidcClient getByClientName(String clientName) {
|
||||
return oidcClientRepository.findOne(qOidcClient.clientName.eq(clientName)).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientId
|
||||
* @param clientSecret
|
||||
* @return
|
||||
*/
|
||||
public OidcClient getByClientIdAndSecret(String clientId, String clientSecret) {
|
||||
return oidcClientRepository.findOne(
|
||||
qOidcClient.clientId.eq(clientId).and(qOidcClient.clientSecret.eq(clientSecret)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientId
|
||||
* @return
|
||||
*/
|
||||
public void deleteByClientId(String clientId) {
|
||||
OidcClient oidcClient = getByClientId(clientId);
|
||||
if (oidcClient != null) {
|
||||
oidcClientRepository.delete(oidcClient);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public void deleteByClientName(String clientName) {
|
||||
OidcClient oidcClient = getByClientName(clientName);
|
||||
Assert.notNull(oidcClient, "No client found for clientName '" + clientName + "'");
|
||||
oidcClientRepository.delete(oidcClient);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param page
|
||||
* @param size
|
||||
* @param sortBy
|
||||
* @param descending
|
||||
* @return
|
||||
*/
|
||||
public Page<OidcClient> get(int page, int size, String sortBy, boolean descending) {
|
||||
Sort sort = descending ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending();
|
||||
return oidcClientRepository.findAll(PageRequest.of(page, size, sort));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.businesslogic;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.beust.jcommander.internal.Maps;
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jose.JWSSigner;
|
||||
import com.nimbusds.jose.JWSVerifier;
|
||||
import com.nimbusds.jose.crypto.RSASSASigner;
|
||||
import com.nimbusds.jose.crypto.RSASSAVerifier;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
|
||||
import com.nimbusds.jwt.JWTClaimsSet.Builder;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
import de.bstly.we.businesslogic.PermissionManager;
|
||||
import de.bstly.we.businesslogic.QuotaManager;
|
||||
import de.bstly.we.businesslogic.SystemPropertyManager;
|
||||
import de.bstly.we.businesslogic.UserManager;
|
||||
import de.bstly.we.businesslogic.UserProfileFieldManager;
|
||||
import de.bstly.we.businesslogic.UserProfileFields;
|
||||
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.model.OidcClient;
|
||||
import de.bstly.we.oidc.model.OidcToken;
|
||||
import de.bstly.we.oidc.model.QOidcToken;
|
||||
import de.bstly.we.oidc.repository.OidcTokenRepository;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
public class OidcTokenManager {
|
||||
|
||||
public static final int ACCESS_TOKEN_LENGTH = 64;
|
||||
public static final String OIDC_JWK_PUBLIC_KEY = "oidc-jwk-public-key";
|
||||
public static final String BEARER_TOKEN_TYPE = "Bearer";
|
||||
|
||||
private RSAKey publicKey;
|
||||
private JWKSet jwkSet;
|
||||
private JWSSigner signer;
|
||||
private JWSVerifier verifier;
|
||||
|
||||
@Autowired
|
||||
private OidcTokenRepository tokenRepository;
|
||||
@Autowired
|
||||
private UserManager userManager;
|
||||
@Autowired
|
||||
private UserProfileFieldManager userProfileFieldManager;
|
||||
@Autowired
|
||||
private PermissionManager permissionManager;
|
||||
@Autowired
|
||||
private QuotaManager quotaManager;
|
||||
@Autowired
|
||||
private SystemPropertyManager systemPropertyManager;
|
||||
private QOidcToken qOidcToken = QOidcToken.oidcToken;
|
||||
|
||||
@PostConstruct
|
||||
public void initOidcTokenManager() throws JOSEException {
|
||||
RSAKey rsaJWK = null;
|
||||
if (systemPropertyManager.has(OIDC_JWK_PUBLIC_KEY)) {
|
||||
try {
|
||||
rsaJWK = RSAKey.parse(systemPropertyManager.get(OIDC_JWK_PUBLIC_KEY));
|
||||
} catch (ParseException e) {
|
||||
|
||||
}
|
||||
} else {
|
||||
rsaJWK = new RSAKeyGenerator(2048).keyID("1").generate();
|
||||
systemPropertyManager.add(OIDC_JWK_PUBLIC_KEY, rsaJWK.toJSONString());
|
||||
}
|
||||
|
||||
this.publicKey = rsaJWK.toPublicJWK();
|
||||
this.signer = new RSASSASigner(rsaJWK);
|
||||
this.jwkSet = new JWKSet(this.publicKey);
|
||||
this.verifier = new RSASSAVerifier(this.publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param client
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public OidcToken createToken(OidcClient client, Long userId) {
|
||||
OidcToken token = new OidcToken();
|
||||
token.setUserId(userId);
|
||||
token.setAccessToken(RandomStringUtils.random(ACCESS_TOKEN_LENGTH, true, true));
|
||||
token.setExpiresIn(client.getTokenLifetime());
|
||||
return tokenRepository.save(token);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param client
|
||||
* @param userId
|
||||
* @param nonce
|
||||
* @param scopes
|
||||
* @param issuer
|
||||
* @return
|
||||
* @throws JOSEException
|
||||
*/
|
||||
public OidcToken createTokenWithIdToken(OidcClient client, Long userId, String nonce,
|
||||
Set<String> scopes, String issuer) throws JOSEException {
|
||||
OidcToken token = new OidcToken();
|
||||
token.setClient(client.getId());
|
||||
|
||||
User user = userManager.get(userId);
|
||||
|
||||
Assert.notNull(user, "User does not exist!");
|
||||
|
||||
token.setUserId(user.getId());
|
||||
token.setAccessToken(RandomStringUtils.random(ACCESS_TOKEN_LENGTH, true, true));
|
||||
token.setExpiresIn(client.getTokenLifetime());
|
||||
|
||||
Builder claimsSetBuilder = createUserClaims(user);
|
||||
|
||||
claimsSetBuilder.issuer(issuer);
|
||||
claimsSetBuilder.audience(client.getClientId());
|
||||
claimsSetBuilder.expirationTime(
|
||||
Date.from(Instant.now().plus(client.getTokenLifetime(), ChronoUnit.SECONDS)
|
||||
.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
claimsSetBuilder.issueTime(new Date());
|
||||
|
||||
if (StringUtils.hasText(nonce)) {
|
||||
claimsSetBuilder.claim("nonce", nonce);
|
||||
}
|
||||
|
||||
JWSHeader.Builder headerBuilder = new JWSHeader.Builder(JWSAlgorithm.RS256);
|
||||
headerBuilder.keyID(getPublicKey().getKeyID());
|
||||
|
||||
SignedJWT jwt = new SignedJWT(headerBuilder.build(), claimsSetBuilder.build());
|
||||
jwt.sign(getSigner());
|
||||
|
||||
token.setIdToken(jwt.serialize());
|
||||
|
||||
return tokenRepository.save(token);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public Builder createUserClaims(User user) {
|
||||
|
||||
Builder claimsSetBuilder = new Builder();
|
||||
claimsSetBuilder.subject(String.valueOf(user.getId()));
|
||||
claimsSetBuilder.claim("name", user.getUsername());
|
||||
claimsSetBuilder.claim("preferred_username", user.getUsername());
|
||||
|
||||
UserProfileField emailProfileField = userProfileFieldManager.get(user.getId(),
|
||||
UserProfileFields.PROFILE_FIELD_EMAIL);
|
||||
UserProfileField emailPrimaryProfileField = userProfileFieldManager.get(user.getId(),
|
||||
UserProfileFields.PROFILE_FIELD_EMAIL_PRIMARY);
|
||||
if (emailProfileField != null && emailPrimaryProfileField != null
|
||||
&& StringUtils.hasText(emailProfileField.getValue())
|
||||
&& Boolean.getBoolean(emailPrimaryProfileField.getValue())) {
|
||||
claimsSetBuilder.claim("email", emailProfileField.getValue());
|
||||
} else {
|
||||
claimsSetBuilder.claim("email_verified", true);
|
||||
claimsSetBuilder.claim("email", userManager.getBstlyEmail(user.getUsername()));
|
||||
}
|
||||
|
||||
UserProfileField localeProfileField = userProfileFieldManager.get(user.getId(),
|
||||
UserProfileFields.PROFILE_FIELD_LOCALE);
|
||||
|
||||
if (localeProfileField != null) {
|
||||
if ("de-informal".equals(localeProfileField.getValue())) {
|
||||
claimsSetBuilder.claim(UserProfileFields.PROFILE_FIELD_LOCALE, "de");
|
||||
} else if (StringUtils.hasText(localeProfileField.getValue())) {
|
||||
claimsSetBuilder.claim(UserProfileFields.PROFILE_FIELD_LOCALE,
|
||||
localeProfileField.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> permissions = Maps.newHashMap();
|
||||
for (Permission permission : permissionManager.getNotExpiresByTarget(user.getId())) {
|
||||
permissions.put(permission.getName(), permission.getExpires().toString());
|
||||
}
|
||||
|
||||
if (!permissions.isEmpty()) {
|
||||
claimsSetBuilder.claim("permissions", permissions);
|
||||
}
|
||||
|
||||
Map<String, String> quotas = Maps.newHashMap();
|
||||
for (Quota quota : quotaManager.getNotExpiresByTarget(user.getId())) {
|
||||
quotas.put(quota.getName(), String.valueOf(quota.getValue()) + quota.getUnit());
|
||||
}
|
||||
|
||||
if (!quotas.isEmpty()) {
|
||||
claimsSetBuilder.claim("quotas", quotas);
|
||||
}
|
||||
|
||||
return claimsSetBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param accessToken
|
||||
* @return
|
||||
*/
|
||||
public OidcToken getByAccessToken(String accessToken) {
|
||||
return tokenRepository.findOne(qOidcToken.accessToken.eq(accessToken)).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public JWSSigner getSigner() {
|
||||
return signer;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public JWSVerifier getVerifier() {
|
||||
return verifier;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public RSAKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public JWKSet getJwkSet() {
|
||||
return jwkSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.controller;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
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.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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/oidc/clients")
|
||||
public class OIDCClientController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private OidcClientManager registeredClientService;
|
||||
@Autowired
|
||||
private OidcClientRepository registeredClientRepository;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pageParameter
|
||||
* @param sizeParameter
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping
|
||||
public Page<OidcClient> getClients(@RequestParam("page") Optional<Integer> pageParameter,
|
||||
@RequestParam("size") Optional<Integer> sizeParameter) {
|
||||
return registeredClientService.get(pageParameter.orElse(0), sizeParameter.orElse(10),
|
||||
"clientName", true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping("/{name}")
|
||||
public OidcClient get(@PathVariable("name") String name) {
|
||||
OidcClient client = registeredClientService.getByClientName(name);
|
||||
if (client == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientId
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping("/id/{clientId}")
|
||||
public OidcClient getByClientId(@PathVariable("clientId") String clientId) {
|
||||
OidcClient client = registeredClientService.getByClientId(clientId);
|
||||
if (client == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param oidcClientModel
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping
|
||||
public OidcClient create(@RequestBody OidcClientModel oidcClientModel) {
|
||||
if (registeredClientService.getByClientName(oidcClientModel.getName()) != null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
if (oidcClientModel.getClientAuthenticationMethods() == null
|
||||
|| oidcClientModel.getClientAuthenticationMethods().isEmpty()) {
|
||||
oidcClientModel.setClientAuthenticationMethods(Sets.newHashSet(
|
||||
OidcClientAuthenticationMethod.basic, OidcClientAuthenticationMethod.post));
|
||||
}
|
||||
|
||||
if (oidcClientModel.getAuthorizationGrantTypes() == null
|
||||
|| oidcClientModel.getAuthorizationGrantTypes().isEmpty()) {
|
||||
oidcClientModel.setAuthorizationGrantTypes(
|
||||
Sets.newHashSet(OidcAuthorizationGrantType.authorization_code));
|
||||
}
|
||||
|
||||
return registeredClientService.createClient(oidcClientModel.getName(),
|
||||
oidcClientModel.getClientAuthenticationMethods(),
|
||||
oidcClientModel.getAuthorizationGrantTypes(),
|
||||
oidcClientModel.getRegisteredRedirectUris(), oidcClientModel.getScopes(),
|
||||
oidcClientModel.getLoginUrl(), oidcClientModel.isAlwaysPermitted());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param oAuth2ClientModel
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PatchMapping
|
||||
public OidcClient update(@RequestBody OidcClient client) {
|
||||
|
||||
OidcClient oldClient = registeredClientRepository.findById(client.getId()).orElse(null);
|
||||
|
||||
if (oldClient == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
OidcClient otherClient = registeredClientService.getByClientName(client.getClientName());
|
||||
|
||||
if (otherClient != null && !otherClient.getId().equals(client.getId())) {
|
||||
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
return registeredClientService.updateClient(client);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param oAuth2ClientModel
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@DeleteMapping("/{name}")
|
||||
public void deleteClient(@PathVariable("name") String name) {
|
||||
if (registeredClientService.getByClientName(name) == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
|
||||
}
|
||||
registeredClientService.deleteByClientName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param oAuth2ClientModel
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/{name}/secret")
|
||||
public OidcClient createNewSecret(@PathVariable("name") String name) {
|
||||
if (registeredClientService.getByClientName(name) == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
|
||||
}
|
||||
return registeredClientService.createNewSecretByClientName(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
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.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.model.OidcClient;
|
||||
import de.bstly.we.security.model.LocalUserDetails;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@RequestMapping("/oidc/authorize")
|
||||
@RestController
|
||||
public class OidcAuthorizationController {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(OidcAuthorizationController.class);
|
||||
|
||||
@Autowired
|
||||
private PermissionManager permissionManager;
|
||||
@Autowired
|
||||
private OidcClientManager oidcClientManager;
|
||||
@Autowired
|
||||
private OidcAuthorizationCodeManager oidcAuthorizationCodeManager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param scope
|
||||
* @param responseType
|
||||
* @param clientId
|
||||
* @param redirectUri
|
||||
* @param state
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws IOException
|
||||
*/
|
||||
@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 {
|
||||
|
||||
if (!StringUtils.hasText(clientId)) {
|
||||
logger.debug("missing client_id");
|
||||
throw new InvalidAuthorizationRequestError(redirectUri,
|
||||
OidcAuthorizationErrorCode.INVALID_REQUEST, "missing client_id", state);
|
||||
}
|
||||
|
||||
OidcClient client = oidcClientManager.getByClientId(clientId);
|
||||
|
||||
if (client == null) {
|
||||
logger.debug("invalid client_id: " + clientId);
|
||||
throw new InvalidAuthorizationRequestError(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, "invalid redirect_uri", state);
|
||||
}
|
||||
|
||||
if (!client.isAlwaysPermitted()
|
||||
&& !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, "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);
|
||||
|
||||
}
|
||||
|
||||
if (!responseType.equals(OidcAuthorizationResponseType.code.toString())) {
|
||||
logger.debug("response type not allowed: " + OidcAuthorizationResponseType.code
|
||||
|
||||
+ " - " + client.getClientName());
|
||||
throw new InvalidAuthorizationRequestError(redirectUri,
|
||||
OidcAuthorizationErrorCode.UNSUPPORTED_RESPONSE_TYPE,
|
||||
"response type not allowed", state);
|
||||
|
||||
}
|
||||
|
||||
Set<String> scopes = Sets.newHashSet(scope.split(" "));
|
||||
|
||||
if (!scopes.contains("openid")) {
|
||||
logger.debug("missing openid scope: " + scopes + " - " + client.getClientName());
|
||||
throw new InvalidAuthorizationRequestError(redirectUri,
|
||||
OidcAuthorizationErrorCode.INVALID_SCOPE, "missing openid scope", state);
|
||||
|
||||
}
|
||||
|
||||
OidcAuthorizationCode authorizationCode = oidcAuthorizationCodeManager.create(clientId,
|
||||
redirectUri, scopes, principal.getUserId(), nonce);
|
||||
|
||||
String uri = redirectUri.toString();
|
||||
|
||||
if (StringUtils.hasText(redirectUri.getQuery())) {
|
||||
uri += "&code=" + authorizationCode.getCode();
|
||||
} else {
|
||||
uri += "?code=" + authorizationCode.getCode();
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(state)) {
|
||||
uri += "&state=" + state;
|
||||
}
|
||||
|
||||
response.sendRedirect(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param exception
|
||||
* @param httpServletResponse
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
@ExceptionHandler(InvalidAuthorizationRequestError.class)
|
||||
public void handle(InvalidAuthorizationRequestError exception, HttpServletResponse response)
|
||||
throws IOException {
|
||||
String uri = exception.getRedirectUri().toString();
|
||||
|
||||
uri += "?error=" + exception.getErrorCode().getAuthorizationErrorCode();
|
||||
|
||||
if (StringUtils.hasText(exception.getErrorDescription())) {
|
||||
uri += "&error_description=" + exception.getErrorDescription();
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(exception.getState())) {
|
||||
uri += "&state=" + exception.getState();
|
||||
}
|
||||
|
||||
response.sendRedirect(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param redirectUri
|
||||
* @param errorCode
|
||||
* @param errorDescription
|
||||
* @param state
|
||||
*/
|
||||
InvalidAuthorizationRequestError(URI redirectUri, OidcAuthorizationErrorCode errorCode,
|
||||
String errorDescription, String state) {
|
||||
super(errorDescription);
|
||||
this.redirectUri = redirectUri;
|
||||
this.errorCode = errorCode;
|
||||
this.errorDescription = errorDescription;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the redirectUri
|
||||
*/
|
||||
public URI getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param redirectUri the redirectUri to set
|
||||
*/
|
||||
public void setRedirectUri(URI redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the errorCode
|
||||
*/
|
||||
public OidcAuthorizationErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode the errorCode to set
|
||||
*/
|
||||
public void setErrorCode(OidcAuthorizationErrorCode errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the errorDescription
|
||||
*/
|
||||
public String getErrorDescription() {
|
||||
return errorDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorDescription the errorDescription to set
|
||||
*/
|
||||
public void setErrorDescription(String errorDescription) {
|
||||
this.errorDescription = errorDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the state
|
||||
*/
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param state the state to set
|
||||
*/
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.controller;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import de.bstly.we.controller.support.EntityResponseStatusException;
|
||||
import de.bstly.we.oidc.model.OidcConfiguration;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@RequestMapping("/.well-known/openid-configuration")
|
||||
@RestController
|
||||
public class OidcDiscoveryController {
|
||||
|
||||
@Value("${oidc.provider.issuer:}")
|
||||
private String oidcIssuer;
|
||||
|
||||
@GetMapping
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
config.setIssuer(issuer);
|
||||
config.setScopes_supported(Sets.newHashSet("openid"));
|
||||
config.setResponse_types_supported(Sets.newHashSet("code"));
|
||||
config.setGrant_types_supported(Sets.newHashSet("authorization_code"));
|
||||
config.setToken_endpoint_auth_methods_supported(
|
||||
Sets.newHashSet("client_secret_post", "client_secret_basic"));
|
||||
config.setSubject_types_supported(Sets.newHashSet("public"));
|
||||
|
||||
try {
|
||||
config.setAuthorization_endpoint(new URI(issuer + "/oidc/authorize"));
|
||||
config.setToken_endpoint(new URI(issuer + "/oidc/token"));
|
||||
config.setUserinfo_endpoint(new URI(issuer + "/oidc/userinfo"));
|
||||
config.setJwks_uri(new URI(issuer + "/oidc/jwks"));
|
||||
} catch (URISyntaxException e) {
|
||||
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.controller;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.oidc.businesslogic.OidcTokenManager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@RequestMapping("/oidc/jwks")
|
||||
@RestController
|
||||
public class OidcJwksController {
|
||||
|
||||
@Autowired
|
||||
private OidcTokenManager oidcTokenManager;
|
||||
|
||||
@GetMapping
|
||||
public Map<String, Object> getJwks() {
|
||||
return oidcTokenManager.getJwkSet().toJSONObject();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
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.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
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.OidcClientManager;
|
||||
import de.bstly.we.oidc.businesslogic.OidcTokenManager;
|
||||
import de.bstly.we.oidc.model.OidcAuthorizationCode;
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@RequestMapping("/oidc/token")
|
||||
@RestController
|
||||
public class OidcTokenController {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(OidcTokenController.class);
|
||||
|
||||
public static final String BASIC_AUTH = "Basic";
|
||||
|
||||
@Autowired
|
||||
private OidcClientManager oidcClientManager;
|
||||
@Autowired
|
||||
private OidcAuthorizationCodeManager oidcAuthorizationCodeManager;
|
||||
@Autowired
|
||||
private OidcTokenManager oidcTokenManager;
|
||||
|
||||
@Value("${oidc.provider.issuer:}")
|
||||
private String oidcIssuer;
|
||||
|
||||
@PostMapping
|
||||
public OidcTokenResponse getToken(
|
||||
// Authorization header for BASIC client authentication method
|
||||
@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String authorizationHeader,
|
||||
// token request
|
||||
@ModelAttribute("tokenRequest") OidcTokenRequest tokenRequest,
|
||||
// the request
|
||||
HttpServletRequest request,
|
||||
// the response
|
||||
HttpServletResponse response) {
|
||||
|
||||
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
|
||||
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
|
||||
|
||||
OidcClientAuthenticationMethod clientAuthenticationMethod = OidcClientAuthenticationMethod.post;
|
||||
|
||||
if (StringUtils.hasText(authorizationHeader)
|
||||
&& StringUtils.startsWithIgnoreCase(authorizationHeader, BASIC_AUTH)) {
|
||||
String decoded = new String(Base64.getDecoder().decode(authorizationHeader
|
||||
.replaceFirst(BASIC_AUTH, "").trim().getBytes(StandardCharsets.UTF_8)),
|
||||
StandardCharsets.UTF_8);
|
||||
if (decoded.indexOf(":") != -1) {
|
||||
tokenRequest.setClient_id(decoded.split(":")[0]);
|
||||
tokenRequest.setClient_secret(decoded.split(":")[1]);
|
||||
clientAuthenticationMethod = OidcClientAuthenticationMethod.basic;
|
||||
} else {
|
||||
logger.debug("invalid_basic_authentication: " + decoded);
|
||||
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_CLIENT,
|
||||
"invalid_basic_authentication");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
if (!client.getClientAuthenticationMethods().contains(clientAuthenticationMethod)) {
|
||||
logger.debug("invalid_authentication_method: " + clientAuthenticationMethod);
|
||||
throw new InvalidTokenRequestError(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");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
OidcToken token = null;
|
||||
switch (tokenRequest.getGrant_type()) {
|
||||
case authorization_code:
|
||||
OidcAuthorizationCode authorizationCode = oidcAuthorizationCodeManager
|
||||
.getByCode(tokenRequest.getCode());
|
||||
if (authorizationCode == null) {
|
||||
logger.debug("invalid authorization code: " + tokenRequest.getCode());
|
||||
throw new InvalidTokenRequestError(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");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
Set<String> scopes = StringUtils.hasText(tokenRequest.getScope())
|
||||
? Sets.newHashSet(tokenRequest.getScope().split(" "))
|
||||
: authorizationCode.getScopes();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
token = oidcTokenManager.createTokenWithIdToken(client,
|
||||
authorizationCode.getUserId(), authorizationCode.getNonce(), scopes,
|
||||
issuer);
|
||||
} catch (JOSEException e) {
|
||||
logger.error("error creating token", client, authorizationCode);
|
||||
e.printStackTrace();
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
oidcAuthorizationCodeManager.removeByCode(tokenRequest.getCode());
|
||||
break;
|
||||
case client_credentials:
|
||||
token = oidcTokenManager.createToken(client, client.getId());
|
||||
break;
|
||||
}
|
||||
|
||||
OidcTokenResponse tokenResponse = new OidcTokenResponse();
|
||||
|
||||
tokenResponse.setAccess_token(token.getAccessToken());
|
||||
tokenResponse.setId_token(token.getIdToken());
|
||||
tokenResponse.setToken_type(OidcTokenManager.BEARER_TOKEN_TYPE);
|
||||
tokenResponse.setExpires_in(client.getTokenLifetime());
|
||||
|
||||
oidcAuthorizationCodeManager.removeByCode(tokenRequest.getCode());
|
||||
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param exception
|
||||
* @param httpServletResponse
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
@ExceptionHandler(InvalidTokenRequestError.class)
|
||||
public ResponseEntity<String> handle(InvalidTokenRequestError exception,
|
||||
HttpServletResponse response) throws IOException {
|
||||
// response.sendError(400, "redirect uri mismatch");
|
||||
return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON)
|
||||
.body(" {\"error\": \"" + exception.getMessage() + "\"}");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
static class InvalidTokenRequestError extends RuntimeException {
|
||||
|
||||
/**
|
||||
* default serialVersionUID
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private OidcTokenErrorCode errorCode;
|
||||
private String errorDescription;
|
||||
|
||||
InvalidTokenRequestError(OidcTokenErrorCode errorCode, String errorDescription) {
|
||||
super(errorDescription);
|
||||
this.errorCode = errorCode;
|
||||
this.errorDescription = errorDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the errorCode
|
||||
*/
|
||||
public OidcTokenErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode the errorCode to set
|
||||
*/
|
||||
public void setErrorCode(OidcTokenErrorCode errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the errorDescription
|
||||
*/
|
||||
public String getErrorDescription() {
|
||||
return errorDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorDescription the errorDescription to set
|
||||
*/
|
||||
public void setErrorDescription(String errorDescription) {
|
||||
this.errorDescription = errorDescription;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.nimbusds.jwt.JWTClaimsSet.Builder;
|
||||
|
||||
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.OidcTokenManager;
|
||||
import de.bstly.we.oidc.model.OidcToken;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@RequestMapping("/oidc/userinfo")
|
||||
@RestController
|
||||
public class OidcUserInfoController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private OidcTokenManager oidcTokenManager;
|
||||
@Autowired
|
||||
private UserManager userManager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param authorizationHeader
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(method = { RequestMethod.GET, RequestMethod.POST })
|
||||
public void getUserInfo(
|
||||
@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String authorizationHeader) {
|
||||
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
Long userId = getCurrentUserId();
|
||||
if (!auth.isAuthenticated()) {
|
||||
if (authorizationHeader == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
String accessToken = authorizationHeader.replaceFirst(OidcTokenManager.BEARER_TOKEN_TYPE, "").trim();
|
||||
|
||||
OidcToken token = oidcTokenManager.getByAccessToken(accessToken);
|
||||
if (token == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
userId = token.getUserId();
|
||||
}
|
||||
|
||||
User user = userManager.get(userId);
|
||||
|
||||
if (user == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
Builder claimsSetBuilder = oidcTokenManager.createUserClaims(user);
|
||||
|
||||
throw new EntityResponseStatusException(claimsSetBuilder.build().toJSONObject(), HttpStatus.OK);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public class OidcClientModel {
|
||||
|
||||
private String name;
|
||||
private Set<String> registeredRedirectUris;
|
||||
private Set<OidcClientAuthenticationMethod> clientAuthenticationMethods;
|
||||
private Set<OidcAuthorizationGrantType> authorizationGrantTypes;
|
||||
private Set<String> scopes;
|
||||
private String loginUrl;
|
||||
private boolean alwaysPermitted;
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the registeredRedirectUris
|
||||
*/
|
||||
public Set<String> getRegisteredRedirectUris() {
|
||||
return registeredRedirectUris;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param registeredRedirectUris the registeredRedirectUris to set
|
||||
*/
|
||||
public void setRegisteredRedirectUris(Set<String> registeredRedirectUris) {
|
||||
this.registeredRedirectUris = registeredRedirectUris;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientAuthenticationMethods
|
||||
*/
|
||||
public Set<OidcClientAuthenticationMethod> getClientAuthenticationMethods() {
|
||||
return clientAuthenticationMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientAuthenticationMethods the clientAuthenticationMethods to set
|
||||
*/
|
||||
public void setClientAuthenticationMethods(
|
||||
Set<OidcClientAuthenticationMethod> clientAuthenticationMethods) {
|
||||
this.clientAuthenticationMethods = clientAuthenticationMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the authorizationGrantTypes
|
||||
*/
|
||||
public Set<OidcAuthorizationGrantType> getAuthorizationGrantTypes() {
|
||||
return authorizationGrantTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authorizationGrantTypes the authorizationGrantTypes to set
|
||||
*/
|
||||
public void setAuthorizationGrantTypes(
|
||||
Set<OidcAuthorizationGrantType> authorizationGrantTypes) {
|
||||
this.authorizationGrantTypes = authorizationGrantTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scopes
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scopes the scopes to set
|
||||
*/
|
||||
public void setScopes(Set<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the loginUrl
|
||||
*/
|
||||
public String getLoginUrl() {
|
||||
return loginUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loginUrl the loginUrl to set
|
||||
*/
|
||||
public void setLoginUrl(String loginUrl) {
|
||||
this.loginUrl = loginUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the alwaysPermitted
|
||||
*/
|
||||
public boolean isAlwaysPermitted() {
|
||||
return alwaysPermitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param alwaysPermitted the alwaysPermitted to set
|
||||
*/
|
||||
public void setAlwaysPermitted(boolean alwaysPermitted) {
|
||||
this.alwaysPermitted = alwaysPermitted;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public class OidcAuthorizationCode {
|
||||
|
||||
public static final int CODE_LENGTH = 32;
|
||||
public static final int EXPIRY_MINUTES = 2;
|
||||
|
||||
private final String clientId;
|
||||
private final URI redirectUri;
|
||||
private final Set<String> scopes;
|
||||
private final String code;
|
||||
private final Instant expiry;
|
||||
private final Long userId;
|
||||
private final String nonce;
|
||||
|
||||
/**
|
||||
* @param clientId
|
||||
* @param redirectUri
|
||||
* @param scopes
|
||||
* @param code
|
||||
* @param expiry
|
||||
* @param subject
|
||||
*/
|
||||
public OidcAuthorizationCode(String clientId, URI redirectUri, Set<String> scopes, Long userId,
|
||||
String nonce) {
|
||||
this.clientId = clientId;
|
||||
this.redirectUri = redirectUri;
|
||||
this.scopes = scopes;
|
||||
this.code = RandomStringUtils.random(CODE_LENGTH, true, true);
|
||||
this.expiry = Instant.now().plus(EXPIRY_MINUTES, ChronoUnit.MINUTES);
|
||||
this.userId = userId;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientId
|
||||
*/
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the redirectUri
|
||||
*/
|
||||
public URI getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scopes
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the code
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expiry
|
||||
*/
|
||||
public Instant getExpiry() {
|
||||
return expiry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the userId
|
||||
*/
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the nonce
|
||||
*/
|
||||
public String getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public enum OidcAuthorizationErrorCode {
|
||||
INVALID_REQUEST("invalid_request"), UNAUTHORIZED_CLIENT("unauthorized_client"), ACCESS_DENIED("access_denied"),
|
||||
UNSUPPORTED_RESPONSE_TYPE("unsupported_response_type"), INVALID_SCOPE("invalid_scope"),
|
||||
SERVER_ERROR("server_error"), TEMPORARILY_UNAVAILABLE("temporarily_unavailable"),
|
||||
INTERACTION_REQUIRED("interaction_required"), LOGIN_REQUIRED("login_required"),
|
||||
ACCOUNT_SELECTION_REQUIRED("account_selection_required"), CONSENT_REQUIRED("consent_required"),
|
||||
INVALID_REQUEST_URI("invalid_request_uri"), INVALID_REQUEST_OBJECT("invalid_request_object"),
|
||||
REQUEST_NOT_SUPPORTED("request_not_supported"), REQUEST_URI_NOT_SUPPORTED("invalid_request"),
|
||||
REGISTRATION_NOT_SUPPORTED("registration_not_supported");
|
||||
|
||||
private final String authorizationErrorCode;
|
||||
|
||||
OidcAuthorizationErrorCode(String authorizationErrorCode) {
|
||||
this.authorizationErrorCode = authorizationErrorCode;
|
||||
}
|
||||
|
||||
public String getAuthorizationErrorCode() {
|
||||
return authorizationErrorCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public enum OidcAuthorizationGrantType {
|
||||
authorization_code, client_credentials
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public enum OidcAuthorizationResponseType {
|
||||
code
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "oidc_clients")
|
||||
public class OidcClient {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
@Column(name = "client_name", unique = true)
|
||||
private String clientName;
|
||||
@Column(name = "client_id", unique = true)
|
||||
private String clientId;
|
||||
@Column(name = "client_secret")
|
||||
private String clientSecret;
|
||||
@ElementCollection
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@CollectionTable(name = "oidc_clients_methods")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Set<OidcClientAuthenticationMethod> clientAuthenticationMethods;
|
||||
@ElementCollection
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@CollectionTable(name = "oidc_clients_grant_types")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Set<OidcAuthorizationGrantType> authorizationGrantTypes;
|
||||
@ElementCollection
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@CollectionTable(name = "oidc_clients_redirect_uris")
|
||||
private Set<String> redirectUris;
|
||||
@ElementCollection
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@CollectionTable(name = "oidc_clients_scopes")
|
||||
private Set<String> scopes;
|
||||
@Column(name = "token_lifetime")
|
||||
private Long tokenLifetime;
|
||||
@Column(name = "login_url", length = 1024)
|
||||
private String loginUrl;
|
||||
@Column(name = "always_permitted", columnDefinition = "boolean default false")
|
||||
private boolean alwaysPermitted;
|
||||
@Column(name = "category")
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* @return the id
|
||||
*/
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the id to set
|
||||
*/
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientName
|
||||
*/
|
||||
public String getClientName() {
|
||||
return clientName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientName the clientName to set
|
||||
*/
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientId
|
||||
*/
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientId the clientId to set
|
||||
*/
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientSecret
|
||||
*/
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientSecret the clientSecret to set
|
||||
*/
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientAuthenticationMethods
|
||||
*/
|
||||
public Set<OidcClientAuthenticationMethod> getClientAuthenticationMethods() {
|
||||
return clientAuthenticationMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientAuthenticationMethods the clientAuthenticationMethods to set
|
||||
*/
|
||||
public void setClientAuthenticationMethods(
|
||||
Set<OidcClientAuthenticationMethod> clientAuthenticationMethods) {
|
||||
this.clientAuthenticationMethods = clientAuthenticationMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the authorizationGrantTypes
|
||||
*/
|
||||
public Set<OidcAuthorizationGrantType> getAuthorizationGrantTypes() {
|
||||
return authorizationGrantTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authorizationGrantTypes the authorizationGrantTypes to set
|
||||
*/
|
||||
public void setAuthorizationGrantTypes(
|
||||
Set<OidcAuthorizationGrantType> authorizationGrantTypes) {
|
||||
this.authorizationGrantTypes = authorizationGrantTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the redirectUris
|
||||
*/
|
||||
public Set<String> getRedirectUris() {
|
||||
return redirectUris;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param redirectUris the redirectUris to set
|
||||
*/
|
||||
public void setRedirectUris(Set<String> redirectUris) {
|
||||
this.redirectUris = redirectUris;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scopes
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scopes the scopes to set
|
||||
*/
|
||||
public void setScopes(Set<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tokenLifetime
|
||||
*/
|
||||
public Long getTokenLifetime() {
|
||||
return tokenLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tokenLifetime the tokenLifetime to set
|
||||
*/
|
||||
public void setTokenLifetime(Long tokenLifetime) {
|
||||
this.tokenLifetime = tokenLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the loginUrl
|
||||
*/
|
||||
public String getLoginUrl() {
|
||||
return loginUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loginUrl the loginUrl to set
|
||||
*/
|
||||
public void setLoginUrl(String loginUrl) {
|
||||
this.loginUrl = loginUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the alwaysPermitted
|
||||
*/
|
||||
public boolean isAlwaysPermitted() {
|
||||
return alwaysPermitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param alwaysPermitted the alwaysPermitted to set
|
||||
*/
|
||||
public void setAlwaysPermitted(boolean alwaysPermitted) {
|
||||
this.alwaysPermitted = alwaysPermitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the category
|
||||
*/
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param category the category to set
|
||||
*/
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public enum OidcClientAuthenticationMethod {
|
||||
basic, post
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public class OidcConfiguration {
|
||||
|
||||
private String issuer;
|
||||
private URI authorization_endpoint;
|
||||
private URI token_endpoint;
|
||||
private URI userinfo_endpoint;
|
||||
private URI jwks_uri;
|
||||
private Set<String> scopes_supported;
|
||||
private Set<String> response_types_supported;
|
||||
private Set<String> subject_types_supported;
|
||||
private Set<String> grant_types_supported;
|
||||
private Set<String> token_endpoint_auth_methods_supported;
|
||||
|
||||
/**
|
||||
* @return the issuer
|
||||
*/
|
||||
public String getIssuer() {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param issuer the issuer to set
|
||||
*/
|
||||
public void setIssuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the authorization_endpoint
|
||||
*/
|
||||
public URI getAuthorization_endpoint() {
|
||||
return authorization_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authorization_endpoint the authorization_endpoint to set
|
||||
*/
|
||||
public void setAuthorization_endpoint(URI authorization_endpoint) {
|
||||
this.authorization_endpoint = authorization_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the token_endpoint
|
||||
*/
|
||||
public URI getToken_endpoint() {
|
||||
return token_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param token_endpoint the token_endpoint to set
|
||||
*/
|
||||
public void setToken_endpoint(URI token_endpoint) {
|
||||
this.token_endpoint = token_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the userinfo_endpoint
|
||||
*/
|
||||
public URI getUserinfo_endpoint() {
|
||||
return userinfo_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userinfo_endpoint the userinfo_endpoint to set
|
||||
*/
|
||||
public void setUserinfo_endpoint(URI userinfo_endpoint) {
|
||||
this.userinfo_endpoint = userinfo_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the jwks_uri
|
||||
*/
|
||||
public URI getJwks_uri() {
|
||||
return jwks_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param jwks_uri the jwks_uri to set
|
||||
*/
|
||||
public void setJwks_uri(URI jwks_uri) {
|
||||
this.jwks_uri = jwks_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scopes_supported
|
||||
*/
|
||||
public Set<String> getScopes_supported() {
|
||||
return scopes_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scopes_supported the scopes_supported to set
|
||||
*/
|
||||
public void setScopes_supported(Set<String> scopes_supported) {
|
||||
this.scopes_supported = scopes_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the response_types_supported
|
||||
*/
|
||||
public Set<String> getResponse_types_supported() {
|
||||
return response_types_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param response_types_supported the response_types_supported to set
|
||||
*/
|
||||
public void setResponse_types_supported(Set<String> response_types_supported) {
|
||||
this.response_types_supported = response_types_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the subject_types_supported
|
||||
*/
|
||||
public Set<String> getSubject_types_supported() {
|
||||
return subject_types_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subject_types_supported the subject_types_supported to set
|
||||
*/
|
||||
public void setSubject_types_supported(Set<String> subject_types_supported) {
|
||||
this.subject_types_supported = subject_types_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the grant_types_supported
|
||||
*/
|
||||
public Set<String> getGrant_types_supported() {
|
||||
return grant_types_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param grant_types_supported the grant_types_supported to set
|
||||
*/
|
||||
public void setGrant_types_supported(Set<String> grant_types_supported) {
|
||||
this.grant_types_supported = grant_types_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the token_endpoint_auth_methods_supported
|
||||
*/
|
||||
public Set<String> getToken_endpoint_auth_methods_supported() {
|
||||
return token_endpoint_auth_methods_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param token_endpoint_auth_methods_supported the
|
||||
* token_endpoint_auth_methods_supported
|
||||
* to set
|
||||
*/
|
||||
public void setToken_endpoint_auth_methods_supported(
|
||||
Set<String> token_endpoint_auth_methods_supported) {
|
||||
this.token_endpoint_auth_methods_supported = token_endpoint_auth_methods_supported;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "oidc_tokens")
|
||||
public class OidcToken {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
@Column(name = "user_id")
|
||||
private Long userId;
|
||||
@Column(name = "client_id")
|
||||
private Long client;
|
||||
@Column(name = "access_token")
|
||||
private String accessToken;
|
||||
@Column(name = "refresh_token")
|
||||
private String refreshToken;
|
||||
@Column(name = "expires_in")
|
||||
private Long expiresIn;
|
||||
@Column(name = "id_token", length = 4000)
|
||||
private String idToken;
|
||||
@JsonIgnore
|
||||
@ElementCollection
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
private Set<String> scopes;
|
||||
|
||||
/**
|
||||
* @return the id
|
||||
*/
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the id to set
|
||||
*/
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the userId
|
||||
*/
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userId the userId to set
|
||||
*/
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the client
|
||||
*/
|
||||
public Long getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param client the client to set
|
||||
*/
|
||||
public void setClient(Long client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the accessToken
|
||||
*/
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param accessToken the accessToken to set
|
||||
*/
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the refreshToken
|
||||
*/
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param refreshToken the refreshToken to set
|
||||
*/
|
||||
public void setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expiresIn
|
||||
*/
|
||||
public Long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expiresIn the expiresIn to set
|
||||
*/
|
||||
public void setExpiresIn(Long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the idToken
|
||||
*/
|
||||
public String getIdToken() {
|
||||
return idToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param idToken the idToken to set
|
||||
*/
|
||||
public void setIdToken(String idToken) {
|
||||
this.idToken = idToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scopes
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scopes the scopes to set
|
||||
*/
|
||||
public void setScopes(Set<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public enum OidcTokenErrorCode {
|
||||
INVALID_REQUEST("invalid_request"), INVALID_CLIENT("invalid_client"),
|
||||
INVALID_GRANT("invalid_grant"), UNAUTHORIZED_CLIENT("unauthorized_client"),
|
||||
UNSUPPORTED_GRANT_TYPE("unsupported_grant_type"), INVALID_SCOPE("invalid_scope");
|
||||
|
||||
private final String tokenErrorCode;
|
||||
|
||||
OidcTokenErrorCode(String tokenErrorCode) {
|
||||
this.tokenErrorCode = tokenErrorCode;
|
||||
}
|
||||
|
||||
public String getTokenErrorCode() {
|
||||
return tokenErrorCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public class OidcTokenRequest {
|
||||
|
||||
private final String code;
|
||||
private final OidcAuthorizationGrantType grant_type;
|
||||
private String client_id;
|
||||
private String client_secret;
|
||||
private final URI redirect_uri;
|
||||
private final String scope;
|
||||
|
||||
/**
|
||||
* @param code
|
||||
* @param grant_type
|
||||
* @param client_id
|
||||
* @param client_secret
|
||||
* @param redirect_uri
|
||||
* @param scope
|
||||
*/
|
||||
public OidcTokenRequest(String code, OidcAuthorizationGrantType grant_type, String client_id,
|
||||
String client_secret, 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.redirect_uri = redirect_uri;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the client_id
|
||||
*/
|
||||
public String getClient_id() {
|
||||
return client_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param client_id the client_id to set
|
||||
*/
|
||||
public void setClient_id(String client_id) {
|
||||
this.client_id = client_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the client_secret
|
||||
*/
|
||||
public String getClient_secret() {
|
||||
return client_secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param client_secret the client_secret to set
|
||||
*/
|
||||
public void setClient_secret(String client_secret) {
|
||||
this.client_secret = client_secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the code
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the grant_type
|
||||
*/
|
||||
public OidcAuthorizationGrantType getGrant_type() {
|
||||
return grant_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the redirect_uri
|
||||
*/
|
||||
public URI getRedirect_uri() {
|
||||
return redirect_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scope
|
||||
*/
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.oidc.model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public class OidcTokenResponse {
|
||||
|
||||
private String access_token;
|
||||
private String token_type;
|
||||
private String refresh_token;
|
||||
private long expires_in;
|
||||
private String id_token;
|
||||
|
||||
/**
|
||||
* @return the access_token
|
||||
*/
|
||||
public String getAccess_token() {
|
||||
return access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param access_token the access_token to set
|
||||
*/
|
||||
public void setAccess_token(String access_token) {
|
||||
this.access_token = access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the token_type
|
||||
*/
|
||||
public String getToken_type() {
|
||||
return token_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param token_type the token_type to set
|
||||
*/
|
||||
public void setToken_type(String token_type) {
|
||||
this.token_type = token_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the refresh_token
|
||||
*/
|
||||
public String getRefresh_token() {
|
||||
return refresh_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param refresh_token the refresh_token to set
|
||||
*/
|
||||
public void setRefresh_token(String refresh_token) {
|
||||
this.refresh_token = refresh_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expires_in
|
||||
*/
|
||||
public long getExpires_in() {
|
||||
return expires_in;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expires_in the expires_in to set
|
||||
*/
|
||||
public void setExpires_in(long expires_in) {
|
||||
this.expires_in = expires_in;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id_token
|
||||
*/
|
||||
public String getId_token() {
|
||||
return id_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id_token the id_token to set
|
||||
*/
|
||||
public void setId_token(String id_token) {
|
||||
this.id_token = id_token;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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.OidcClient;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public interface OidcClientRepository extends JpaRepository<OidcClient, Long>, QuerydslPredicateExecutor<OidcClient> {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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.OidcToken;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public interface OidcTokenRepository extends JpaRepository<OidcToken, Long>, QuerydslPredicateExecutor<OidcToken> {
|
||||
}
|
||||
Reference in New Issue
Block a user