Making building the docker images optional in spring_boot_scan
[cxf.git] / services / sts / sts-core / src / main / java / org / apache / cxf / sts / token / validator / jwt / JWTTokenValidator.java
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.cxf.sts.token.validator.jwt;
20
21 import java.security.KeyStore;
22 import java.security.Principal;
23 import java.util.Properties;
24 import java.util.Set;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
27
28 import javax.xml.soap.Node;
29
30 import org.w3c.dom.Element;
31
32 import org.apache.cxf.common.logging.LogUtils;
33 import org.apache.cxf.common.security.SimplePrincipal;
34 import org.apache.cxf.rs.security.jose.common.JoseConstants;
35 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
36 import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
37 import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
38 import org.apache.cxf.rs.security.jose.jws.JwsUtils;
39 import org.apache.cxf.rs.security.jose.jwt.JwtToken;
40 import org.apache.cxf.rs.security.jose.jwt.JwtUtils;
41 import org.apache.cxf.sts.STSPropertiesMBean;
42 import org.apache.cxf.sts.request.ReceivedToken;
43 import org.apache.cxf.sts.request.ReceivedToken.STATE;
44 import org.apache.cxf.sts.token.realm.JWTRealmCodec;
45 import org.apache.cxf.sts.token.validator.TokenValidator;
46 import org.apache.cxf.sts.token.validator.TokenValidatorParameters;
47 import org.apache.cxf.sts.token.validator.TokenValidatorResponse;
48 import org.apache.cxf.ws.security.sts.provider.STSException;
49 import org.apache.wss4j.common.crypto.Crypto;
50 import org.apache.wss4j.common.crypto.Merlin;
51
52 /**
53 * Validate a SAML Assertion. It is valid if it was issued and signed by this STS.
54 */
55 public class JWTTokenValidator implements TokenValidator {
56
57 private static final Logger LOG = LogUtils.getL7dLogger(JWTTokenValidator.class);
58 private int clockOffset;
59 private int ttl;
60 private JWTRoleParser roleParser;
61 private JWTRealmCodec realmCodec;
62
63 /**
64 * Return true if this TokenValidator implementation is capable of validating the
65 * ReceivedToken argument.
66 */
67 public boolean canHandleToken(ReceivedToken validateTarget) {
68 return canHandleToken(validateTarget, null);
69 }
70
71 /**
72 * Return true if this TokenValidator implementation is capable of validating the
73 * ReceivedToken argument. The realm is ignored in this Validator.
74 */
75 public boolean canHandleToken(ReceivedToken validateTarget, String realm) {
76 Object token = validateTarget.getToken();
77 if (token instanceof Element) {
78 Element tokenEl = (Element)token;
79 if (tokenEl.getFirstChild().getNodeType() == Node.TEXT_NODE) {
80 try {
81 JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(tokenEl.getTextContent());
82 if (jwtConsumer.getJwtToken() != null) {
83 return true;
84 }
85 } catch (RuntimeException ex) {
86 return false;
87 }
88 }
89 }
90 return false;
91 }
92
93 /**
94 * Validate a Token using the given TokenValidatorParameters.
95 */
96 public TokenValidatorResponse validateToken(TokenValidatorParameters tokenParameters) {
97 LOG.fine("Validating JWT Token");
98 STSPropertiesMBean stsProperties = tokenParameters.getStsProperties();
99
100 TokenValidatorResponse response = new TokenValidatorResponse();
101 ReceivedToken validateTarget = tokenParameters.getToken();
102 validateTarget.setState(STATE.INVALID);
103 response.setToken(validateTarget);
104
105 String token = ((Element)validateTarget.getToken()).getTextContent();
106 if (token == null || "".equals(token)) {
107 return response;
108 }
109
110 if (token.split("\\.").length != 3) {
111 LOG.log(Level.WARNING, "JWT Token appears not to be signed. Validation has failed");
112 return response;
113 }
114
115 JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(token);
116 JwtToken jwt = jwtConsumer.getJwtToken();
117
118 // Verify the signature
119 Properties verificationProperties = new Properties();
120
121 Crypto signatureCrypto = stsProperties.getSignatureCrypto();
122 String alias = stsProperties.getSignatureUsername();
123
124 if (alias != null) {
125 verificationProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, alias);
126 }
127
128 if (!(signatureCrypto instanceof Merlin)) {
129 throw new STSException("Can't get the keystore", STSException.REQUEST_FAILED);
130 }
131 KeyStore keystore = ((Merlin)signatureCrypto).getKeyStore();
132 verificationProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore);
133
134 JwsSignatureVerifier signatureVerifier =
135 JwsUtils.loadSignatureVerifier(verificationProperties, jwt.getJwsHeaders());
136
137 if (!jwtConsumer.verifySignatureWith(signatureVerifier)) {
138 return response;
139 }
140
141 try {
142 validateToken(jwt);
143 } catch (RuntimeException ex) {
144 LOG.log(Level.WARNING, "JWT token validation failed", ex);
145 return response;
146 }
147
148
149 // Get the realm of the JWT Token
150 if (realmCodec != null) {
151 String tokenRealm = realmCodec.getRealmFromToken(jwt);
152 response.setTokenRealm(tokenRealm);
153 }
154
155 if (isVerifiedWithAPublicKey(jwt)) {
156 Principal principal = new SimplePrincipal(jwt.getClaims().getSubject());
157 response.setPrincipal(principal);
158
159 // Parse roles from the validated token
160 if (roleParser != null) {
161 Set<Principal> roles =
162 roleParser.parseRolesFromToken(principal, null, jwt);
163 response.setRoles(roles);
164 }
165 }
166
167 validateTarget.setState(STATE.VALID);
168 LOG.fine("JWT Token successfully validated");
169
170 return response;
171 }
172
173 private boolean isVerifiedWithAPublicKey(JwtToken jwt) {
174 String alg = (String)jwt.getJwsHeader(JoseConstants.HEADER_ALGORITHM);
175 SignatureAlgorithm sigAlg = SignatureAlgorithm.getAlgorithm(alg);
176 return SignatureAlgorithm.isPublicKeyAlgorithm(sigAlg);
177 }
178
179 protected void validateToken(JwtToken jwt) {
180 JwtUtils.validateTokenClaims(jwt.getClaims(), ttl, clockOffset, false);
181 }
182
183 public int getClockOffset() {
184 return clockOffset;
185 }
186
187 public void setClockOffset(int clockOffset) {
188 this.clockOffset = clockOffset;
189 }
190
191 public int getTtl() {
192 return ttl;
193 }
194
195 public void setTtl(int ttl) {
196 this.ttl = ttl;
197 }
198
199 public JWTRoleParser getRoleParser() {
200 return roleParser;
201 }
202
203 public void setRoleParser(JWTRoleParser roleParser) {
204 this.roleParser = roleParser;
205 }
206
207 public JWTRealmCodec getRealmCodec() {
208 return realmCodec;
209 }
210
211 public void setRealmCodec(JWTRealmCodec realmCodec) {
212 this.realmCodec = realmCodec;
213 }
214 }