Skip to content

Commit 98aaf6b

Browse files
committed
Introduce JwtEncoder with JWS implementation
Closes spring-projectsgh-81
1 parent ca110f1 commit 98aaf6b

File tree

10 files changed

+847
-0
lines changed

10 files changed

+847
-0
lines changed

crypto/spring-security-crypto2.gradle

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apply plugin: 'io.spring.convention.spring-module'
2+
3+
dependencies {
4+
compile project(':spring-security-core2')
5+
compile 'org.springframework.security:spring-security-core'
6+
compile springCoreDependency
7+
8+
testCompile 'junit:junit'
9+
testCompile 'org.assertj:assertj-core'
10+
testCompile 'org.mockito:mockito-core'
11+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.crypto.keys;
17+
18+
import java.util.List;
19+
20+
/**
21+
*
22+
* @author Joe Grandja
23+
* @since 0.0.1
24+
*/
25+
public interface KeyManager {
26+
27+
ManagedKey findByKeyId(String keyId);
28+
29+
List<ManagedKey> findByAlgorithm(String algorithm);
30+
31+
List<ManagedKey> getKeys();
32+
33+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.crypto.keys;
17+
18+
import org.springframework.lang.Nullable;
19+
import org.springframework.security.core.SpringSecurityCoreVersion2;
20+
import org.springframework.util.Assert;
21+
22+
import javax.crypto.SecretKey;
23+
import java.io.Serializable;
24+
import java.security.Key;
25+
import java.security.PrivateKey;
26+
import java.security.PublicKey;
27+
import java.time.Instant;
28+
29+
/**
30+
*
31+
* @author Joe Grandja
32+
* @since 0.0.1
33+
*/
34+
public final class ManagedKey implements Serializable {
35+
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
36+
private Key key;
37+
private PublicKey publicKey;
38+
private String keyId;
39+
private Instant activatedOn;
40+
private Instant deactivatedOn;
41+
42+
private ManagedKey() {
43+
}
44+
45+
public boolean isSymmetric() {
46+
return SecretKey.class.isAssignableFrom(this.key.getClass());
47+
}
48+
49+
public boolean isAsymmetric() {
50+
return PrivateKey.class.isAssignableFrom(this.key.getClass());
51+
}
52+
53+
@SuppressWarnings("unchecked")
54+
public <T> T getKey() {
55+
return (T) this.key;
56+
}
57+
58+
@Nullable
59+
public PublicKey getPublicKey() {
60+
return this.publicKey;
61+
}
62+
63+
public String getKeyId() {
64+
return this.keyId;
65+
}
66+
67+
public Instant getActivatedOn() {
68+
return this.activatedOn;
69+
}
70+
71+
public Instant getDeactivatedOn() {
72+
return this.deactivatedOn;
73+
}
74+
75+
public boolean isActive() {
76+
return getDeactivatedOn() == null;
77+
}
78+
79+
public String getAlgorithm() {
80+
return this.key.getAlgorithm();
81+
}
82+
83+
public static Builder withSymmetricKey(SecretKey secretKey) {
84+
return new Builder(secretKey);
85+
}
86+
87+
public static Builder withAsymmetricKey(PublicKey publicKey, PrivateKey privateKey) {
88+
return new Builder(publicKey, privateKey);
89+
}
90+
91+
public static class Builder {
92+
private Key key;
93+
private PublicKey publicKey;
94+
private String keyId;
95+
private Instant activatedOn;
96+
private Instant deactivatedOn;
97+
98+
private Builder(SecretKey secretKey) {
99+
Assert.notNull(secretKey, "secretKey cannot be null");
100+
this.key = secretKey;
101+
}
102+
103+
private Builder(PublicKey publicKey, PrivateKey privateKey) {
104+
Assert.notNull(publicKey, "publicKey cannot be null");
105+
Assert.notNull(privateKey, "privateKey cannot be null");
106+
this.key = privateKey;
107+
this.publicKey = publicKey;
108+
}
109+
110+
public Builder keyId(String keyId) {
111+
this.keyId = keyId;
112+
return this;
113+
}
114+
115+
public Builder activatedOn(Instant activatedOn) {
116+
this.activatedOn = activatedOn;
117+
return this;
118+
}
119+
120+
public Builder deactivatedOn(Instant deactivatedOn) {
121+
this.deactivatedOn = deactivatedOn;
122+
return this;
123+
}
124+
125+
public ManagedKey build() {
126+
Assert.hasText(this.keyId, "keyId cannot be empty");
127+
Assert.notNull(this.activatedOn, "activatedOn cannot be null");
128+
129+
ManagedKey managedKey = new ManagedKey();
130+
managedKey.key = this.key;
131+
managedKey.publicKey = this.publicKey;
132+
managedKey.keyId = this.keyId;
133+
managedKey.activatedOn = this.activatedOn;
134+
managedKey.deactivatedOn = this.deactivatedOn;
135+
return managedKey;
136+
}
137+
}
138+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.crypto.keys;
17+
18+
import org.springframework.util.Assert;
19+
20+
import javax.crypto.KeyGenerator;
21+
import javax.crypto.SecretKey;
22+
import java.security.KeyPair;
23+
import java.security.KeyPairGenerator;
24+
import java.time.Instant;
25+
import java.util.ArrayList;
26+
import java.util.Collections;
27+
import java.util.HashMap;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.UUID;
31+
import java.util.stream.Collectors;
32+
import java.util.stream.Stream;
33+
34+
/**
35+
*
36+
* @author Joe Grandja
37+
* @since 0.0.1
38+
*/
39+
public final class StaticKeyGeneratingKeyManager implements KeyManager {
40+
private final Map<String, ManagedKey> keys;
41+
42+
public StaticKeyGeneratingKeyManager() {
43+
this.keys = Collections.unmodifiableMap(new HashMap<>(generateKeys()));
44+
}
45+
46+
@Override
47+
public ManagedKey findByKeyId(String keyId) {
48+
Assert.hasText(keyId, "keyId cannot be empty");
49+
return this.keys.get(keyId);
50+
}
51+
52+
@Override
53+
public List<ManagedKey> findByAlgorithm(String algorithm) {
54+
Assert.hasText(algorithm, "algorithm cannot be empty");
55+
return this.keys.values().stream()
56+
.filter(managedKey -> managedKey.getAlgorithm().equals(algorithm))
57+
.collect(Collectors.toList());
58+
}
59+
60+
@Override
61+
public List<ManagedKey> getKeys() {
62+
return new ArrayList<>(this.keys.values());
63+
}
64+
65+
private static Map<String, ManagedKey> generateKeys() {
66+
return Stream.of(generateRsaKey(), generateSecretKey())
67+
.collect(Collectors.toMap(ManagedKey::getKeyId, v -> v));
68+
}
69+
70+
private static ManagedKey generateRsaKey() {
71+
KeyPair keyPair;
72+
try {
73+
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
74+
keyPairGenerator.initialize(2048);
75+
keyPair = keyPairGenerator.generateKeyPair();
76+
} catch (Exception ex) {
77+
throw new IllegalStateException(ex);
78+
}
79+
80+
return ManagedKey.withAsymmetricKey(keyPair.getPublic(), keyPair.getPrivate())
81+
.keyId(UUID.randomUUID().toString())
82+
.activatedOn(Instant.now())
83+
.build();
84+
}
85+
86+
private static ManagedKey generateSecretKey() {
87+
SecretKey hmacKey;
88+
try {
89+
hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey();
90+
} catch (Exception ex) {
91+
throw new IllegalStateException(ex);
92+
}
93+
94+
return ManagedKey.withSymmetricKey(hmacKey)
95+
.keyId(UUID.randomUUID().toString())
96+
.activatedOn(Instant.now())
97+
.build();
98+
}
99+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apply plugin: 'io.spring.convention.spring-module'
2+
3+
dependencies {
4+
compile project(':spring-security-crypto2')
5+
compile 'org.springframework.security:spring-security-oauth2-core'
6+
compile 'org.springframework.security:spring-security-oauth2-jose'
7+
compile springCoreDependency
8+
compile 'com.nimbusds:nimbus-jose-jwt'
9+
10+
testCompile 'junit:junit'
11+
testCompile 'org.assertj:assertj-core'
12+
testCompile 'org.mockito:mockito-core'
13+
}

0 commit comments

Comments
 (0)