1
2
3
4
5
6
7
8
9
10
11
12
13 package org.springframework.security.oauth2.provider.token.store;
14
15 import org.apache.commons.logging.Log;
16 import org.apache.commons.logging.LogFactory;
17 import org.springframework.beans.factory.InitializingBean;
18 import org.springframework.security.crypto.codec.Base64;
19 import org.springframework.security.jwt.Jwt;
20 import org.springframework.security.jwt.JwtHelper;
21 import org.springframework.security.jwt.crypto.sign.*;
22 import org.springframework.security.oauth2.common.*;
23 import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
24 import org.springframework.security.oauth2.common.util.JsonParser;
25 import org.springframework.security.oauth2.common.util.JsonParserFactory;
26 import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
27 import org.springframework.security.oauth2.provider.OAuth2Authentication;
28 import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
29 import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
30 import org.springframework.security.oauth2.provider.token.TokenEnhancer;
31 import org.springframework.util.Assert;
32
33 import java.security.KeyPair;
34 import java.security.PrivateKey;
35 import java.security.interfaces.RSAPrivateKey;
36 import java.security.interfaces.RSAPublicKey;
37 import java.util.Date;
38 import java.util.LinkedHashMap;
39 import java.util.Map;
40
41
42
43
44
45
46
47
48
49
50
51
52 public class JwtAccessTokenConverter implements TokenEnhancer, AccessTokenConverter, InitializingBean {
53
54
55
56
57 public static final String TOKEN_ID = AccessTokenConverter.JTI;
58
59
60
61
62 public static final String ACCESS_TOKEN_ID = AccessTokenConverter.ATI;
63
64 private static final Log logger = LogFactory.getLog(JwtAccessTokenConverter.class);
65
66 private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
67
68 private JwtClaimsSetVerifier jwtClaimsSetVerifier = new NoOpJwtClaimsSetVerifier();
69
70 private JsonParser objectMapper = JsonParserFactory.create();
71
72 private String verifierKey = new RandomValueStringGenerator().generate();
73
74 private Signer signer = new MacSigner(verifierKey);
75
76 private String signingKey = verifierKey;
77
78 private SignatureVerifier verifier;
79
80
81
82
83 public void setAccessTokenConverter(AccessTokenConverter tokenConverter) {
84 this.tokenConverter = tokenConverter;
85 }
86
87
88
89
90 public AccessTokenConverter getAccessTokenConverter() {
91 return tokenConverter;
92 }
93
94
95
96
97 public JwtClaimsSetVerifier getJwtClaimsSetVerifier() {
98 return this.jwtClaimsSetVerifier;
99 }
100
101
102
103
104 public void setJwtClaimsSetVerifier(JwtClaimsSetVerifier jwtClaimsSetVerifier) {
105 Assert.notNull(jwtClaimsSetVerifier, "jwtClaimsSetVerifier cannot be null");
106 this.jwtClaimsSetVerifier = jwtClaimsSetVerifier;
107 }
108
109 @Override
110 public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
111 return tokenConverter.convertAccessToken(token, authentication);
112 }
113
114 @Override
115 public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map) {
116 return tokenConverter.extractAccessToken(value, map);
117 }
118
119 @Override
120 public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
121 return tokenConverter.extractAuthentication(map);
122 }
123
124
125
126
127
128
129 public void setVerifier(SignatureVerifier verifier) {
130 this.verifier = verifier;
131 }
132
133
134
135
136
137
138 public void setSigner(Signer signer) {
139 this.signer = signer;
140 }
141
142
143
144
145
146
147 public Map<String, String> getKey() {
148 Map<String, String> result = new LinkedHashMap<String, String>();
149 result.put("alg", signer.algorithm());
150 result.put("value", verifierKey);
151 return result;
152 }
153
154 public void setKeyPair(KeyPair keyPair) {
155 PrivateKey privateKey = keyPair.getPrivate();
156 Assert.state(privateKey instanceof RSAPrivateKey, "KeyPair must be an RSA ");
157 signer = new RsaSigner((RSAPrivateKey) privateKey);
158 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
159 verifier = new RsaVerifier(publicKey);
160 verifierKey = "-----BEGIN PUBLIC KEY-----\n" + new String(Base64.encode(publicKey.getEncoded()))
161 + "\n-----END PUBLIC KEY-----";
162 }
163
164
165
166
167
168
169
170 public void setSigningKey(String key) {
171 Assert.hasText(key);
172 key = key.trim();
173
174 this.signingKey = key;
175
176 if (isPublic(key)) {
177 signer = new RsaSigner(key);
178 logger.info("Configured with RSA signing key");
179 }
180 else {
181
182 this.verifierKey = key;
183 signer = new MacSigner(key);
184 }
185 }
186
187
188
189
190 private boolean isPublic(String key) {
191 return key.startsWith("-----BEGIN");
192 }
193
194
195
196
197 public boolean isPublic() {
198 return signer instanceof RsaSigner;
199 }
200
201
202
203
204
205
206
207
208
209
210
211 public void setVerifierKey(String key) {
212 this.verifierKey = key;
213 }
214
215 public OAuth2AccessToken../../../../org/springframework/security/oauth2/common/OAuth2AccessToken.html#OAuth2AccessToken">OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
216 DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
217 Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
218 String tokenId = result.getValue();
219 if (!info.containsKey(TOKEN_ID)) {
220 info.put(TOKEN_ID, tokenId);
221 }
222 else {
223 tokenId = (String) info.get(TOKEN_ID);
224 }
225 result.setAdditionalInformation(info);
226 result.setValue(encode(result, authentication));
227 OAuth2RefreshToken refreshToken = result.getRefreshToken();
228 if (refreshToken != null) {
229 DefaultOAuth2AccessToken encodedRefreshToken = new DefaultOAuth2AccessToken(accessToken);
230 encodedRefreshToken.setValue(refreshToken.getValue());
231
232 encodedRefreshToken.setExpiration(null);
233 try {
234 Map<String, Object> claims = objectMapper
235 .parseMap(JwtHelper.decode(refreshToken.getValue()).getClaims());
236 if (claims.containsKey(TOKEN_ID)) {
237 encodedRefreshToken.setValue(claims.get(TOKEN_ID).toString());
238 }
239 }
240 catch (IllegalArgumentException e) {
241 }
242 Map<String, Object> refreshTokenInfo = new LinkedHashMap<String, Object>(
243 accessToken.getAdditionalInformation());
244 refreshTokenInfo.put(TOKEN_ID, encodedRefreshToken.getValue());
245 refreshTokenInfo.put(ACCESS_TOKEN_ID, tokenId);
246 encodedRefreshToken.setAdditionalInformation(refreshTokenInfo);
247 DefaultOAuth2RefreshToken token = new DefaultOAuth2RefreshToken(
248 encode(encodedRefreshToken, authentication));
249 if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
250 Date expiration = ((ExpiringOAuth2RefreshToken) refreshToken).getExpiration();
251 encodedRefreshToken.setExpiration(expiration);
252 token = new DefaultExpiringOAuth2RefreshToken(encode(encodedRefreshToken, authentication), expiration);
253 }
254 result.setRefreshToken(token);
255 }
256 return result;
257 }
258
259 public boolean isRefreshToken(OAuth2AccessToken token) {
260 return token.getAdditionalInformation().containsKey(ACCESS_TOKEN_ID);
261 }
262
263 protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
264 String content;
265 try {
266 content = objectMapper.formatMap(tokenConverter.convertAccessToken(accessToken, authentication));
267 }
268 catch (Exception e) {
269 throw new IllegalStateException("Cannot convert access token to JSON", e);
270 }
271 String token = JwtHelper.encode(content, signer).getEncoded();
272 return token;
273 }
274
275 protected Map<String, Object> decode(String token) {
276 try {
277 Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
278 String claimsStr = jwt.getClaims();
279 Map<String, Object> claims = objectMapper.parseMap(claimsStr);
280 if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) {
281 Integer intValue = (Integer) claims.get(EXP);
282 claims.put(EXP, new Long(intValue));
283 }
284 this.getJwtClaimsSetVerifier().verify(claims);
285 return claims;
286 }
287 catch (Exception e) {
288 throw new InvalidTokenException("Cannot convert access token to JSON", e);
289 }
290 }
291
292 public void afterPropertiesSet() throws Exception {
293 if (verifier != null) {
294
295 return;
296 }
297 SignatureVerifier verifier = new MacSigner(verifierKey);
298 try {
299 verifier = new RsaVerifier(verifierKey);
300 }
301 catch (Exception e) {
302 logger.warn("Unable to create an RSA verifier from verifierKey (ignoreable if using MAC)");
303 }
304
305 if (signer instanceof RsaSigner) {
306 byte[] test = "test".getBytes();
307 try {
308 verifier.verify(test, signer.sign(test));
309 logger.info("Signing and verification RSA keys match");
310 }
311 catch (InvalidSignatureException e) {
312 logger.error("Signing and verification RSA keys do not match");
313 }
314 }
315 else if (verifier instanceof MacSigner) {
316
317
318 Assert.state(this.signingKey == this.verifierKey,
319 "For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key");
320 }
321 this.verifier = verifier;
322 }
323
324 private class NoOpJwtClaimsSetVerifier implements JwtClaimsSetVerifier {
325 @Override
326 public void verify(Map<String, Object> claims) throws InvalidTokenException {
327 }
328 }
329 }