draft for borrow, update partey tags, added jwt

This commit is contained in:
2021-10-22 10:56:20 +02:00
parent 442bdb4996
commit a24f0650d1
41 changed files with 3415 additions and 162 deletions
+26
View File
@@ -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>jwt</name>
<artifactId>webstly-jwt</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,293 @@
/**
*
*/
package de.bstly.we.jwt.businesslogic;
import java.text.ParseException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.UUID;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import com.google.common.collect.Lists;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.crypto.Ed25519Signer;
import com.nimbusds.jose.crypto.Ed25519Verifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import de.bstly.we.jwt.model.JwtKey;
import de.bstly.we.jwt.model.QJwtKey;
import de.bstly.we.jwt.repository.JwtKeyRepository;
/**
* The Class JwtKeyManager.
*/
@Component
public class JwtKeyManager {
@Autowired
private JwtKeyRepository jwtKeyRepository;
private QJwtKey qJwtKey = QJwtKey.jwtKey;
/**
* Gets the jwk set.
*
* @param name the name
* @param createDefault the create default
* @return the jwk set
*/
@Transactional
public JWKSet getJwkSet(String name, boolean createDefault) {
List<JWK> jwkList = Lists.newArrayList();
for (JwtKey jwtKey : jwtKeyRepository.findAll(qJwtKey.name.eq(name))) {
JWK jwk = parseKey(jwtKey);
if (jwk != null) {
jwkList.add(jwk.toPublicJWK());
}
}
if (jwkList.isEmpty()) {
JwtKey jwtKey = getLatest(name, createDefault);
if (jwtKey == null) {
// ERROR
}
JWK jwk = parseKey(jwtKey);
if (jwk != null) {
jwkList.add(jwk.toPublicJWK());
}
}
return new JWKSet(jwkList);
}
/**
* Gets the latest.
*
* @param name the name
* @param createDefault the create default
* @return the latest
*/
@Transactional
public JwtKey getLatest(String name, boolean createDefault) {
List<JwtKey> jwtKeys = Lists.newArrayList(jwtKeyRepository.findAll(qJwtKey.name.eq(name),
PageRequest.of(0, 1, Sort.by(Order.desc("created")))));
JwtKey jwtKey = null;
if (!jwtKeys.isEmpty()) {
jwtKey = jwtKeys.get(0);
}
if (jwtKey != null) {
if (jwtKey.getLifetime() == -1L || jwtKey.getExpires()
.isAfter(Instant.now().plus(jwtKey.getLifetime(), ChronoUnit.SECONDS))) {
return jwtKey;
}
JwtKey newKey = new JwtKey();
newKey.setName(name);
newKey.setKeyParameter(jwtKey.getKeyParameter());
newKey.setKeyType(jwtKey.getKeyType());
newKey.setKeyUse(jwtKey.getKeyUse());
newKey.setLifetime(jwtKey.getLifetime());
newKey.setExpires(Instant.now().plus(jwtKey.getLifetime(), ChronoUnit.SECONDS));
return createKey(newKey);
} else if (createDefault) {
jwtKey = new JwtKey();
jwtKey.setName(name);
return createKey(jwtKey);
}
return null;
}
/**
* Gets the by key ID.
*
* @param keyID the key ID
* @return the by key ID
*/
@Transactional
public JwtKey getByKeyID(String keyID) {
return jwtKeyRepository.findOne(qJwtKey.keyID.eq(keyID)).orElse(null);
}
@Transactional
public JwtKey getByCreated(String name, Instant created) {
return jwtKeyRepository
.findOne(qJwtKey.name.eq(name)
.and(qJwtKey.created.before(created)
.and(qJwtKey.lifetime.eq(-1L).or(qJwtKey.expires.after(created)))))
.orElse(null);
}
/**
* Creates the key.
*
* @param jwtKey the jwt key
* @return the jwt key
*/
public JwtKey createKey(JwtKey jwtKey) {
JWK jwk = null;
String keyID = UUID.randomUUID().toString();
while (jwtKeyRepository.exists(qJwtKey.keyID.eq(keyID))) {
keyID = UUID.randomUUID().toString();
}
try {
KeyType keyType = KeyType.parse(jwtKey.getKeyType());
if (KeyType.EC.equals(keyType)) {
jwk = new ECKeyGenerator(Curve.parse(jwtKey.getKeyParameter()))
.keyUse(KeyUse.parse(jwtKey.getKeyUse())).keyID(keyID).generate();
} else if (KeyType.OCT.equals(keyType)) {
jwk = new OctetKeyPairGenerator(Curve.parse(jwtKey.getKeyParameter()))
.keyUse(KeyUse.parse(jwtKey.getKeyUse())).keyID(keyID).generate();
} else if (KeyType.RSA.equals(keyType)) {
jwk = new RSAKeyGenerator(Integer.valueOf(jwtKey.getKeyParameter()))
.keyUse(KeyUse.parse(jwtKey.getKeyUse())).keyID(keyID).generate();
}
} catch (JOSEException | ParseException e) {
e.printStackTrace();
}
if (jwk != null) {
jwtKey.setKeyID(keyID);
jwtKey.setData(jwk.toJSONString());
jwtKey.setCreated(Instant.now());
jwtKey.setExpires(Instant.now().plus(jwtKey.getLifetime(), ChronoUnit.SECONDS));
return jwtKeyRepository.save(jwtKey);
}
jwtKeyRepository.delete(jwtKey);
return null;
}
/**
* Parses the key.
*
* @param jwtKey the jwt key
* @return the jwk
*/
public JWK parseKey(JwtKey jwtKey) {
try {
KeyType keyType = KeyType.parse(jwtKey.getKeyType());
if (KeyType.EC.equals(keyType)) {
return ECKey.parse(jwtKey.getData());
} else if (KeyType.OCT.equals(keyType)) {
return OctetKeyPair.parse(jwtKey.getData());
} else if (KeyType.RSA.equals(keyType)) {
return RSAKey.parse(jwtKey.getData());
}
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
/**
* Gets the jws algorithm.
*
* @param jwtKey the jwt key
* @return the jws algorithm
*/
public JWSAlgorithm getJwsAlgorithm(JwtKey jwtKey) {
if (jwtKey != null && StringUtils.hasText(jwtKey.getJwsAlgorithm())) {
return JWSAlgorithm.parse(jwtKey.getJwsAlgorithm());
}
return null;
}
/**
* Gets the jws algorithm.
*
* @param name the name
* @param createDefault the create default
* @return the jws algorithm
*/
public JWSAlgorithm getJwsAlgorithm(String name, boolean createDefault) {
return getJwsAlgorithm(getLatest(name, createDefault));
}
/**
* Creates the signer.
*
* @param jwtKey the jwt key
* @return the JWS signer
*/
public JWSSigner createSigner(JwtKey jwtKey) {
JWK jwk = parseKey(jwtKey);
if (jwk != null) {
try {
KeyType keyType = KeyType.parse(jwtKey.getKeyType());
if (KeyType.EC.equals(keyType)) {
return new ECDSASigner((ECKey) jwk);
} else if (KeyType.OCT.equals(keyType)) {
return new Ed25519Signer((OctetKeyPair) jwk);
} else if (KeyType.RSA.equals(keyType)) {
return new RSASSASigner((RSAKey) jwk);
}
} catch (JOSEException e) {
e.printStackTrace();
}
}
return null;
}
/**
* Creates the verfifier.
*
* @param jwtKey the jwt key
* @return the JWS verifier
*/
public JWSVerifier createVerfifier(JwtKey jwtKey) {
JWK jwk = parseKey(jwtKey);
if (jwk != null) {
try {
KeyType keyType = KeyType.parse(jwtKey.getKeyType());
if (KeyType.EC.equals(keyType)) {
return new ECDSAVerifier((ECKey) jwk);
} else if (KeyType.OCT.equals(keyType)) {
return new Ed25519Verifier((OctetKeyPair) jwk);
} else if (KeyType.RSA.equals(keyType)) {
return new RSASSAVerifier((RSAKey) jwk);
}
} catch (JOSEException e) {
e.printStackTrace();
}
}
return null;
}
}
@@ -0,0 +1,88 @@
/**
*
*/
package de.bstly.we.jwt.businesslogic;
import java.text.ParseException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
import com.nimbusds.jwt.SignedJWT;
import de.bstly.we.businesslogic.UserManager;
import de.bstly.we.jwt.model.JwtKey;
import de.bstly.we.model.User;
/**
* @author Lurkars
*
*/
@Component
public class JwtUserManager {
@Autowired
private JwtKeyManager jwtKeyManager;
@Autowired
private UserManager userManager;
public static final String JWT_USER_DATA_KEY_NAME = "user";
public SignedJWT createSignedJwt(Long userId, Object data, String issuer, Long lifetime)
throws JOSEException {
User user = userManager.get(userId);
if (user == null) {
return null;
}
Builder claimsSetBuilder = new Builder();
claimsSetBuilder.subject(String.valueOf(user.getId()));
claimsSetBuilder.claim("name", user.getUsername());
claimsSetBuilder.issuer(issuer);
claimsSetBuilder.issueTime(new Date());
if (lifetime != null && lifetime > 0) {
claimsSetBuilder
.expirationTime(Date.from(Instant.now().plus(lifetime, ChronoUnit.SECONDS)));
}
if (data != null) {
claimsSetBuilder.claim("data", data);
}
JwtKey jwtKey = jwtKeyManager.getLatest(JWT_USER_DATA_KEY_NAME, true);
JWSHeader.Builder headerBuilder = new JWSHeader.Builder(
jwtKeyManager.getJwsAlgorithm(jwtKey));
headerBuilder.keyID(jwtKey.getKeyID());
headerBuilder.type(JOSEObjectType.JWT);
SignedJWT jwt = new SignedJWT(headerBuilder.build(), claimsSetBuilder.build());
jwt.sign(jwtKeyManager.createSigner(jwtKey));
return jwt;
}
public boolean verify(SignedJWT jwt) throws JOSEException, ParseException {
if (jwt.getHeader() == null || !StringUtils.hasText(jwt.getHeader().getKeyID())) {
throw new KeySourceException("No KeyID provided!");
}
JwtKey jwtKey = jwtKeyManager.getByKeyID(jwt.getHeader().getKeyID());
if (jwtKey == null) {
throw new KeySourceException("No key found for given keyID!");
}
return jwt.verify(jwtKeyManager.createVerfifier(jwtKey));
}
}
@@ -0,0 +1,105 @@
/**
*
*/
package de.bstly.we.jwt.controller;
import java.text.ParseException;
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.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jwt.SignedJWT;
import de.bstly.we.controller.BaseController;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.jwt.businesslogic.JwtUserManager;
/**
* @author Lurkars
*
*/
@RestController
@RequestMapping("/jwt/user")
public class JwtUserDataController extends BaseController {
@Autowired
private JwtUserManager jwtUserDataManager;
@Value("${bstly.we.jwtUserIssuer:}")
private String jwtUserIssuer;
@PreAuthorize("isAuthenticated()")
@GetMapping("/auth")
public String createSignedJwtAuth(HttpServletRequest request) {
String issuer = jwtUserIssuer;
if (!StringUtils.hasText(issuer)) {
issuer = request.getScheme()
+ "://"
+ request.getServerName();
if (request.getServerPort() != 443 && request.getServerPort() != 80) {
issuer += ":"
+ request.getServerPort();
}
}
try {
return jwtUserDataManager.createSignedJwt(getCurrentUserId(), null, issuer, 120L)
.serialize();
} catch (JOSEException e) {
e.printStackTrace();
throw new EntityResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PreAuthorize("isAuthenticated()")
@PostMapping("/create")
public String createSignedJwt(@RequestBody Object data, HttpServletRequest request) {
String issuer = jwtUserIssuer;
if (!StringUtils.hasText(issuer)) {
issuer = request.getScheme()
+ "://"
+ request.getServerName();
if (request.getServerPort() != 443 && request.getServerPort() != 80) {
issuer += ":"
+ request.getServerPort();
}
}
try {
return jwtUserDataManager.createSignedJwt(getCurrentUserId(), data, issuer, -1L)
.serialize();
} catch (JOSEException e) {
e.printStackTrace();
throw new EntityResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PostMapping("verify")
public Object verfiy(@RequestBody String serialized, HttpServletRequest request,
HttpServletResponse response) {
try {
SignedJWT signedJwt = SignedJWT.parse(serialized);
if (jwtUserDataManager.verify(signedJwt)) {
return signedJwt.getJWTClaimsSet();
} else {
throw new EntityResponseStatusException(HttpStatus.NOT_ACCEPTABLE);
}
} catch (ParseException e) {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
} catch (JOSEException e) {
e.printStackTrace();
throw new EntityResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
@@ -0,0 +1,252 @@
/**
*
*/
package de.bstly.we.jwt.model;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.KeyUse;
/**
* The Class JwtKey.
*/
@Entity
@Table(name = "jwt_keys")
public class JwtKey {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "key_id", nullable = true)
private String keyID;
@Lob
@Column(name = "data", nullable = true)
private String data;
@Column(name = "key_type", nullable = false)
private String keyType = KeyType.EC.getValue();
@Column(name = "key_parameter", nullable = false)
private String keyParameter = Curve.P_256.toString();
@Column(name = "key_use", nullable = false)
private String keyUse = KeyUse.SIGNATURE.getValue();
@Column(name = "jws_algorithm", nullable = false)
private String jwsAlgorithm = JWSAlgorithm.ES256.getName();
@Column(name = "lifetime", nullable = false)
private Long lifetime = -1L;
@Column(name = "created", nullable = false)
private Instant created;
@Column(name = "expires", nullable = false)
private Instant expires;
/**
* 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 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 key ID.
*
* @return the key ID
*/
public String getKeyID() {
return keyID;
}
/**
* Sets the key ID.
*
* @param keyID the new key ID
*/
public void setKeyID(String keyID) {
this.keyID = keyID;
}
/**
* Gets the data.
*
* @return the data
*/
public String getData() {
return data;
}
/**
* Sets the data.
*
* @param data the new data
*/
public void setData(String data) {
this.data = data;
}
/**
* Gets the key type.
*
* @return the key type
*/
public String getKeyType() {
return keyType;
}
/**
* Sets the key type.
*
* @param keyType the new key type
*/
public void setKeyType(String keyType) {
this.keyType = keyType;
}
/**
* Gets the key parameter.
*
* @return the key parameter
*/
public String getKeyParameter() {
return keyParameter;
}
/**
* Sets the key parameter.
*
* @param keyParameter the new key parameter
*/
public void setKeyParameter(String keyParameter) {
this.keyParameter = keyParameter;
}
/**
* Gets the key use.
*
* @return the key use
*/
public String getKeyUse() {
return keyUse;
}
/**
* Sets the key use.
*
* @param keyUse the new key use
*/
public void setKeyUse(String keyUse) {
this.keyUse = keyUse;
}
/**
* Gets the jws algorithm.
*
* @return the jws algorithm
*/
public String getJwsAlgorithm() {
return jwsAlgorithm;
}
/**
* Sets the jws algorithm.
*
* @param jwsAlgorithm the new jws algorithm
*/
public void setJwsAlgorithm(String jwsAlgorithm) {
this.jwsAlgorithm = jwsAlgorithm;
}
/**
* Gets the lifetime.
*
* @return the lifetime
*/
public Long getLifetime() {
return lifetime;
}
/**
* Sets the lifetime.
*
* @param lifetime the new lifetime
*/
public void setLifetime(Long lifetime) {
this.lifetime = lifetime;
}
/**
* 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 expires.
*
* @return the expires
*/
public Instant getExpires() {
return expires;
}
/**
* Sets the expires.
*
* @param expires the new expires
*/
public void setExpires(Instant expires) {
this.expires = expires;
}
}
@@ -0,0 +1,18 @@
/**
*
*/
package de.bstly.we.jwt.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;
import de.bstly.we.jwt.model.JwtKey;
/**
* The Interface JwtKeyRepository.
*/
@Repository
public interface JwtKeyRepository
extends JpaRepository<JwtKey, Long>, QuerydslPredicateExecutor<JwtKey> {
}