├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── NOTICES ├── pom.xml ├── readme.md └── src ├── main └── java │ └── io │ └── curity │ └── oauth │ ├── AbstractJwtValidator.java │ ├── AuthenticatedUser.java │ ├── AuthenticatedUserRequestWrapper.java │ ├── DefaultHttpClientProvider.java │ ├── DefaultIntrospectClient.java │ ├── DefaultWebKeysClient.java │ ├── EdDSAPublicKeyCreator.java │ ├── Expirable.java │ ├── ExpirationBasedCache.java │ ├── FilterHelper.java │ ├── HttpClientProvider.java │ ├── IntrospectionClient.java │ ├── JsonData.java │ ├── JsonUtils.java │ ├── JsonWebKey.java │ ├── JsonWebKeyNotFoundException.java │ ├── JsonWebKeyType.java │ ├── JwkManager.java │ ├── JwksResponse.java │ ├── JwtValidator.java │ ├── JwtValidatorWithCert.java │ ├── JwtValidatorWithJwk.java │ ├── OAuthFilter.java │ ├── OAuthIntrospectResponse.java │ ├── OAuthJwtFilter.java │ ├── OAuthOpaqueFilter.java │ ├── OpaqueTokenValidator.java │ ├── RsaPublicKeyCreator.java │ ├── TimeBasedCache.java │ ├── TokenValidationException.java │ ├── TokenValidator.java │ ├── ValidationExceptions.java │ └── WebKeysClient.java └── test ├── java └── io │ └── curity │ └── oauth │ ├── AuthenticatedUserRequestWrapperTest.java │ ├── HttpClientProviderTest.java │ ├── JwtTokenIssuer.java │ ├── JwtWithCertTest.java │ ├── JwtWithJwksTest.java │ └── TimeBasedCacheTest.java └── resources ├── META-INF └── services │ └── io.curity.oauth.HttpClientProvider ├── Se.Curity.Test.p12 └── log4j2-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | lib 3 | *.sublime-workspace 4 | 5 | # Logs and databases # 6 | ###################### 7 | *.log 8 | *.sqlite 9 | 10 | # Compiled files # 11 | ###################### 12 | *.pyc 13 | *.class 14 | 15 | # OS generated files # 16 | ###################### 17 | .DS_Store 18 | .DS_Store? 19 | ._* 20 | .Spotlight-V100 21 | .Trashes 22 | ehthumbs.db 23 | Thumbs.db 24 | 25 | # Eclipse files # 26 | ################# 27 | .classpath 28 | .project 29 | .settings 30 | 31 | 32 | # MS Word files # 33 | ~$*.doc* 34 | ~WRL*.tmp 35 | 36 | # Ignore build dir and dist package 37 | .idea 38 | *.iml 39 | ======= 40 | .idea 41 | *.iml 42 | 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## OAuth Filter for Java Changelog 2 | 3 | ## Unreleased 4 | 5 | ## 3.0.0 (2021-05-27) 6 | 7 | Features: 8 | - Changed groupID to `io.curity` and package name to `io.curity.oauth`. 9 | - Updated dependencies. 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICES: -------------------------------------------------------------------------------- 1 | # Attributions, Copyrights, and Licenses 2 | 3 | 4 | 5 | ## Definitions 6 | 7 | * "**Curity**" means Curity AB, a Swedish corporation 8 | * "**Software**" means the software program that this document is included with as provided by Curity 9 | 10 | ## Content 11 | 12 | This document contains attributions, copyright notices, and links to the software licenses of third-party open source software libraries used by the Software. 13 | 14 | ## License 15 | 16 | The Software is licensed under the terms of the Apache v. 2 license agreement. 17 | 18 | ## Included Components 19 | 20 | The Software makes use of the following third-party open source software libraries. 21 | 22 | ### Apache HttpComponents 23 | 24 | * License: Apache v. 2 25 | * Modifications: No 26 | * Link: https://hc.apache.org/ 27 | * Notices: 28 | 29 |
 30 | Apache HttpComponents Client
 31 | Copyright 1999-2015 The Apache Software Foundation
 32 | 
 33 | This product includes software developed at
 34 | The Apache Software Foundation (http://www.apache.org/).
 35 | 
36 | 37 | ### Java Servlet API 38 | 39 | * License: CDDL v. 1 40 | * Modifications: No 41 | * Link: http://servlet-spec.java.net 42 | * Notices: 43 | 44 |
Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
45 | 46 | ### JSR 353 (JSON Processing) API 47 | 48 | * License: CDDL v. 1 49 | * Modifications: No 50 | * Link: 51 | * Notices: 52 | 53 |
Copyright (c) 2011-2013 Oracle and/or its affiliates. All rights reserved.
54 | 55 | ## Optionally Included Components 56 | 57 | If the Software is obtained in source code form, the following third-party open source software libraries are also used by the Software. 58 | 59 | ### Apache Log4j 2 60 | 61 | * License: Apache v. 2 62 | * Modifications: No 63 | * Link: http://logging.apache.org/log4j/ 64 | * Notices: 65 | 66 |
 67 | Apache Log4j
 68 | Copyright 1999-2017 Apache Software Foundation
 69 | 
 70 | This product includes software developed at
 71 | The Apache Software Foundation (http://www.apache.org/).
 72 | 
 73 | ResolverUtil.java
 74 | Copyright 2005-2006 Tim Fennell
 75 | 
 76 | Dumbster SMTP test server
 77 | Copyright 2004 Jason Paul Kitchen
 78 | 
 79 | TypeUtil.java
 80 | Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams
 81 | 
82 | 83 | ### Genson 84 | 85 | * License: Apache v. 2 86 | * Modifications: No 87 | * Link: https://owlike.github.io/genson/ 88 | * Notices: 89 | 90 |
Copyright 2011-2014 Genson - Cepoi Eugen
91 | 92 | ### jose4j 93 | 94 | * License: Apache v. 2 95 | * Modifications: No 96 | * Link: https://bitbucket.org/b_c/jose4j 97 | * Notices: 98 | 99 |
100 | jose4j
101 | Copyright 2012-2015 Brian Campbell
102 | 
103 | EcdsaUsingShaAlgorithm contains code for converting the concatenated
104 | R & S values of the signature to and from DER, which was originally
105 | derived from the Apache Santuario XML Security library's SignatureECDSA
106 | implementation. http://santuario.apache.org/
107 | 
108 | The Base64 implementation in this software was derived from the
109 | Apache Commons Codec project. http://commons.apache.org/proper/commons-codec/
110 | 
111 | JSON processing in this software was derived from the JSON.simple toolkit.
112 | https://code.google.com/p/json-simple/
113 | 
114 | 115 | ### Junit 116 | 117 | * License: EPL v. 1 118 | * Modifications: No 119 | * Link: http://junit.org/ 120 | 121 | ### Mockito 122 | * License: MIT 123 | * Modifications: No 124 | * Link: http://mockito.org/ 125 | * Notices: 126 | 127 |
Copyright (c) 2007 Mockito contributors
128 | 129 | ## Licenses 130 | 131 | ### Apache v. 2 132 | 133 |
134 |                                  Apache License
135 |                            Version 2.0, January 2004
136 |                         http://www.apache.org/licenses/
137 | 
138 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
139 | 
140 |    1. Definitions.
141 | 
142 |       "License" shall mean the terms and conditions for use, reproduction,
143 |       and distribution as defined by Sections 1 through 9 of this document.
144 | 
145 |       "Licensor" shall mean the copyright owner or entity authorized by
146 |       the copyright owner that is granting the License.
147 | 
148 |       "Legal Entity" shall mean the union of the acting entity and all
149 |       other entities that control, are controlled by, or are under common
150 |       control with that entity. For the purposes of this definition,
151 |       "control" means (i) the power, direct or indirect, to cause the
152 |       direction or management of such entity, whether by contract or
153 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
154 |       outstanding shares, or (iii) beneficial ownership of such entity.
155 | 
156 |       "You" (or "Your") shall mean an individual or Legal Entity
157 |       exercising permissions granted by this License.
158 | 
159 |       "Source" form shall mean the preferred form for making modifications,
160 |       including but not limited to software source code, documentation
161 |       source, and configuration files.
162 | 
163 |       "Object" form shall mean any form resulting from mechanical
164 |       transformation or translation of a Source form, including but
165 |       not limited to compiled object code, generated documentation,
166 |       and conversions to other media types.
167 | 
168 |       "Work" shall mean the work of authorship, whether in Source or
169 |       Object form, made available under the License, as indicated by a
170 |       copyright notice that is included in or attached to the work
171 |       (an example is provided in the Appendix below).
172 | 
173 |       "Derivative Works" shall mean any work, whether in Source or Object
174 |       form, that is based on (or derived from) the Work and for which the
175 |       editorial revisions, annotations, elaborations, or other modifications
176 |       represent, as a whole, an original work of authorship. For the purposes
177 |       of this License, Derivative Works shall not include works that remain
178 |       separable from, or merely link (or bind by name) to the interfaces of,
179 |       the Work and Derivative Works thereof.
180 | 
181 |       "Contribution" shall mean any work of authorship, including
182 |       the original version of the Work and any modifications or additions
183 |       to that Work or Derivative Works thereof, that is intentionally
184 |       submitted to Licensor for inclusion in the Work by the copyright owner
185 |       or by an individual or Legal Entity authorized to submit on behalf of
186 |       the copyright owner. For the purposes of this definition, "submitted"
187 |       means any form of electronic, verbal, or written communication sent
188 |       to the Licensor or its representatives, including but not limited to
189 |       communication on electronic mailing lists, source code control systems,
190 |       and issue tracking systems that are managed by, or on behalf of, the
191 |       Licensor for the purpose of discussing and improving the Work, but
192 |       excluding communication that is conspicuously marked or otherwise
193 |       designated in writing by the copyright owner as "Not a Contribution."
194 | 
195 |       "Contributor" shall mean Licensor and any individual or Legal Entity
196 |       on behalf of whom a Contribution has been received by Licensor and
197 |       subsequently incorporated within the Work.
198 | 
199 |    2. Grant of Copyright License. Subject to the terms and conditions of
200 |       this License, each Contributor hereby grants to You a perpetual,
201 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
202 |       copyright license to reproduce, prepare Derivative Works of,
203 |       publicly display, publicly perform, sublicense, and distribute the
204 |       Work and such Derivative Works in Source or Object form.
205 | 
206 |    3. Grant of Patent License. Subject to the terms and conditions of
207 |       this License, each Contributor hereby grants to You a perpetual,
208 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
209 |       (except as stated in this section) patent license to make, have made,
210 |       use, offer to sell, sell, import, and otherwise transfer the Work,
211 |       where such license applies only to those patent claims licensable
212 |       by such Contributor that are necessarily infringed by their
213 |       Contribution(s) alone or by combination of their Contribution(s)
214 |       with the Work to which such Contribution(s) was submitted. If You
215 |       institute patent litigation against any entity (including a
216 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
217 |       or a Contribution incorporated within the Work constitutes direct
218 |       or contributory patent infringement, then any patent licenses
219 |       granted to You under this License for that Work shall terminate
220 |       as of the date such litigation is filed.
221 | 
222 |    4. Redistribution. You may reproduce and distribute copies of the
223 |       Work or Derivative Works thereof in any medium, with or without
224 |       modifications, and in Source or Object form, provided that You
225 |       meet the following conditions:
226 | 
227 |       (a) You must give any other recipients of the Work or
228 |           Derivative Works a copy of this License; and
229 | 
230 |       (b) You must cause any modified files to carry prominent notices
231 |           stating that You changed the files; and
232 | 
233 |       (c) You must retain, in the Source form of any Derivative Works
234 |           that You distribute, all copyright, patent, trademark, and
235 |           attribution notices from the Source form of the Work,
236 |           excluding those notices that do not pertain to any part of
237 |           the Derivative Works; and
238 | 
239 |       (d) If the Work includes a "NOTICE" text file as part of its
240 |           distribution, then any Derivative Works that You distribute must
241 |           include a readable copy of the attribution notices contained
242 |           within such NOTICE file, excluding those notices that do not
243 |           pertain to any part of the Derivative Works, in at least one
244 |           of the following places: within a NOTICE text file distributed
245 |           as part of the Derivative Works; within the Source form or
246 |           documentation, if provided along with the Derivative Works; or,
247 |           within a display generated by the Derivative Works, if and
248 |           wherever such third-party notices normally appear. The contents
249 |           of the NOTICE file are for informational purposes only and
250 |           do not modify the License. You may add Your own attribution
251 |           notices within Derivative Works that You distribute, alongside
252 |           or as an addendum to the NOTICE text from the Work, provided
253 |           that such additional attribution notices cannot be construed
254 |           as modifying the License.
255 | 
256 |       You may add Your own copyright statement to Your modifications and
257 |       may provide additional or different license terms and conditions
258 |       for use, reproduction, or distribution of Your modifications, or
259 |       for any such Derivative Works as a whole, provided Your use,
260 |       reproduction, and distribution of the Work otherwise complies with
261 |       the conditions stated in this License.
262 | 
263 |    5. Submission of Contributions. Unless You explicitly state otherwise,
264 |       any Contribution intentionally submitted for inclusion in the Work
265 |       by You to the Licensor shall be under the terms and conditions of
266 |       this License, without any additional terms or conditions.
267 |       Notwithstanding the above, nothing herein shall supersede or modify
268 |       the terms of any separate license agreement you may have executed
269 |       with Licensor regarding such Contributions.
270 | 
271 |    6. Trademarks. This License does not grant permission to use the trade
272 |       names, trademarks, service marks, or product names of the Licensor,
273 |       except as required for reasonable and customary use in describing the
274 |       origin of the Work and reproducing the content of the NOTICE file.
275 | 
276 |    7. Disclaimer of Warranty. Unless required by applicable law or
277 |       agreed to in writing, Licensor provides the Work (and each
278 |       Contributor provides its Contributions) on an "AS IS" BASIS,
279 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
280 |       implied, including, without limitation, any warranties or conditions
281 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
282 |       PARTICULAR PURPOSE. You are solely responsible for determining the
283 |       appropriateness of using or redistributing the Work and assume any
284 |       risks associated with Your exercise of permissions under this License.
285 | 
286 |    8. Limitation of Liability. In no event and under no legal theory,
287 |       whether in tort (including negligence), contract, or otherwise,
288 |       unless required by applicable law (such as deliberate and grossly
289 |       negligent acts) or agreed to in writing, shall any Contributor be
290 |       liable to You for damages, including any direct, indirect, special,
291 |       incidental, or consequential damages of any character arising as a
292 |       result of this License or out of the use or inability to use the
293 |       Work (including but not limited to damages for loss of goodwill,
294 |       work stoppage, computer failure or malfunction, or any and all
295 |       other commercial damages or losses), even if such Contributor
296 |       has been advised of the possibility of such damages.
297 | 
298 |    9. Accepting Warranty or Additional Liability. While redistributing
299 |       the Work or Derivative Works thereof, You may choose to offer,
300 |       and charge a fee for, acceptance of support, warranty, indemnity,
301 |       or other liability obligations and/or rights consistent with this
302 |       License. However, in accepting such obligations, You may act only
303 |       on Your own behalf and on Your sole responsibility, not on behalf
304 |       of any other Contributor, and only if You agree to indemnify,
305 |       defend, and hold each Contributor harmless for any liability
306 |       incurred by, or claims asserted against, such Contributor by reason
307 |       of your accepting any such warranty or additional liability.
308 | 
309 |    END OF TERMS AND CONDITIONS
310 | 
311 |    APPENDIX: How to apply the Apache License to your work.
312 | 
313 |       To apply the Apache License to your work, attach the following
314 |       boilerplate notice, with the fields enclosed by brackets "[]"
315 |       replaced with your own identifying information. (Don't include
316 |       the brackets!)  The text should be enclosed in the appropriate
317 |       comment syntax for the file format. We also recommend that a
318 |       file or class name and description of purpose be included on the
319 |       same "printed page" as the copyright notice for easier
320 |       identification within third-party archives.
321 | 
322 |    Copyright [yyyy] [name of copyright owner]
323 | 
324 |    Licensed under the Apache License, Version 2.0 (the "License");
325 |    you may not use this file except in compliance with the License.
326 |    You may obtain a copy of the License at
327 | 
328 |        http://www.apache.org/licenses/LICENSE-2.0
329 | 
330 |    Unless required by applicable law or agreed to in writing, software
331 |    distributed under the License is distributed on an "AS IS" BASIS,
332 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
333 |    See the License for the specific language governing permissions and
334 |    limitations under the License.
335 | 
336 | 337 | ### CDDL v. 1 338 | 339 |
340 | COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
341 | 
342 | 1. Definitions.
343 | 
344 |    1.1. Contributor. means each individual or entity that creates or contributes to the creation of Modifications.
345 | 
346 |    1.2. Contributor Version. means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor.
347 | 
348 |    1.3. Covered Software. means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof.
349 | 
350 |    1.4. Executable. means the Covered Software in any form other than Source Code.
351 | 
352 |    1.5. Initial Developer. means the individual or entity that first makes Original Software available under this License.
353 | 
354 |    1.6. Larger Work. means a work which combines Covered Software or portions thereof with code not governed by the terms of this License.
355 | 
356 |    1.7. License. means this document.
357 | 
358 |    1.8. Licensable. means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein.
359 | 
360 |    1.9. Modifications. means the Source Code and Executable form of any of the following:
361 | 
362 |         A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications;
363 | 
364 |         B. Any new file that contains any part of the Original Software or previous Modification; or
365 | 
366 |         C. Any new file that is contributed or otherwise made available under the terms of this License.
367 | 
368 |    1.10. Original Software. means the Source Code and Executable form of computer software code that is originally released under this License.
369 | 
370 |    1.11. Patent Claims. means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor.
371 | 
372 |    1.12. Source Code. means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code.
373 | 
374 |    1.13. You. (or .Your.) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, .You. includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, .control. means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
375 | 
376 | 2. License Grants.
377 | 
378 |       2.1. The Initial Developer Grant.
379 | 
380 |       Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license:
381 | 
382 |          (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and
383 | 
384 |          (b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof).
385 | 
386 |         (c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License.
387 | 
388 |         (d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software, or (ii) the combination of the Original Software with other software or devices.
389 | 
390 |     2.2. Contributor Grant.
391 | 
392 |     Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
393 | 
394 |         (a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and
395 | 
396 |         (b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination).
397 | 
398 |         (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party.
399 | 
400 |         (d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor.
401 | 
402 | 3. Distribution Obligations.
403 | 
404 |       3.1. Availability of Source Code.
405 |       Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange.
406 | 
407 |       3.2. Modifications.
408 |       The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License.
409 | 
410 |       3.3. Required Notices.
411 |       You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer.
412 | 
413 |       3.4. Application of Additional Terms.
414 |       You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients. rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer.
415 | 
416 |       3.5. Distribution of Executable Versions.
417 |       You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipient.s rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer.
418 | 
419 |       3.6. Larger Works.
420 |       You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software.
421 | 
422 | 4. Versions of the License.
423 | 
424 |       4.1. New Versions.
425 |       Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License.
426 | 
427 |       4.2. Effect of New Versions.
428 |       You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward.
429 | 
430 |       4.3. Modified Versions.
431 |       When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a) rename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that the license contains terms which differ from this License.
432 | 
433 | 5. DISCLAIMER OF WARRANTY.
434 | 
435 |    COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN .AS IS. BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
436 | 
437 | 6. TERMINATION.
438 | 
439 |       6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive.
440 | 
441 |       6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as .Participant.) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant.
442 | 
443 |       6.3. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination.
444 | 
445 | 7. LIMITATION OF LIABILITY.
446 | 
447 |    UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY.S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
448 | 
449 | 8. U.S. GOVERNMENT END USERS.
450 | 
451 |    The Covered Software is a .commercial item,. as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of .commercial computer software. (as that term is defined at 48 C.F.R. ? 252.227-7014(a)(1)) and .commercial computer software documentation. as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License.
452 | 
453 | 9. MISCELLANEOUS.
454 | 
455 |    This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdiction.s conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys. fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software.
456 | 
457 | 10. RESPONSIBILITY FOR CLAIMS.
458 | 
459 |    As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability.
460 | 
461 |    NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)
462 | 
463 |    The code released under the CDDL shall be governed by the laws of the State of California (excluding conflict-of-law provisions). Any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California and the state courts of the State of California, with venue lying in Santa Clara County, California.
464 | 
465 | 466 | ### EPL v. 1 467 | 468 |
469 | Eclipse Public License - v 1.0
470 | 
471 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
472 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
473 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
474 | 
475 | 1. DEFINITIONS
476 | 
477 | "Contribution" means:
478 | 
479 |       a) in the case of the initial Contributor, the initial code and
480 |          documentation distributed under this Agreement, and
481 |       b) in the case of each subsequent Contributor:
482 | 
483 |       i) changes to the Program, and
484 | 
485 |       ii) additions to the Program;
486 | 
487 |       where such changes and/or additions to the Program originate from and are
488 | distributed by that particular Contributor. A Contribution 'originates' from a
489 | Contributor if it was added to the Program by such Contributor itself or anyone
490 | acting on such Contributor's behalf. Contributions do not include additions to
491 | the Program which: (i) are separate modules of software distributed in
492 | conjunction with the Program under their own license agreement, and (ii) are
493 | not derivative works of the Program. 
494 | 
495 | "Contributor" means any person or entity that distributes the Program.
496 | 
497 | "Licensed Patents " mean patent claims licensable by a Contributor which are
498 | necessarily infringed by the use or sale of its Contribution alone or when
499 | combined with the Program.
500 | 
501 | "Program" means the Contributions distributed in accordance with this Agreement.
502 | 
503 | "Recipient" means anyone who receives the Program under this Agreement,
504 | including all Contributors.
505 | 
506 | 2. GRANT OF RIGHTS
507 | 
508 |       a) Subject to the terms of this Agreement, each Contributor hereby grants
509 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
510 | reproduce, prepare derivative works of, publicly display, publicly perform,
511 | distribute and sublicense the Contribution of such Contributor, if any, and
512 | such derivative works, in source code and object code form.
513 | 
514 |       b) Subject to the terms of this Agreement, each Contributor hereby grants
515 | Recipient a non-exclusive, worldwide, royalty-free patent license under
516 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
517 | transfer the Contribution of such Contributor, if any, in source code and
518 | object code form. This patent license shall apply to the combination of the
519 | Contribution and the Program if, at the time the Contribution is added by the
520 | Contributor, such addition of the Contribution causes such combination to be
521 | covered by the Licensed Patents. The patent license shall not apply to any
522 | other combinations which include the Contribution. No hardware per se is
523 | licensed hereunder. 
524 | 
525 |       c) Recipient understands that although each Contributor grants the
526 | licenses to its Contributions set forth herein, no assurances are provided by
527 | any Contributor that the Program does not infringe the patent or other
528 | intellectual property rights of any other entity. Each Contributor disclaims
529 | any liability to Recipient for claims brought by any other entity based on
530 | infringement of intellectual property rights or otherwise. As a condition to
531 | exercising the rights and licenses granted hereunder, each Recipient hereby
532 | assumes sole responsibility to secure any other intellectual property rights
533 | needed, if any. For example, if a third party patent license is required to
534 | allow Recipient to distribute the Program, it is Recipient's responsibility to
535 | acquire that license before distributing the Program.
536 | 
537 |       d) Each Contributor represents that to its knowledge it has sufficient
538 | copyright rights in its Contribution, if any, to grant the copyright license
539 | set forth in this Agreement. 
540 | 
541 | 3. REQUIREMENTS
542 | 
543 | A Contributor may choose to distribute the Program in object code form under
544 | its own license agreement, provided that:
545 | 
546 |       a) it complies with the terms and conditions of this Agreement; and
547 | 
548 |       b) its license agreement:
549 | 
550 |       i) effectively disclaims on behalf of all Contributors all warranties and
551 | conditions, express and implied, including warranties or conditions of title
552 | and non-infringement, and implied warranties or conditions of merchantability
553 | and fitness for a particular purpose; 
554 | 
555 |       ii) effectively excludes on behalf of all Contributors all liability for
556 | damages, including direct, indirect, special, incidental and consequential
557 | damages, such as lost profits; 
558 | 
559 |       iii) states that any provisions which differ from this Agreement are
560 | offered by that Contributor alone and not by any other party; and
561 | 
562 |       iv) states that source code for the Program is available from such
563 | Contributor, and informs licensees how to obtain it in a reasonable manner on
564 | or through a medium customarily used for software exchange. 
565 | 
566 | When the Program is made available in source code form:
567 | 
568 |       a) it must be made available under this Agreement; and 
569 | 
570 |       b) a copy of this Agreement must be included with each copy of the
571 | Program. 
572 | 
573 | Contributors may not remove or alter any copyright notices contained within the
574 | Program.
575 | 
576 | Each Contributor must identify itself as the originator of its Contribution, if
577 | any, in a manner that reasonably allows subsequent Recipients to identify the
578 | originator of the Contribution.
579 | 
580 | 4. COMMERCIAL DISTRIBUTION
581 | 
582 | Commercial distributors of software may accept certain responsibilities with
583 | respect to end users, business partners and the like. While this license is
584 | intended to facilitate the commercial use of the Program, the Contributor who
585 | includes the Program in a commercial product offering should do so in a manner
586 | which does not create potential liability for other Contributors. Therefore, if
587 | a Contributor includes the Program in a commercial product offering, such
588 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
589 | every other Contributor ("Indemnified Contributor") against any losses, damages
590 | and costs (collectively "Losses") arising from claims, lawsuits and other legal
591 | actions brought by a third party against the Indemnified Contributor to the
592 | extent caused by the acts or omissions of such Commercial Contributor in
593 | connection with its distribution of the Program in a commercial product
594 | offering. The obligations in this section do not apply to any claims or Losses
595 | relating to any actual or alleged intellectual property infringement. In order
596 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
597 | Contributor in writing of such claim, and b) allow the Commercial Contributor
598 | to control, and cooperate with the Commercial Contributor in, the defense and
599 | any related settlement negotiations. The Indemnified Contributor may
600 | participate in any such claim at its own expense.
601 | 
602 | For example, a Contributor might include the Program in a commercial product
603 | offering, Product X. That Contributor is then a Commercial Contributor. If that
604 | Commercial Contributor then makes performance claims, or offers warranties
605 | related to Product X, those performance claims and warranties are such
606 | Commercial Contributor's responsibility alone. Under this section, the
607 | Commercial Contributor would have to defend claims against the other
608 | Contributors related to those performance claims and warranties, and if a court
609 | requires any other Contributor to pay any damages as a result, the Commercial
610 | Contributor must pay those damages.
611 | 
612 | 5. NO WARRANTY
613 | 
614 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
615 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
616 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
617 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
618 | Recipient is solely responsible for determining the appropriateness of using
619 | and distributing the Program and assumes all risks associated with its exercise
620 | of rights under this Agreement, including but not limited to the risks and
621 | costs of program errors, compliance with applicable laws, damage to or loss of
622 | data, programs or equipment, and unavailability or interruption of operations.
623 | 
624 | 6. DISCLAIMER OF LIABILITY
625 | 
626 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
627 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
628 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
629 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
630 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
631 | WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
632 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
633 | 
634 | 7. GENERAL
635 | 
636 | If any provision of this Agreement is invalid or unenforceable under applicable
637 | law, it shall not affect the validity or enforceability of the remainder of the
638 | terms of this Agreement, and without further action by the parties hereto, such
639 | provision shall be reformed to the minimum extent necessary to make such
640 | provision valid and enforceable.
641 | 
642 | If Recipient institutes patent litigation against any
643 | entity (including a cross-claim or counterclaim in a lawsuit) alleging that the
644 | Program itself (excluding combinations of the Program with other software or
645 | hardware) infringes such Recipient's patent(s), then such Recipient's rights
646 | granted under Section 2(b) shall terminate as of the date such litigation is
647 | filed.
648 | 
649 | All Recipient's rights under this Agreement shall terminate if it fails to
650 | comply with any of the material terms or conditions of this Agreement and does
651 | not cure such failure in a reasonable period of time after becoming aware of
652 | such noncompliance. If all Recipient's rights under this Agreement terminate,
653 | Recipient agrees to cease use and distribution of the Program as soon as
654 | reasonably practicable. However, Recipient's obligations under this Agreement
655 | and any licenses granted by Recipient relating to the Program shall continue
656 | and survive.
657 | 
658 | Everyone is permitted to copy and distribute copies of this Agreement, but in
659 | order to avoid inconsistency the Agreement is copyrighted and may only be
660 | modified in the following manner. The Agreement Steward reserves the right to
661 | publish new versions (including revisions) of this Agreement from time to time.
662 | No one other than the Agreement Steward has the right to modify this Agreement.
663 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to
664 | serve as the Agreement Steward to a suitable separate entity. Each new version
665 | of the Agreement will be given a distinguishing version number. The Program
666 | (including Contributions) may always be distributed subject to the version of
667 | the Agreement under which it was received. In addition, after a new version of
668 | the Agreement is published, Contributor may elect to distribute the Program
669 | (including its Contributions) under the new version. Except as expressly stated
670 | in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
671 | the intellectual property of any Contributor under this Agreement, whether
672 | expressly, by implication, estoppel or otherwise. All rights in the Program not
673 | expressly granted under this Agreement are reserved.
674 | 
675 | This Agreement is governed by the laws of the State of New York and the
676 | intellectual property laws of the United States of America. No party to this
677 | Agreement will bring a legal action under this Agreement more than one year
678 | after the cause of action arose. Each party waives its rights to a jury trial
679 | in any resulting litigation. 
680 | 
681 | 682 | ### MIT 683 | 684 |
685 | Permission is hereby granted, free of charge, to any person obtaining
686 | a copy of this software and associated documentation files (the
687 | "Software"), to deal in the Software without restriction, including
688 | without limitation the rights to use, copy, modify, merge, publish,
689 | distribute, sublicense, and/or sell copies of the Software, and to
690 | permit persons to whom the Software is furnished to do so, subject to
691 | the following conditions:
692 | 
693 | The above copyright notice and this permission notice shall be
694 | included in all copies or substantial portions of the Software.
695 | 
696 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
697 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
698 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
699 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
700 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
701 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
702 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
703 | 
-------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 19 | 4.0.0 20 | 21 | io.curity 22 | oauth-filter 23 | 4.0.0 24 | OAuth API Filter 25 | A Servlet Filter that authenticates and authorizes requests using OAuth access tokens of various kinds. 26 | https://github.com/curityio/oauth-filter-for-java 27 | 28 | 29 | Curity AB 30 | https://curity.io 31 | 32 | 33 | 34 | 35 | The Apache License, Version 2.0 36 | https://www.apache.org/licenses/LICENSE-2.0.txt 37 | 38 | 39 | 40 | 41 | 42 | Travis Spencer 43 | travis.spencer@curity.io 44 | Curity AB 45 | https://curity.io 46 | 47 | 48 | Michal Trojanowski 49 | michal.trojanowski@curity.io 50 | Curity AB 51 | https://curity.io 52 | 53 | 54 | Judith Kahrer 55 | judith.kahrer@curity.io 56 | Curity AB 57 | https://curity.io 58 | 59 | 60 | 61 | 62 | scm:git:git://github.com:curityio/oauth-filter-for-java.git 63 | scm:git:ssh://github.com:curityio/oauth-filter-for-java.git 64 | https://github.com/curityio/oauth-filter-for-java 65 | 66 | 67 | 68 | UTF-8 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-compiler-plugin 76 | 3.1 77 | 78 | 17 79 | 17 80 | true 81 | false 82 | 83 | 84 | 85 | 86 | 87 | maven-failsafe-plugin 88 | 2.22.2 89 | 90 | 91 | org.apache.logging.log4j.jul.LogManager 92 | 93 | 94 | 95 | **/integration/**/IntegrationTest*.java 96 | **/integration/**/*IntegrationTest.java 97 | 98 | UTF-8 99 | 100 | 101 | 102 | 103 | integration-test 104 | verify 105 | 106 | 107 | 108 | 109 | 110 | 111 | org.apache.maven.plugins 112 | maven-surefire-plugin 113 | 2.16 114 | 115 | 116 | org.apache.logging.log4j.jul.LogManager 117 | 118 | 119 | 120 | **/integration/**/*.java 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | jakarta.servlet 131 | jakarta.servlet-api 132 | 6.0.0 133 | 134 | 135 | javax.json 136 | javax.json-api 137 | 138 | 139 | org.apache.httpcomponents 140 | httpclient 141 | true 142 | 143 | 144 | 145 | 146 | 147 | com.owlike 148 | genson 149 | test 150 | 151 | 152 | junit 153 | junit 154 | test 155 | 156 | 157 | org.mockito 158 | mockito-core 159 | test 160 | 161 | 162 | org.apache.logging.log4j 163 | log4j-core 164 | test 165 | 166 | 167 | org.apache.logging.log4j 168 | log4j-jul 169 | test 170 | 171 | 172 | org.apache.logging.log4j 173 | log4j-jcl 174 | test 175 | 176 | 177 | org.bitbucket.b_c 178 | jose4j 179 | test 180 | 181 | 182 | 183 | 184 | 185 | 186 | org.apache.httpcomponents 187 | httpclient 188 | 4.5.13 189 | 190 | 191 | javax.servlet 192 | javax.servlet-api 193 | 4.0.1 194 | 195 | 196 | com.google.guava 197 | guava 198 | 32.0.0-jre 199 | 200 | 201 | javax.json 202 | javax.json-api 203 | 1.1.4 204 | 205 | 206 | com.owlike 207 | genson 208 | 1.6 209 | 210 | 211 | org.bitbucket.b_c 212 | jose4j 213 | 0.9.4 214 | 215 | 216 | junit 217 | junit 218 | 4.13.2 219 | 220 | 221 | org.mockito 222 | mockito-core 223 | 3.10.0 224 | test 225 | 226 | 227 | org.apache.logging.log4j 228 | log4j-bom 229 | 2.14.1 230 | import 231 | pom 232 | 233 | 234 | 235 | 236 | 237 | 238 | release 239 | 240 | 241 | ossrh 242 | https://oss.sonatype.org/content/repositories/snapshots 243 | 244 | 245 | ossrh 246 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 247 | 248 | 249 | 250 | 251 | 252 | org.apache.maven.plugins 253 | maven-source-plugin 254 | 3.2.1 255 | 256 | 257 | attach-sources 258 | 259 | jar-no-fork 260 | 261 | 262 | 263 | 264 | 265 | org.apache.maven.plugins 266 | maven-javadoc-plugin 267 | 3.3.0 268 | 269 | 270 | attach-javadocs 271 | 272 | jar 273 | 274 | 275 | 276 | 277 | 8 278 | 279 | 280 | 281 | org.apache.maven.plugins 282 | maven-gpg-plugin 283 | 3.0.1 284 | 285 | 286 | sign-artifacts 287 | verify 288 | 289 | sign 290 | 291 | 292 | 293 | 294 | 295 | org.sonatype.plugins 296 | nexus-staging-maven-plugin 297 | 1.6.8 298 | true 299 | 300 | ossrh 301 | https://oss.sonatype.org/ 302 | true 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # OAuth Filter for Java 2 | 3 | [![Quality](https://img.shields.io/badge/quality-test-yellow)](https://curity.io/resources/code-examples/status/) 4 | [![Availability](https://img.shields.io/badge/availability-source-blue)](https://curity.io/resources/code-examples/status/) 5 | 6 | This project contains a Servlet Filter that authenticates and authorizes requests using OAuth access tokens of various kinds. There are two `OAuthFilter` implementations. `OAuthJwtFilter` and `OAuthOpaqueFilter`. Both implement `jakarta.servlet.Filter`, and can be used to protect APIs built using Java. Depending on the format of the access token, these two concrete implementations can be used in the following manner: 7 | 8 | 1. If the token is a Json Web Token (JWT) then validate the token using a public key 9 | 2. If the token is a reference (opaque) token, then validate by calling the OAuth server's 10 | [introspection](https://tools.ietf.org/search/rfc7662) endpoint. 11 | 12 | An example of how to use this filter can be found in a [separate repository](https://github.com/curityio/example-java-oauth-protected-api). 13 | 14 | ## Filter Overview 15 | 16 | The filter is build to perform two tasks. 17 | 18 | 1. Authenticate the caller by validating the incoming access token 19 | 2. Authorize the operation by validating the scopes in the access token against the configured scopes 20 | 21 | The authorization is very basic, and in the default implementation only checks that all configured scopes are present in the token. A more advanced scenario could check the HTTP method, along with sub-paths in order to determine if the appropriate scope is present in the request. To change the default behavior, override the method `io.curity.oauth.OAuthFilter#authorize`. 22 | 23 | ## Using Json Web Tokens (JWT) 24 | 25 | `OAuthJwtFilter` implements a filter that expects a Json Web Token, and that can validate the token either by using a pre-shared certificate or by calling the OAuth servers Json Web Key (JWK) endpoint. The default is to use the JWK service, as this provides a more maintainable deployment structure for microservices. 26 | 27 | ## Using Opaque Tokens 28 | 29 | `OAuthOpaqueFilter` implements a filter that expects an opaque token (i.e., a token that needs to be introspected in order to determine the contents). This requires the OAuth server to support [introspection](https://tools.ietf.org/search/rfc7662). Introspection means that the API acts as an introspecting client, and, therefore, needs client credentials in order to authenticate itself against the introspection endpoint. Each new token received is introspected, then cached for a limited time. In production, this should be refined to perhaps use a shared cache or at least a datastore for the cache if there is a large number of requests coming in to the API. 30 | 31 | ## Scope-bases Authorization 32 | 33 | The abstract class `OAuthFilter` implements a simple authorize method, that validates the incoming scopes against the configured ones. It is simple to override this method in the implementing classes instead to perform more advanced authorization. 34 | 35 | ## Installing the library 36 | 37 | The `oauth-filter` library is available on Maven Central since version 3.0.0. so you can easily include in your projects. 38 | 39 | For example, if you use Maven, add to your `pom.xml`: 40 | 41 | ```xml 42 | 43 | io.curity 44 | oauth-filter 45 | 4.0.0 46 | 47 | ``` 48 | 49 | or with Gradle, inlcude in `build.gradle`: 50 | 51 | ```groovy 52 | implementation 'io.curity:oauth-filter:4.0.0' 53 | ``` 54 | 55 | ## Configuring the Filter 56 | 57 | To configure the filter, the following settings are required for each of the two concrete implementations, depending on the format of the token your OAuth server is using. 58 | 59 | ### Init-params for the `OAuthJwtFilter` 60 | 61 | Configuration Setting Name | Description 62 | ---------------------------|---------------- 63 | oauthHost | Hostname of the OAuth server. 64 | oauthPort | Port of the OAuth server. 65 | jsonWebKeysPath | Path to the JWKS endpoint on the OAuth server. 66 | scope | A space separated list of scopes required to access the API. 67 | minKidReloadTimeInSeconds | Minimum time to reload the webKeys cache used by the Filter. 68 | 69 | ### Init-params for the `OAuthOpaqueFilter` 70 | 71 | Configuration Setting Name | Description 72 | ---------------------------|---------------- 73 | oauthHost | Hostname of the OAuth server. 74 | oauthPort | Port of the OAuth server. 75 | introspectionPath | Path to the introspection endpoint on the OAuth server. 76 | scope | A space separated list of scopes required to access the API. 77 | clientId | Your application's client id to use for introspection. 78 | clientSecret | Your application's client secret. 79 | 80 | ## Providing an external HttpClient 81 | 82 | The `OAuthFilter` uses an [HttpClient](https://hc.apache.org/httpcomponents-client-ga/) to communicate with the authentication server. The HttpClient may be overridden by the web application by defining a service Provider class in this location: 83 | 84 | * `META-INF/services/io.curity.oauth.HttpClientProvider` relative to the classpath 85 | 86 | The file should contain the name of the class used as the provider, e.g. `com.example.HttpClientProvider` 87 | 88 | This class must extend the abstract class `io.curity.oauth.HttpClientProvider` and implement two methods which create an introspection client (implementation of `io.curity.oauth.IntrospectionClient`), and a web keys client (implementation of `io.curity.oauth.WebKeysClient`). 89 | 90 | ## More Information 91 | 92 | For more information, please contact [Curity](http://curity.io). 93 | 94 | Copyright (C) 2016-2022 Curity AB. All rights reserved 95 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/AbstractJwtValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonObject; 20 | import javax.json.JsonReader; 21 | import javax.json.JsonReaderFactory; 22 | import java.io.StringReader; 23 | import java.nio.ByteBuffer; 24 | import java.nio.charset.StandardCharsets; 25 | import java.security.PublicKey; 26 | import java.security.Signature; 27 | import java.security.interfaces.EdECPublicKey; 28 | import java.time.Instant; 29 | import java.util.Base64; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | import java.util.Optional; 33 | import java.util.logging.Level; 34 | import java.util.logging.Logger; 35 | 36 | abstract class AbstractJwtValidator implements JwtValidator 37 | { 38 | private static final Logger _logger = Logger.getLogger(AbstractJwtValidator.class.getName()); 39 | 40 | // Caches with object scope that will ensure that we only decode the same JWT parts once per the lifetime of this 41 | // object 42 | private final Map _decodedJwtBodyByEncodedBody = new HashMap<>(1); 43 | private final Map _decodedJwtHeaderByEncodedHeader = new HashMap<>(1); 44 | private final JsonReaderFactory _jsonReaderFactory; 45 | private final String _audience; 46 | private final String _issuer; 47 | 48 | AbstractJwtValidator(String issuer, String audience, JsonReaderFactory jsonReaderFactory) 49 | { 50 | _issuer = issuer; 51 | _audience = audience; 52 | _jsonReaderFactory = jsonReaderFactory; 53 | } 54 | 55 | public final JsonData validate(String jwt) throws TokenValidationException 56 | { 57 | String[] jwtParts = jwt.split("\\."); 58 | 59 | if (jwtParts.length != 3) 60 | { 61 | throw new InvalidTokenFormatException(); 62 | } 63 | 64 | JsonObject jwtBody = decodeJwtBody(jwtParts[1]); 65 | JwtHeader jwtHeader = decodeJwtHeader(jwtParts[0]); 66 | byte[] jwtSignature = Base64.getUrlDecoder().decode(jwtParts[2]); 67 | byte[] headerAndPayload = convertToBytes(jwtParts[0] + "." + jwtParts[1]); 68 | 69 | validateSignature(jwtHeader, jwtSignature, headerAndPayload); 70 | 71 | try 72 | { 73 | long exp = JsonUtils.getLong(jwtBody, "exp"); 74 | long iat = JsonUtils.getLong(jwtBody, "iat"); 75 | 76 | String aud = JsonUtils.getString(jwtBody, "aud"); 77 | String iss = JsonUtils.getString(jwtBody, "iss"); 78 | 79 | assert aud != null && aud.length() > 0 : "aud claim is not present in JWT"; 80 | assert iss != null && iss.length() > 0 : "iss claim is not present in JWT"; 81 | 82 | if (!aud.equals(_audience)) 83 | { 84 | throw new InvalidAudienceException(_audience, aud); 85 | } 86 | 87 | if (!iss.equals(_issuer)) 88 | { 89 | throw new InvalidIssuerException(_issuer, iss); 90 | } 91 | 92 | Instant now = Instant.now(); 93 | 94 | if (now.getEpochSecond() > exp) 95 | { 96 | throw new ExpiredTokenException(); 97 | } 98 | 99 | if (now.getEpochSecond() < iat) 100 | { 101 | throw new InvalidIssuanceInstantException(); 102 | } 103 | } 104 | catch (Exception e) 105 | { 106 | _logger.log(Level.INFO, "Could not extract token data", e); 107 | 108 | throw new InvalidTokenFormatException("Failed to extract data from Token"); 109 | } 110 | 111 | return new JsonData(jwtBody); 112 | } 113 | 114 | private void validateSignature(JwtHeader jwtHeader, byte[] jwtSignatureData, 115 | byte[] headerAndPayload) 116 | throws TokenValidationException 117 | { 118 | String algorithm = jwtHeader.getAlgorithm(); 119 | 120 | if (algorithm == null || algorithm.length() <= 0) 121 | { 122 | throw new MissingAlgorithmException(); 123 | } 124 | 125 | if (canRecognizeAlg(algorithm)) 126 | { 127 | Optional signatureVerificationKey = getPublicKey(jwtHeader); 128 | 129 | if (signatureVerificationKey.isEmpty()) 130 | { 131 | _logger.warning("Received token but could not find matching key"); 132 | 133 | throw new UnknownSignatureVerificationKey(); 134 | } 135 | 136 | if (!verifySignature(algorithm, headerAndPayload, jwtSignatureData, signatureVerificationKey.get())) 137 | { 138 | throw new InvalidSignatureException(); 139 | } 140 | } 141 | else 142 | { 143 | _logger.warning(() -> String.format("Requested JsonWebKey using unrecognizable alg: %s", algorithm)); 144 | 145 | throw new UnknownAlgorithmException(algorithm); 146 | } 147 | } 148 | 149 | protected abstract Optional getPublicKey(JwtHeader jwtHeader); 150 | 151 | /** 152 | * Convert base64 to bytes (ASCII) 153 | * 154 | * @param input input 155 | * @return The array of bytes 156 | */ 157 | private byte[] convertToBytes(String input) 158 | { 159 | byte[] bytes = new byte[input.length()]; 160 | 161 | for (int i = 0; i < input.length(); i++) 162 | { 163 | //Convert and treat as ascii. 164 | int integer = input.charAt(i); 165 | 166 | //Since byte is signed in Java we cannot use normal conversion 167 | //but must drop it into a byte array and truncate. 168 | byte[] rawBytes = ByteBuffer.allocate(4).putInt(integer).array(); 169 | //Only store the least significant byte (the others should be 0 TODO check) 170 | bytes[i] = rawBytes[3]; 171 | } 172 | 173 | return bytes; 174 | } 175 | 176 | private boolean verifySignature(String algorithm, byte[] signingInput, byte[] signature, PublicKey publicKey) 177 | { 178 | try 179 | { 180 | Signature verifier = switch (algorithm) { 181 | case "RS256" -> Signature.getInstance("SHA256withRSA"); 182 | case "EdDSA" -> Signature.getInstance(((EdECPublicKey) publicKey).getParams().getName()); 183 | default -> throw new UnknownAlgorithmException(String.format("Unsupported signature algorithm '%s'", algorithm)); 184 | }; 185 | 186 | verifier.initVerify(publicKey); 187 | verifier.update(signingInput); 188 | 189 | return verifier.verify(signature); 190 | } 191 | catch (Exception e) 192 | { 193 | throw new RuntimeException("Unable to validate JWT signature", e); 194 | } 195 | } 196 | 197 | private boolean canRecognizeAlg(String alg) 198 | { 199 | return switch (alg) { 200 | case "RS256", "EdDSA" -> true; 201 | default -> false; 202 | }; 203 | } 204 | 205 | private JsonObject decodeJwtBody(String body) 206 | { 207 | return _decodedJwtBodyByEncodedBody.computeIfAbsent(body, key -> 208 | { 209 | // TODO: Switch to stream 210 | String decodedBody = new String(Base64.getUrlDecoder().decode(body), StandardCharsets.UTF_8); 211 | JsonReader jsonBodyReader = _jsonReaderFactory.createReader(new StringReader(decodedBody)); 212 | 213 | return jsonBodyReader.readObject(); 214 | }); 215 | } 216 | 217 | private JwtHeader decodeJwtHeader(String header) 218 | { 219 | return _decodedJwtHeaderByEncodedHeader.computeIfAbsent(header, key -> 220 | { 221 | Base64.Decoder base64 = Base64.getDecoder(); 222 | String decodedHeader = new String(base64.decode(header), StandardCharsets.UTF_8); 223 | JsonReader jsonHeaderReader = _jsonReaderFactory.createReader(new StringReader(decodedHeader)); 224 | 225 | return new JwtHeader(jsonHeaderReader.readObject()); 226 | }); 227 | } 228 | 229 | class JwtHeader 230 | { 231 | private final JsonObject _jsonObject; 232 | 233 | JwtHeader(JsonObject jsonObject) 234 | { 235 | _jsonObject = jsonObject; 236 | } 237 | 238 | String getAlgorithm() 239 | { 240 | return getString("alg"); 241 | } 242 | 243 | String getKeyId() 244 | { 245 | return getString("kid"); 246 | } 247 | 248 | String getString(String name) 249 | { 250 | return JsonUtils.getString(_jsonObject, name); 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/AuthenticatedUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonObject; 20 | import javax.json.JsonValue; 21 | import java.util.Objects; 22 | import java.util.Set; 23 | 24 | public class AuthenticatedUser 25 | { 26 | private final String _sub; 27 | private final Set _scopes; 28 | private final JsonData _jsonData; 29 | 30 | private AuthenticatedUser(String subject, Set scopes, JsonData jsonData) 31 | { 32 | _sub = subject; 33 | _scopes = scopes; 34 | _jsonData = jsonData; 35 | } 36 | 37 | public String getSubject() 38 | { 39 | return _sub; 40 | } 41 | 42 | public Set getScopes() 43 | { 44 | return _scopes; 45 | } 46 | 47 | public JsonValue getClaim(String name) 48 | { 49 | return _jsonData.getClaim(name); 50 | } 51 | 52 | public JsonObject getClaims() 53 | { 54 | return _jsonData.getClaims(); 55 | } 56 | 57 | static AuthenticatedUser from(JsonData tokenData) 58 | { 59 | Objects.requireNonNull(tokenData); 60 | 61 | String subject = tokenData.getSubject(); 62 | 63 | Objects.requireNonNull(subject); 64 | 65 | return new AuthenticatedUser(subject, tokenData.getScopes(), tokenData); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/AuthenticatedUserRequestWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import jakarta.servlet.ServletException; 20 | import jakarta.servlet.http.HttpServletRequest; 21 | import jakarta.servlet.http.HttpServletRequestWrapper; 22 | import jakarta.servlet.http.HttpServletResponse; 23 | import java.io.IOException; 24 | import java.security.Principal; 25 | 26 | class AuthenticatedUserRequestWrapper extends HttpServletRequestWrapper 27 | { 28 | /** 29 | * String identifier for OAuth authentication (i.e., authentication that conforms to RFC 6750). Value "OAUTH". 30 | * 31 | * @see RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer 32 | * Token Usage 33 | */ 34 | @SuppressWarnings("WeakerAccess") 35 | public static final String OAUTH_AUTH = "OAUTH"; 36 | 37 | private final HttpServletRequest _request; 38 | 39 | private AuthenticatedUser _authenticatedUser; // Not final because logout mutates this 40 | 41 | AuthenticatedUserRequestWrapper(HttpServletRequest request, AuthenticatedUser authenticatedUser) 42 | { 43 | super(request); 44 | 45 | _request = request; 46 | _authenticatedUser = authenticatedUser; 47 | } 48 | 49 | @Override 50 | public String getRemoteUser() 51 | { 52 | return _authenticatedUser == null ? _request.getRemoteUser() : _authenticatedUser.getSubject(); 53 | } 54 | 55 | @Override 56 | public Principal getUserPrincipal() 57 | { 58 | return _authenticatedUser == null ? _request.getUserPrincipal() : _authenticatedUser::getSubject; 59 | } 60 | 61 | @Override 62 | public String getAuthType() 63 | { 64 | //noinspection VariableNotUsedInsideIf 65 | return _authenticatedUser == null ? _request.getAuthType() : OAUTH_AUTH; 66 | } 67 | 68 | @Override 69 | public boolean authenticate(HttpServletResponse response) throws IOException, ServletException 70 | { 71 | return _authenticatedUser != null || (response.isCommitted() && 72 | (response.getStatus() == HttpServletResponse.SC_UNAUTHORIZED || 73 | response.getStatus() == HttpServletResponse.SC_FORBIDDEN)); 74 | } 75 | 76 | @Override 77 | public void login(String username, String password) throws ServletException 78 | { 79 | throw new ServletException("Authentication with username/password is not supported"); 80 | } 81 | 82 | @Override 83 | public void logout() throws ServletException 84 | { 85 | _authenticatedUser = null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/DefaultHttpClientProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import org.apache.http.client.HttpClient; 20 | import org.apache.http.impl.client.HttpClients; 21 | 22 | import jakarta.servlet.UnavailableException; 23 | import java.net.URI; 24 | import java.net.URISyntaxException; 25 | import java.util.Map; 26 | import java.util.concurrent.TimeUnit; 27 | import java.util.logging.Level; 28 | import java.util.logging.Logger; 29 | 30 | import static io.curity.oauth.FilterHelper.getInitParamValue; 31 | 32 | class DefaultHttpClientProvider extends HttpClientProvider 33 | { 34 | private static final Logger _logger = Logger.getLogger(OAuthOpaqueFilter.class.getName()); 35 | 36 | private interface InitParams 37 | { 38 | String OAUTH_HOST = "oauthHost"; 39 | String OAUTH_PORT = "oauthPort"; 40 | String JSON_WEB_KEYS_PATH = "jsonWebKeysPath"; 41 | String INTROSPECTION_PATH = "introspectionPath"; 42 | String CLIENT_ID = "clientId"; 43 | String CLIENT_SECRET = "clientSecret"; 44 | } 45 | 46 | @Override 47 | public IntrospectionClient createIntrospectionClient(Map config) throws UnavailableException 48 | { 49 | String oauthHost = getInitParamValue(InitParams.OAUTH_HOST, config); 50 | int oauthPort = getInitParamValue(InitParams.OAUTH_PORT, config, Integer::parseInt); 51 | 52 | String introspectionPath = getInitParamValue(InitParams.INTROSPECTION_PATH, config); 53 | String clientId = getInitParamValue(InitParams.CLIENT_ID, config); 54 | String clientSecret = getInitParamValue(InitParams.CLIENT_SECRET, config); 55 | 56 | URI introspectionUri = null; 57 | try 58 | { 59 | introspectionUri = new URI("https", null, oauthHost, oauthPort, introspectionPath, null, null); 60 | } 61 | catch (URISyntaxException e) 62 | { 63 | _logger.log(Level.SEVERE, "Invalid parameters", e); 64 | 65 | throw new UnavailableException("Service is unavailable"); 66 | } 67 | 68 | HttpClient httpClient = HttpClients 69 | .custom() 70 | .disableAuthCaching() 71 | .disableAutomaticRetries() 72 | .disableRedirectHandling() 73 | .setConnectionTimeToLive(2, TimeUnit.SECONDS) 74 | .build(); 75 | 76 | return new DefaultIntrospectClient(introspectionUri, clientId, clientSecret, httpClient); 77 | } 78 | 79 | @Override 80 | public WebKeysClient createWebKeysClient(Map config) throws UnavailableException 81 | { 82 | URI webKeysUri; 83 | 84 | try 85 | { 86 | int oauthPort = FilterHelper.getInitParamValue(InitParams.OAUTH_PORT, config, Integer::parseInt); 87 | String webKeysPath = FilterHelper.getInitParamValue(InitParams.JSON_WEB_KEYS_PATH, config); 88 | String oauthHost = FilterHelper.getInitParamValue(InitParams.OAUTH_HOST, config); 89 | 90 | webKeysUri = new URI("https", null, oauthHost, oauthPort, webKeysPath, null, null); 91 | } 92 | catch (URISyntaxException e) 93 | { 94 | _logger.log(Level.SEVERE, "Invalid parameters", e); 95 | 96 | throw new UnavailableException("Service is unavailable"); 97 | } 98 | 99 | HttpClient httpClient = HttpClients 100 | .custom() 101 | .disableAutomaticRetries() 102 | .disableRedirectHandling() 103 | .setConnectionTimeToLive(2, TimeUnit.SECONDS) 104 | .build(); 105 | 106 | return new DefaultWebKeysClient(webKeysUri, httpClient); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/DefaultIntrospectClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import org.apache.http.HttpResponse; 20 | import org.apache.http.HttpStatus; 21 | import org.apache.http.NameValuePair; 22 | import org.apache.http.client.HttpClient; 23 | import org.apache.http.client.entity.UrlEncodedFormEntity; 24 | import org.apache.http.client.methods.HttpPost; 25 | import org.apache.http.entity.ContentType; 26 | import org.apache.http.message.BasicNameValuePair; 27 | import org.apache.http.util.EntityUtils; 28 | 29 | import java.io.Closeable; 30 | import java.io.IOException; 31 | import java.net.URI; 32 | import java.nio.charset.StandardCharsets; 33 | import java.util.ArrayList; 34 | import java.util.List; 35 | import java.util.logging.Logger; 36 | 37 | import static org.apache.http.HttpHeaders.ACCEPT; 38 | 39 | class DefaultIntrospectClient implements IntrospectionClient 40 | { 41 | private static final Logger _logger = Logger.getLogger(DefaultIntrospectClient.class.getName()); 42 | 43 | private final HttpClient _httpClient; 44 | private final URI _introspectionUri; 45 | private final String _clientId; 46 | private final String _clientSecret; 47 | 48 | DefaultIntrospectClient(URI introspectionUri, String clientId, String clientSecret, HttpClient httpClient) 49 | { 50 | _introspectionUri = introspectionUri; 51 | _clientId = clientId; 52 | _clientSecret = clientSecret; 53 | _httpClient = httpClient; 54 | } 55 | 56 | @Override 57 | public String introspect(String token) throws IOException 58 | { 59 | HttpPost post = new HttpPost(_introspectionUri); 60 | 61 | post.setHeader(ACCEPT, ContentType.APPLICATION_JSON.getMimeType()); 62 | 63 | List params = new ArrayList<>(3); 64 | 65 | params.add(new BasicNameValuePair("token", token)); 66 | params.add(new BasicNameValuePair("client_id", _clientId)); 67 | params.add(new BasicNameValuePair("client_secret", _clientSecret)); 68 | 69 | post.setEntity(new UrlEncodedFormEntity(params)); 70 | 71 | HttpResponse response = _httpClient.execute(post); 72 | 73 | if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) 74 | { 75 | _logger.severe(() -> "Got error from introspection server: " + response.getStatusLine().getStatusCode()); 76 | 77 | throw new IOException("Got error from introspection server: " + response.getStatusLine().getStatusCode()); 78 | } 79 | 80 | return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); 81 | } 82 | 83 | @Override 84 | public void close() throws IOException 85 | { 86 | if (_httpClient instanceof Closeable) 87 | { 88 | ((Closeable) _httpClient).close(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/DefaultWebKeysClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import org.apache.http.HttpResponse; 20 | import org.apache.http.HttpStatus; 21 | import org.apache.http.client.HttpClient; 22 | import org.apache.http.client.methods.HttpGet; 23 | import org.apache.http.entity.ContentType; 24 | import org.apache.http.util.EntityUtils; 25 | 26 | import java.io.IOException; 27 | import java.net.URI; 28 | import java.nio.charset.StandardCharsets; 29 | import java.util.logging.Logger; 30 | 31 | import static org.apache.http.HttpHeaders.ACCEPT; 32 | 33 | class DefaultWebKeysClient implements WebKeysClient 34 | { 35 | private static final Logger _logger = Logger.getLogger(DefaultWebKeysClient.class.getName()); 36 | private final URI _jwksUri; 37 | private final HttpClient _httpClient; 38 | 39 | DefaultWebKeysClient(URI jwksUri, HttpClient httpClient) 40 | { 41 | _jwksUri = jwksUri; 42 | _httpClient = httpClient; 43 | } 44 | 45 | @Override 46 | public String getKeys() throws IOException 47 | { 48 | HttpGet get = new HttpGet(_jwksUri); 49 | 50 | get.setHeader(ACCEPT, ContentType.APPLICATION_JSON.getMimeType()); 51 | 52 | HttpResponse response = _httpClient.execute(get); 53 | 54 | if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) 55 | { 56 | _logger.severe(() -> "Got error from Jwks server: " + response.getStatusLine().getStatusCode()); 57 | 58 | throw new IOException("Got error from Jwks server: " + response.getStatusLine().getStatusCode()); 59 | } 60 | 61 | return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/EdDSAPublicKeyCreator.java: -------------------------------------------------------------------------------- 1 | package io.curity.oauth; 2 | 3 | import java.math.BigInteger; 4 | import java.security.KeyFactory; 5 | import java.security.NoSuchAlgorithmException; 6 | import java.security.PublicKey; 7 | import java.security.spec.EdECPoint; 8 | import java.security.spec.EdECPublicKeySpec; 9 | import java.security.spec.InvalidKeySpecException; 10 | import java.security.spec.NamedParameterSpec; 11 | import java.util.Base64; 12 | 13 | final class EdDSAPublicKeyCreator { 14 | 15 | static PublicKey createPublicKey(String curveName, String publicKeyJwt) throws InvalidKeySpecException, NoSuchAlgorithmException 16 | { 17 | Base64.Decoder decoder = Base64.getUrlDecoder(); 18 | byte[] publicKeyBytes = decoder.decode(publicKeyJwt); 19 | int b = publicKeyBytes.length; 20 | 21 | if (b != 32 && b != 57) { 22 | throw new InvalidKeySpecException("Invalid key length for EdDSA key."); 23 | } 24 | 25 | // Byte array is in little endian encoding, the most significant bit in final octet indicates if X is negative or not: 26 | // https://www.rfc-editor.org/rfc/rfc8032.html#section-3.1 27 | // https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.2 28 | boolean XIsNegative = (publicKeyBytes[b-1] & 0x80) != 0; 29 | // Recover y value by clearing x-bit. 30 | publicKeyBytes[b-1] = (byte)(publicKeyBytes[b-1] & 0x7f); 31 | 32 | byte[] publicKeyBytesBE = new byte[b]; 33 | 34 | // Switch to big endian encoding 35 | for(int i = 0; i < b; i++) { 36 | publicKeyBytesBE[i] = publicKeyBytes[b-1-i]; 37 | } 38 | 39 | // Create key from specs 40 | NamedParameterSpec crvKeySpec = new NamedParameterSpec(curveName); 41 | EdECPublicKeySpec edECPublicKeySpec = new EdECPublicKeySpec(crvKeySpec, new EdECPoint(XIsNegative, new BigInteger(1, publicKeyBytesBE))); 42 | KeyFactory keyFactory = KeyFactory.getInstance("EdDSA"); 43 | 44 | return keyFactory.generatePublic(edECPublicKeySpec); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/Expirable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import java.time.Instant; 20 | 21 | public interface Expirable 22 | { 23 | Instant getExpiresAt(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/ExpirationBasedCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import java.time.Clock; 20 | import java.time.Instant; 21 | import java.util.HashSet; 22 | import java.util.Optional; 23 | import java.util.Set; 24 | import java.util.Timer; 25 | import java.util.TimerTask; 26 | import java.util.concurrent.ConcurrentHashMap; 27 | 28 | /** 29 | * A cache that expires it's entries after a given timeout 30 | * @param The key 31 | * @param A value that can expire 32 | */ 33 | public class ExpirationBasedCache 34 | { 35 | private final ConcurrentHashMap _cache; 36 | private final Clock _clock; 37 | 38 | ExpirationBasedCache() 39 | { 40 | _cache = new ConcurrentHashMap<>(); 41 | _clock = Clock.systemUTC(); 42 | 43 | Timer timer = new Timer("cacheExpiration", true); 44 | timer.scheduleAtFixedRate(new TimerTask() { 45 | @Override 46 | public void run() { 47 | expireCacheEntries(); 48 | }} , 60, 60); 49 | } 50 | 51 | public Optional get(K key) 52 | { 53 | //optimistically get a value 54 | V value = _cache.get(key); 55 | 56 | //make sure it's not expired yet 57 | if (value != null && value.getExpiresAt().isAfter(Instant.now(_clock))) 58 | { 59 | value = null; 60 | } 61 | 62 | return Optional.ofNullable(value); 63 | } 64 | 65 | void put(K key, V value) 66 | { 67 | _cache.putIfAbsent(key, value); 68 | } 69 | 70 | private void expireCacheEntries() 71 | { 72 | Instant now = Instant.now(_clock); 73 | //This might miss the last entry if new are put in, but that's ok 74 | //it will be caught in the next expiration round instead. 75 | Set keySet = new HashSet<>(_cache.keySet()); 76 | 77 | for (K key : keySet) 78 | { 79 | V entry = _cache.get(key); 80 | 81 | if (now.isAfter(entry.getExpiresAt())) 82 | { 83 | _cache.remove(key); 84 | } 85 | } 86 | } 87 | 88 | void clear() 89 | { 90 | _cache.clear(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/FilterHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import jakarta.servlet.FilterConfig; 20 | import jakarta.servlet.UnavailableException; 21 | import java.util.Enumeration; 22 | import java.util.LinkedHashMap; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | import java.util.function.Function; 26 | 27 | final class FilterHelper 28 | { 29 | private FilterHelper() 30 | { 31 | // no instantiation - static functions only 32 | } 33 | 34 | static Map initParamsMapFrom(FilterConfig config) 35 | { 36 | Map result = new LinkedHashMap<>(); 37 | Enumeration names = config.getInitParameterNames(); 38 | 39 | while (names.hasMoreElements()) 40 | { 41 | String name = names.nextElement(); 42 | 43 | result.put(name, config.getInitParameter(name)); 44 | } 45 | 46 | return result; 47 | } 48 | 49 | static String getInitParamValue(String name, Map initParams) throws UnavailableException 50 | { 51 | Optional value = getSingleValue(name, initParams); 52 | 53 | if (value.isPresent()) 54 | { 55 | return value.get(); 56 | } 57 | else 58 | { 59 | throw new UnavailableException(missingInitParamMessage(name)); 60 | } 61 | } 62 | 63 | static T getInitParamValue(String name, Map initParams, 64 | Function converter) throws UnavailableException 65 | { 66 | return converter.apply(getInitParamValue(name, initParams)); 67 | } 68 | 69 | static Optional getOptionalInitParamValue(String name, Map initParams, 70 | Function converter) throws UnavailableException 71 | { 72 | Optional value = getSingleValue(name, initParams); 73 | 74 | return value.flatMap(s -> Optional.ofNullable(converter.apply(s))); 75 | } 76 | 77 | private static Optional getSingleValue(String name, Map initParams) throws 78 | UnavailableException 79 | { 80 | return Optional.ofNullable(initParams.get(name)).map(Object::toString); 81 | } 82 | 83 | private static String missingInitParamMessage(String paramName) 84 | { 85 | return String.format("%s - missing required initParam [%s]", 86 | OAuthFilter.class.getName(), 87 | paramName); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/HttpClientProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import jakarta.servlet.UnavailableException; 20 | import java.util.Iterator; 21 | import java.util.Map; 22 | import java.util.ServiceLoader; 23 | 24 | public abstract class HttpClientProvider 25 | { 26 | public HttpClientProvider() 27 | { 28 | } 29 | 30 | static HttpClientProvider provider() 31 | { 32 | ServiceLoader loader = ServiceLoader.load(HttpClientProvider.class); 33 | Iterator it = loader.iterator(); 34 | 35 | if (it.hasNext()) 36 | { 37 | return it.next(); 38 | } 39 | 40 | return new DefaultHttpClientProvider(); 41 | } 42 | 43 | public abstract IntrospectionClient createIntrospectionClient(Map config) throws UnavailableException; 44 | 45 | public abstract WebKeysClient createWebKeysClient(Map config) throws UnavailableException; 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/IntrospectionClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import java.io.Closeable; 20 | import java.io.IOException; 21 | 22 | public interface IntrospectionClient extends Closeable 23 | { 24 | String introspect(String token) throws IOException; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JsonData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonObject; 20 | import javax.json.JsonValue; 21 | import java.time.Instant; 22 | import java.util.Set; 23 | 24 | public class JsonData implements Expirable 25 | { 26 | private final JsonObject _jsonObject; 27 | private final Set _scopes; 28 | 29 | JsonData(JsonObject jsonObject) 30 | { 31 | _jsonObject = jsonObject; 32 | _scopes = JsonUtils.getScopes(jsonObject); 33 | } 34 | 35 | JsonObject getJsonObject() 36 | { 37 | return _jsonObject; 38 | } 39 | 40 | public String getSubject() 41 | { 42 | return JsonUtils.getString(_jsonObject, "sub"); 43 | } 44 | 45 | public Set getScopes() 46 | { 47 | return _scopes; 48 | } 49 | 50 | public Set getClaimNames() 51 | { 52 | return _jsonObject.keySet(); 53 | } 54 | 55 | public JsonObject getClaims() 56 | { 57 | return _jsonObject; 58 | } 59 | 60 | public JsonValue getClaim(String claimName) 61 | { 62 | return _jsonObject.get(claimName); 63 | } 64 | 65 | @Override 66 | public Instant getExpiresAt() 67 | { 68 | return Instant.ofEpochSecond(JsonUtils.getLong(_jsonObject, "exp")); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JsonUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonNumber; 20 | import javax.json.JsonObject; 21 | import javax.json.JsonReaderFactory; 22 | import javax.json.JsonString; 23 | import javax.json.JsonValue; 24 | import javax.json.spi.JsonProvider; 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | import java.util.HashSet; 28 | import java.util.Optional; 29 | import java.util.Set; 30 | 31 | final class JsonUtils 32 | { 33 | private static final String[] NO_SCOPES = {}; 34 | 35 | private JsonUtils() 36 | { 37 | } 38 | 39 | static JsonReaderFactory createDefaultReaderFactory() 40 | { 41 | return JsonProvider.provider().createReaderFactory(Collections.emptyMap()); 42 | } 43 | 44 | static Set getScopes(JsonObject jsonObject) 45 | { 46 | String scopesInToken = getString(jsonObject, "scope"); 47 | String[] presentedScopes = scopesInToken == null ? NO_SCOPES : scopesInToken.split("\\s+"); 48 | 49 | return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(presentedScopes))); 50 | } 51 | 52 | static String getString(JsonObject jsonObject, String name) 53 | { 54 | return Optional.ofNullable(jsonObject.get(name)) 55 | .filter(it -> it.getValueType() == JsonValue.ValueType.STRING) 56 | .map(it -> ((JsonString) it).getString()) 57 | .orElse(null); 58 | } 59 | 60 | static long getLong(JsonObject jsonObject, String name) 61 | { 62 | return Optional.ofNullable(jsonObject.get(name)) 63 | .filter(it -> it.getValueType() == JsonValue.ValueType.NUMBER) 64 | .map(it -> ((JsonNumber) it).longValue()) 65 | .orElse(Long.MIN_VALUE); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JsonWebKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonObject; 20 | import javax.json.JsonString; 21 | import javax.json.JsonValue; 22 | import java.util.Optional; 23 | 24 | class JsonWebKey 25 | { 26 | private final JsonObject _jsonObject; 27 | 28 | private JsonWebKeyType _keyType; 29 | 30 | JsonWebKey(JsonObject jsonObject) 31 | { 32 | _jsonObject = jsonObject; 33 | 34 | JsonValue jsonValue = jsonObject.get("kty"); 35 | 36 | _keyType = JsonWebKeyType.from(jsonValue); 37 | } 38 | 39 | String getKeyId() 40 | { 41 | return getString("kid"); 42 | } 43 | 44 | JsonWebKeyType getKeyType() 45 | { 46 | return _keyType; 47 | } 48 | 49 | String getUse() 50 | { 51 | return getString("use"); 52 | } 53 | 54 | String getXCoordinate() 55 | { 56 | return getString("x"); 57 | } 58 | 59 | String getYCoordinate() 60 | { 61 | return getString("y"); 62 | } 63 | 64 | String getEllipticalCurve() 65 | { 66 | return getString("crv"); 67 | } 68 | 69 | String getModulus() 70 | { 71 | return getString("n"); 72 | } 73 | 74 | String getExponent() 75 | { 76 | return getString("e"); 77 | } 78 | 79 | String getAlgorithm() 80 | { 81 | return getString("alg"); 82 | } 83 | 84 | private String getString(String name) 85 | { 86 | return Optional.ofNullable(_jsonObject.get(name)) 87 | .filter(it -> it.getValueType() == JsonValue.ValueType.STRING) 88 | .map(it -> ((JsonString) it).getString()) 89 | .orElse(null); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JsonWebKeyNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import java.io.IOException; 20 | 21 | class JsonWebKeyNotFoundException extends IOException 22 | { 23 | JsonWebKeyNotFoundException(String msg) 24 | { 25 | super(msg); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JsonWebKeyType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonString; 20 | import javax.json.JsonValue; 21 | import java.util.logging.Logger; 22 | 23 | enum JsonWebKeyType { 24 | EC("EC"), 25 | OCT("oct"), 26 | OKP("OKP"), 27 | RSA("RSA"), 28 | UNSPECIFIED("UNSPECIFIED"); 29 | 30 | private static final Logger _logger = Logger.getLogger(JsonWebKeyType.class.getName()); 31 | String name; 32 | 33 | JsonWebKeyType(String name) { 34 | this.name = name; 35 | } 36 | 37 | static JsonWebKeyType from(JsonValue value) { 38 | if (value == null || value.toString().length() == 0) { 39 | return UNSPECIFIED; 40 | } 41 | 42 | if (value.getValueType() != JsonValue.ValueType.STRING) { 43 | _logger.warning(() -> String.format("Value '%s' is not a string, as required; it is %s", 44 | value, value.getValueType())); 45 | } 46 | 47 | switch (((JsonString) value).getString()) { 48 | case "RSA": 49 | return RSA; 50 | case "EC": 51 | return EC; 52 | case "OKP": 53 | return OKP; 54 | case "oct": 55 | return OCT; 56 | default: 57 | 58 | _logger.warning(() -> String.format("Unknown enumeration value '%s' given.", value)); 59 | 60 | throw new IllegalArgumentException("value"); 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JwkManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonObject; 20 | import javax.json.JsonReader; 21 | import javax.json.JsonReaderFactory; 22 | import java.io.Closeable; 23 | import java.io.IOException; 24 | import java.io.StringReader; 25 | import java.time.Duration; 26 | import java.time.Instant; 27 | import java.util.Collections; 28 | import java.util.HashMap; 29 | import java.util.Map; 30 | import java.util.concurrent.Executors; 31 | import java.util.concurrent.ScheduledExecutorService; 32 | import java.util.concurrent.TimeUnit; 33 | import java.util.logging.Level; 34 | import java.util.logging.Logger; 35 | 36 | final class JwkManager implements Closeable 37 | { 38 | private static final Logger _logger = Logger.getLogger(JwkManager.class.getName()); 39 | private static final String ACCEPT = "Accept"; 40 | 41 | private final TimeBasedCache _jsonWebKeyByKID; 42 | private final WebKeysClient _webKeysClient; 43 | private final ScheduledExecutorService _executor = Executors.newSingleThreadScheduledExecutor(); 44 | private final JsonReaderFactory _jsonReaderFactory; 45 | 46 | JwkManager(long minKidReloadTimeInSeconds, WebKeysClient webKeysClient, JsonReaderFactory jsonReaderFactory) 47 | { 48 | _jsonWebKeyByKID = new TimeBasedCache<>(Duration.ofSeconds(minKidReloadTimeInSeconds), this::reload); 49 | _webKeysClient = webKeysClient; 50 | _jsonReaderFactory = jsonReaderFactory; 51 | 52 | // invalidate the cache periodically to avoid stale state 53 | _executor.scheduleAtFixedRate(this::ensureCacheIsFresh, 5, 15, TimeUnit.MINUTES); 54 | } 55 | 56 | /** 57 | * checks if the JsonWebKey exists in the local cached, otherwise this 58 | * method will call the JsonWebKeyService to get the new keys. 59 | * 60 | * @param keyId keyId 61 | * @return JsonWebKey 62 | */ 63 | JsonWebKey getJsonWebKeyForKeyId(String keyId) throws JsonWebKeyNotFoundException 64 | { 65 | JsonWebKey key = _jsonWebKeyByKID.get(keyId); 66 | 67 | if (key != null) 68 | { 69 | return key; 70 | } 71 | 72 | throw new JsonWebKeyNotFoundException("Json Web Key does not exist: keyid=" + keyId); 73 | } 74 | 75 | private Map reload() 76 | { 77 | Map newKeys = new HashMap<>(); 78 | 79 | try 80 | { 81 | JwksResponse response = parseJwksResponse(_webKeysClient.getKeys()); 82 | 83 | for (JsonWebKey key : response.getKeys()) 84 | { 85 | newKeys.put(key.getKeyId(), key); 86 | } 87 | 88 | _logger.info(() -> String.format("Fetched JsonWebKeys: %s", newKeys)); 89 | 90 | return Collections.unmodifiableMap(newKeys); 91 | } 92 | catch (IOException e) 93 | { 94 | _logger.log(Level.SEVERE, "Could not contact JWKS Server", e); 95 | 96 | return Collections.emptyMap(); 97 | } 98 | } 99 | 100 | private JwksResponse parseJwksResponse(String response) 101 | { 102 | JsonReader jsonReader = _jsonReaderFactory.createReader(new StringReader(response)); 103 | JsonObject jsonObject = jsonReader.readObject(); 104 | 105 | return new JwksResponse(jsonObject); 106 | } 107 | 108 | private void ensureCacheIsFresh() 109 | { 110 | _logger.info("Called ensureCacheIsFresh"); 111 | 112 | Instant lastLoading = _jsonWebKeyByKID.getLastReloadInstant().orElse(Instant.MIN); 113 | boolean cacheIsNotFresh = lastLoading.isBefore(Instant.now() 114 | .minus(_jsonWebKeyByKID.getMinTimeBetweenReloads())); 115 | 116 | if (cacheIsNotFresh) 117 | { 118 | _logger.info("Invalidating JSON WebKeyID cache"); 119 | 120 | _jsonWebKeyByKID.clear(); 121 | } 122 | } 123 | 124 | @Override 125 | public void close() throws IOException 126 | { 127 | _executor.shutdown(); 128 | 129 | if (_webKeysClient instanceof Closeable) 130 | { 131 | ((Closeable) _webKeysClient).close(); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JwksResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonObject; 20 | import javax.json.JsonValue; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.stream.Collectors; 24 | 25 | class JwksResponse 26 | { 27 | private final List _keys; 28 | 29 | JwksResponse(JsonObject jsonObject) 30 | { 31 | JsonValue keys = jsonObject.get("keys"); 32 | 33 | if (keys.getValueType() != JsonValue.ValueType.ARRAY) 34 | { 35 | _keys = Collections.emptyList(); 36 | } 37 | else 38 | { 39 | _keys = keys.asJsonArray().stream() 40 | .filter(it -> it.getValueType() == JsonValue.ValueType.OBJECT) 41 | .map(JsonValue::asJsonObject) 42 | .map(JsonWebKey::new) 43 | .collect(Collectors.toList()); 44 | } 45 | } 46 | 47 | List getKeys() 48 | { 49 | return _keys; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JwtValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | interface JwtValidator extends TokenValidator 20 | { 21 | /** 22 | * The Jwt validator is a class that takes a JWT and makes sure the Signature is valid 23 | * @param jwt the JWT to be validated 24 | * @return the content of the token body if token signature is valid, otherwise null 25 | * @throws TokenValidationException 26 | */ 27 | JsonData validate(String jwt) throws TokenValidationException; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JwtValidatorWithCert.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonReaderFactory; 20 | import java.security.PublicKey; 21 | import java.util.Map; 22 | import java.util.Optional; 23 | import java.util.logging.Logger; 24 | 25 | final class JwtValidatorWithCert extends AbstractJwtValidator 26 | { 27 | private static final Logger _logger = Logger.getLogger(JwtValidatorWithCert.class.getName()); 28 | 29 | private final Map _keys; 30 | 31 | JwtValidatorWithCert(String issuer, String audience, Map publicKeys) 32 | { 33 | this(issuer, audience, publicKeys, JsonUtils.createDefaultReaderFactory()); 34 | } 35 | 36 | JwtValidatorWithCert(String issuer, String audience, Map publicKeys, 37 | JsonReaderFactory jsonReaderFactory) 38 | { 39 | super(issuer, audience, jsonReaderFactory); 40 | 41 | _keys = publicKeys; 42 | } 43 | 44 | @Override 45 | protected Optional getPublicKey(JwtHeader jwtHeader) 46 | { 47 | return Optional.ofNullable(_keys.get(jwtHeader.getString("x5t#S256"))); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/JwtValidatorWithJwk.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonReaderFactory; 20 | import java.io.IOException; 21 | import java.security.NoSuchAlgorithmException; 22 | import java.security.PublicKey; 23 | import java.security.spec.InvalidKeySpecException; 24 | import java.util.Optional; 25 | import java.util.logging.Level; 26 | import java.util.logging.Logger; 27 | 28 | final class JwtValidatorWithJwk extends AbstractJwtValidator 29 | { 30 | private static final Logger _logger = Logger.getLogger(JwtValidatorWithJwk.class.getName()); 31 | 32 | private final JwkManager _jwkManager; 33 | 34 | JwtValidatorWithJwk(long minKidReloadTime, WebKeysClient webKeysClient, String audience, String issuer, 35 | JsonReaderFactory jsonReaderFactory) 36 | { 37 | super(issuer, audience, jsonReaderFactory); 38 | 39 | _jwkManager = new JwkManager(minKidReloadTime, webKeysClient, jsonReaderFactory); 40 | } 41 | 42 | @Override 43 | protected Optional getPublicKey(JwtHeader jwtHeader) 44 | { 45 | Optional result = Optional.empty(); 46 | 47 | try 48 | { 49 | JsonWebKey jsonWebKeyType = _jwkManager.getJsonWebKeyForKeyId(jwtHeader.getKeyId()); 50 | 51 | switch (jsonWebKeyType.getKeyType()) { 52 | case RSA : 53 | result = Optional.of(RsaPublicKeyCreator.createPublicKey(jsonWebKeyType.getModulus(), 54 | jsonWebKeyType.getExponent())); 55 | break; 56 | case OKP : 57 | if (isEdDSAKey(jsonWebKeyType)) { 58 | result = Optional.of(EdDSAPublicKeyCreator.createPublicKey(jsonWebKeyType.getEllipticalCurve(), jsonWebKeyType.getXCoordinate())); 59 | } else { 60 | throw new NoSuchAlgorithmException(String.format("Unsupported curve %s for key %s", jsonWebKeyType.getEllipticalCurve(), jsonWebKeyType.getKeyId())); 61 | } 62 | break; 63 | case EC : 64 | case OCT : 65 | default: 66 | throw new NoSuchAlgorithmException(String.format("Unsupported key type %s for key %s", jsonWebKeyType.getKeyType(), jsonWebKeyType.getKeyId())); 67 | } 68 | } 69 | catch (JsonWebKeyNotFoundException e) 70 | { 71 | // this is not a very exceptional occurrence, so let's not log a stack-trace 72 | _logger.info(() -> String.format("Could not find requested JsonWebKey: %s", e)); 73 | } 74 | catch (NoSuchAlgorithmException | InvalidKeySpecException e) 75 | { 76 | _logger.log(Level.WARNING, "Could not create public key", e); 77 | } 78 | 79 | return result; 80 | } 81 | 82 | private boolean isEdDSAKey(JsonWebKey jsonWebKey) { 83 | String curve = jsonWebKey.getEllipticalCurve(); 84 | return curve != null && (curve.equals("Ed25519") || curve.equals("Ed448")); 85 | } 86 | 87 | @Override 88 | public void close() throws IOException 89 | { 90 | _jwkManager.close(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/OAuthFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import jakarta.servlet.Filter; 20 | import jakarta.servlet.FilterChain; 21 | import jakarta.servlet.FilterConfig; 22 | import jakarta.servlet.ServletException; 23 | import jakarta.servlet.ServletRequest; 24 | import jakarta.servlet.ServletResponse; 25 | import jakarta.servlet.UnavailableException; 26 | import jakarta.servlet.http.HttpServletRequest; 27 | import jakarta.servlet.http.HttpServletResponse; 28 | import java.io.IOException; 29 | import java.util.Arrays; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.Optional; 33 | import java.util.logging.Level; 34 | import java.util.logging.Logger; 35 | 36 | public abstract class OAuthFilter implements Filter 37 | { 38 | private static final String[] NO_SCOPES = {}; 39 | private static final Logger _logger = Logger.getLogger(OAuthFilter.class.getName()); 40 | private static final String WWW_AUTHENTICATE = "WWW-Authenticate"; 41 | private static final String AUTHORIZATION = "Authorization"; 42 | public static final String PRINCIPAL_ATTRIBUTE_NAME = "principal"; 43 | 44 | private Map _filterConfig; // Protected, so subclasses don't have to repeat the conversion to this 45 | private String _oauthHost = null; 46 | private String[] _scopes = null; 47 | 48 | private interface InitParams 49 | { 50 | String OAUTH_HOST = "oauthHost"; 51 | String SCOPE = "scope"; 52 | } 53 | 54 | @Override 55 | public void init(FilterConfig filterConfig) throws ServletException 56 | { 57 | _filterConfig = FilterHelper.initParamsMapFrom(filterConfig); 58 | 59 | _oauthHost = FilterHelper.getInitParamValue(InitParams.OAUTH_HOST, _filterConfig); 60 | 61 | _scopes = FilterHelper.getOptionalInitParamValue(InitParams.SCOPE, _filterConfig, it -> it.split("\\s+")) 62 | .orElse(NO_SCOPES); 63 | } 64 | 65 | /** 66 | * The doFilter is the primary filter method of a Servlet filter. It is implemented as a final method 67 | * and will call the configured filters authenticate and authorize methods. 68 | * Authorize is optional to implement as this filter implements a default scope check method. 69 | * @param servletRequest The default servlet request 70 | * @param servletResponse The default servlet response 71 | * @param filterChain A filter chain to continue with after this filter is done 72 | * @throws IOException when response fails to send an error 73 | * @throws ServletException when authentication fails for some exceptional reason 74 | */ 75 | @Override 76 | public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 77 | throws IOException, ServletException 78 | { 79 | HttpServletResponse response = (HttpServletResponse)servletResponse; 80 | Optional token = extractAccessTokenFromHeader(servletRequest); 81 | String oauthHost = getOAuthServerRealm(); 82 | 83 | if (!token.isPresent()) 84 | { 85 | setReAuthenticate401(response, oauthHost); 86 | 87 | return; 88 | } 89 | 90 | Optional maybeAuthenticatedUser = authenticate(token.get()); 91 | 92 | if (!maybeAuthenticatedUser.isPresent()) 93 | { 94 | setReAuthenticate401(response, oauthHost); 95 | 96 | return; 97 | } 98 | 99 | AuthenticatedUser authenticatedUser = maybeAuthenticatedUser.get(); 100 | 101 | if (!isAuthorized(authenticatedUser)) 102 | { 103 | //403 Forbidden Scope header 104 | setForbidden403(response, oauthHost); 105 | 106 | return; 107 | } 108 | 109 | servletRequest.setAttribute(PRINCIPAL_ATTRIBUTE_NAME, authenticatedUser); 110 | 111 | if (filterChain != null) 112 | { 113 | filterChain.doFilter( 114 | new AuthenticatedUserRequestWrapper((HttpServletRequest)servletRequest, authenticatedUser), 115 | servletResponse); 116 | } 117 | } 118 | 119 | protected Map getFilterConfiguration() 120 | { 121 | return _filterConfig; 122 | } 123 | 124 | private void setReAuthenticate401(HttpServletResponse response, String oauthHost) throws IOException 125 | { 126 | String msg = String.format("Bearer realm=\"%s\"", oauthHost); 127 | 128 | response.setHeader(WWW_AUTHENTICATE, msg); 129 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED); 130 | } 131 | 132 | private void setForbidden403(HttpServletResponse response, String oauthHost) throws IOException 133 | { 134 | String msg = String.format("Bearer realm=\"%s\"", oauthHost); 135 | 136 | response.setHeader(WWW_AUTHENTICATE, msg); 137 | response.sendError(HttpServletResponse.SC_FORBIDDEN); 138 | } 139 | 140 | /** 141 | * Returns the realm of the OAuth server. 142 | * 143 | *

This is used when the filter returns 401, Access Denied, or 403, Forbiggen, with a WWW-Authenticate HTTP 144 | * indicating to the client that authentication is required

145 | * 146 | * @return The OAuth server's realm as string 147 | * @throws UnavailableException when filter is not initialized 148 | */ 149 | protected String getOAuthServerRealm() throws UnavailableException 150 | { 151 | if (_oauthHost == null) 152 | { 153 | throw new UnavailableException("Filter not initialized"); 154 | } 155 | 156 | return _oauthHost; 157 | } 158 | 159 | protected abstract TokenValidator createTokenValidator(Map initParams) throws UnavailableException; 160 | 161 | protected abstract TokenValidator getTokenValidator(); 162 | 163 | /** 164 | * This is the authenticate method of the filter, it will take the token as string input and 165 | * must perform the appropriate operation to validate the token. 166 | * @param token - The token extracted from the Authorization header and stripped of the Bearer 167 | * @return An AuthenticatedUser if the token was valid, or null if not. 168 | * @throws ServletException when authentication fails for some exceptional reason 169 | */ 170 | protected Optional authenticate(String token) throws ServletException 171 | { 172 | AuthenticatedUser result = null; 173 | 174 | try 175 | { 176 | JsonData validationResult = getTokenValidator().validate(token); 177 | 178 | result = AuthenticatedUser.from(validationResult); 179 | } 180 | catch (Exception e) 181 | { 182 | _logger.fine(() -> String.format("Failed to validate incoming token due to: %s", e.getMessage())); 183 | } 184 | 185 | return Optional.ofNullable(result); 186 | } 187 | /** 188 | * Authorizes the current request by checking that all configured scopes are included in the one presented in the 189 | * request. 190 | * 191 | *

If no scopes were configured for the filter, then any request is authorized. When a set of scopes are 192 | * configured, however, the filter will ensure that all such scopes are included in the presented token.

193 | * 194 | * @param authenticatedUser the user that was authenticated 195 | * @return true if access is allowed 196 | */ 197 | protected boolean isAuthorized(AuthenticatedUser authenticatedUser) 198 | { 199 | List requiredScopes = Arrays.asList(_scopes); 200 | 201 | // No scopes required for authorization 202 | return requiredScopes.isEmpty() || authenticatedUser.getScopes().containsAll(requiredScopes); 203 | } 204 | 205 | @Override 206 | public void destroy() 207 | { 208 | _logger.info("Destroying OAuthFilter"); 209 | 210 | if (getTokenValidator() != null) 211 | { 212 | try 213 | { 214 | getTokenValidator().close(); 215 | } 216 | catch (IOException e) 217 | { 218 | _logger.log(Level.WARNING, "Problem closing token validator", e); 219 | } 220 | } 221 | } 222 | 223 | /** 224 | * Extracts the token from the Authorization header, removing the Bearer prefix 225 | * @param request The incoming request 226 | * @return the token or null if not present 227 | */ 228 | private Optional extractAccessTokenFromHeader(ServletRequest request) 229 | { 230 | HttpServletRequest httpRequest = (HttpServletRequest)request; 231 | String authorizationHeader = httpRequest.getHeader(AUTHORIZATION); 232 | String result = null; 233 | 234 | if (authorizationHeader != null && authorizationHeader.startsWith("Bearer")) 235 | { 236 | String[] tokenSplit = authorizationHeader.split("[Bb][Ee][Aa][Rr][Ee][Rr]\\s+"); 237 | 238 | if(tokenSplit.length != 2) 239 | { 240 | _logger.fine("Incoming token in Authorization header is not a Bearer token"); 241 | } 242 | else 243 | { 244 | result = tokenSplit[1]; 245 | } 246 | } 247 | 248 | return Optional.ofNullable(result); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/OAuthIntrospectResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonObject; 20 | 21 | class OAuthIntrospectResponse 22 | { 23 | private final JsonObject _jsonObject; 24 | 25 | OAuthIntrospectResponse(JsonObject jsonObject) 26 | { 27 | _jsonObject = jsonObject; 28 | } 29 | 30 | boolean isActive() 31 | { 32 | return _jsonObject.getBoolean("active"); 33 | } 34 | 35 | JsonObject getJsonObject() 36 | { 37 | return _jsonObject; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/OAuthJwtFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonReaderFactory; 20 | import javax.json.spi.JsonProvider; 21 | import jakarta.servlet.FilterConfig; 22 | import jakarta.servlet.ServletException; 23 | import jakarta.servlet.UnavailableException; 24 | import java.util.Map; 25 | import java.util.logging.Logger; 26 | 27 | public class OAuthJwtFilter extends OAuthFilter 28 | { 29 | private static final Logger _logger = Logger.getLogger(OAuthJwtFilter.class.getName()); 30 | 31 | private long _minKidReloadTimeInSeconds = 3600; 32 | 33 | private TokenValidator _jwtValidator = null; 34 | 35 | private interface InitParams 36 | { 37 | String ISSUER = "issuer"; 38 | String AUDIENCE = "audience"; 39 | String MIN_KID_RELOAD_TIME = "_minKidReloadTimeInSeconds"; 40 | } 41 | 42 | @Override 43 | public void init(FilterConfig filterConfig) throws ServletException 44 | { 45 | super.init(filterConfig); 46 | 47 | _minKidReloadTimeInSeconds = FilterHelper.getOptionalInitParamValue( 48 | InitParams.MIN_KID_RELOAD_TIME, 49 | getFilterConfiguration(), Long::parseLong).orElse(_minKidReloadTimeInSeconds); 50 | 51 | synchronized (this) 52 | { 53 | if (_jwtValidator == null) 54 | { 55 | _jwtValidator = createTokenValidator(getFilterConfiguration()); 56 | 57 | _logger.info(() -> String.format("%s successfully initialized", OAuthFilter.class.getSimpleName())); 58 | } 59 | else 60 | { 61 | _logger.warning("Attempted to set webkey URI more than once! Ignoring further attempts."); 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | protected TokenValidator createTokenValidator(Map filterConfig) throws UnavailableException 68 | { 69 | // Pass all of the filter's config to the ReaderFactory factory method. It'll ignore anything it doesn't 70 | // understand (per JSR 353). This way, clients can change the provider using the service locator and configure 71 | // the ReaderFactory using the filter's config. 72 | JsonReaderFactory jsonReaderFactory = JsonProvider.provider().createReaderFactory(filterConfig); 73 | WebKeysClient webKeysClient = HttpClientProvider.provider().createWebKeysClient(filterConfig); 74 | String audience = FilterHelper.getInitParamValue(InitParams.AUDIENCE, filterConfig); 75 | String issuer = FilterHelper.getInitParamValue(InitParams.ISSUER, filterConfig); 76 | 77 | return _jwtValidator = new JwtValidatorWithJwk(_minKidReloadTimeInSeconds, webKeysClient, audience, issuer, 78 | jsonReaderFactory); 79 | } 80 | 81 | @Override 82 | protected TokenValidator getTokenValidator() 83 | { 84 | return _jwtValidator; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/OAuthOpaqueFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonReaderFactory; 20 | import javax.json.spi.JsonProvider; 21 | import jakarta.servlet.FilterConfig; 22 | import jakarta.servlet.ServletException; 23 | import jakarta.servlet.UnavailableException; 24 | import java.util.Map; 25 | import java.util.logging.Logger; 26 | 27 | public class OAuthOpaqueFilter extends OAuthFilter 28 | { 29 | private static final Logger _logger = Logger.getLogger(OAuthOpaqueFilter.class.getName()); 30 | 31 | private TokenValidator _opaqueTokenValidator = null; 32 | 33 | private interface InitParams 34 | { 35 | String SCOPE = "scope"; 36 | } 37 | 38 | @Override 39 | public void init(FilterConfig filterConfig) throws ServletException 40 | { 41 | super.init(filterConfig); 42 | 43 | synchronized (this) 44 | { 45 | if (_opaqueTokenValidator == null) 46 | { 47 | _opaqueTokenValidator = createTokenValidator(getFilterConfiguration()); 48 | 49 | _logger.info(() -> String.format("%s successfully initialized", OAuthFilter.class.getSimpleName())); 50 | } 51 | else 52 | { 53 | _logger.warning("Attempted to set introspect URI more than once! Ignoring further attempts."); 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | protected TokenValidator getTokenValidator() 60 | { 61 | return _opaqueTokenValidator; 62 | } 63 | 64 | @Override 65 | protected TokenValidator createTokenValidator(Map initParams) throws UnavailableException 66 | { 67 | // Like in the OAuthJwtFilter, we'll reuse the config of this filter + the service locator to 68 | // get a JsonReaderFactory 69 | JsonReaderFactory jsonReaderFactory = JsonProvider.provider().createReaderFactory(initParams); 70 | IntrospectionClient introspectionClient = HttpClientProvider.provider() 71 | .createIntrospectionClient(initParams); 72 | 73 | return new OpaqueTokenValidator(introspectionClient, jsonReaderFactory); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/OpaqueTokenValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import javax.json.JsonObject; 20 | import javax.json.JsonReader; 21 | import javax.json.JsonReaderFactory; 22 | import java.io.Closeable; 23 | import java.io.IOException; 24 | import java.io.StringReader; 25 | import java.time.Instant; 26 | import java.util.Optional; 27 | 28 | public class OpaqueTokenValidator implements Closeable, TokenValidator 29 | { 30 | private final IntrospectionClient _introspectionClient; 31 | private final ExpirationBasedCache _tokenCache; 32 | private final JsonReaderFactory _jsonReaderFactory; 33 | 34 | OpaqueTokenValidator(IntrospectionClient introspectionClient, JsonReaderFactory jsonReaderFactory) 35 | { 36 | _introspectionClient = introspectionClient; 37 | _tokenCache = new ExpirationBasedCache<>(); 38 | _jsonReaderFactory = jsonReaderFactory; 39 | } 40 | 41 | public JsonData validate(String token) throws TokenValidationException 42 | { 43 | Optional cachedValue = _tokenCache.get(token); 44 | 45 | if (cachedValue.isPresent()) 46 | { 47 | return cachedValue.get(); 48 | } 49 | 50 | String introspectJson; 51 | 52 | try 53 | { 54 | introspectJson = _introspectionClient.introspect(token); 55 | } 56 | catch (Exception e) 57 | { 58 | // TODO: Add logging 59 | throw new TokenValidationException("Failed to introspect token", e); 60 | } 61 | 62 | OAuthIntrospectResponse response = parseIntrospectResponse(introspectJson); 63 | 64 | if (response.isActive()) 65 | { 66 | JsonData newToken = new JsonData(response.getJsonObject()); 67 | 68 | if (newToken.getExpiresAt().isAfter(Instant.now())) 69 | { 70 | //Note: If this cache is backed by some persistent storage, the token should be hashed and not stored 71 | // in clear text 72 | _tokenCache.put(token, newToken); 73 | 74 | return newToken; 75 | } 76 | else 77 | { 78 | throw new ExpiredTokenException(); 79 | } 80 | } 81 | else 82 | { 83 | throw new RevokedTokenException(); 84 | } 85 | } 86 | 87 | private OAuthIntrospectResponse parseIntrospectResponse(String introspectJson) 88 | { 89 | JsonReader jsonReader = _jsonReaderFactory.createReader(new StringReader(introspectJson)); 90 | JsonObject jsonObject = jsonReader.readObject(); 91 | 92 | return new OAuthIntrospectResponse(jsonObject); 93 | } 94 | 95 | @Override 96 | public void close() throws IOException 97 | { 98 | _introspectionClient.close(); 99 | _tokenCache.clear(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/RsaPublicKeyCreator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import java.math.BigInteger; 20 | import java.security.KeyFactory; 21 | import java.security.NoSuchAlgorithmException; 22 | import java.security.PublicKey; 23 | import java.security.spec.InvalidKeySpecException; 24 | import java.security.spec.RSAPublicKeySpec; 25 | import java.util.Base64; 26 | 27 | class RsaPublicKeyCreator 28 | { 29 | static PublicKey createPublicKey(String modulus, String exponent) throws InvalidKeySpecException, NoSuchAlgorithmException 30 | { 31 | Base64.Decoder decoder = Base64.getUrlDecoder(); 32 | BigInteger bigModulus = new BigInteger(1, decoder.decode(modulus)); 33 | BigInteger bigExponent = new BigInteger(1, decoder.decode(exponent)); 34 | RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(bigModulus, bigExponent); 35 | KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 36 | 37 | return keyFactory.generatePublic(publicKeySpec); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/TimeBasedCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import java.time.Clock; 20 | import java.time.Duration; 21 | import java.time.Instant; 22 | import java.util.Collections; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | import java.util.function.Supplier; 26 | 27 | /** 28 | * A Cache which attempts to reload its entries, limited to a minimum reloading time, 29 | * when a value is requested for a key which does not exist in the backing map. 30 | *

31 | * The cache will not attempt to reload more often than once every minTimeBetweenReloads, 32 | * which is a parameter provided in the constructor. 33 | *

34 | * This cache is Thread-safe. 35 | * 36 | * @param key type 37 | * @param value type 38 | */ 39 | class TimeBasedCache 40 | { 41 | private final Object _cacheLock = new Object(); 42 | 43 | private volatile Map _cache; 44 | private volatile Instant _nextLoadingEarliestTime = Instant.MIN; 45 | 46 | private volatile Instant _lastLoading; 47 | 48 | private final Duration _minTimeBetweenReloads; 49 | private final Supplier> _valuesSupplier; 50 | private final Clock _clock; 51 | 52 | TimeBasedCache(Duration minTimeBetweenReloads, Supplier> valuesSupplier) 53 | { 54 | this(minTimeBetweenReloads, valuesSupplier, Collections.emptyMap(), Clock.systemDefaultZone()); 55 | } 56 | 57 | TimeBasedCache(Duration minTimeBetweenReloads, 58 | Supplier> valuesSupplier, 59 | Map initialCache, 60 | Clock clock) 61 | { 62 | _minTimeBetweenReloads = minTimeBetweenReloads; 63 | _valuesSupplier = valuesSupplier; 64 | _cache = initialCache; 65 | _clock = clock; 66 | } 67 | 68 | private boolean mayReload() 69 | { 70 | return Instant.now(_clock).isAfter(_nextLoadingEarliestTime); 71 | } 72 | 73 | public V get(K key) 74 | { 75 | // optimistically try to get the key without locking 76 | V value = _cache.get(key); 77 | 78 | if (value == null && mayReload()) 79 | { 80 | reloadCache(); 81 | value = _cache.get(key); 82 | } 83 | 84 | return value; 85 | } 86 | 87 | void clear() 88 | { 89 | synchronized (_cacheLock) 90 | { 91 | _cache = Collections.emptyMap(); 92 | } 93 | } 94 | 95 | private void reloadCache() 96 | { 97 | final Instant earliestTime = _nextLoadingEarliestTime; 98 | 99 | synchronized (_cacheLock) 100 | { 101 | // if the field was updated while we locked, we should NOT reload the cache 102 | // as it must have just happened 103 | if (earliestTime == _nextLoadingEarliestTime) 104 | { 105 | _cache = _valuesSupplier.get(); 106 | _lastLoading = Instant.now(_clock); 107 | _nextLoadingEarliestTime = _lastLoading.plus(_minTimeBetweenReloads); 108 | } 109 | } 110 | } 111 | 112 | Optional getLastReloadInstant() 113 | { 114 | return Optional.ofNullable(_lastLoading); 115 | } 116 | 117 | Duration getMinTimeBetweenReloads() 118 | { 119 | return _minTimeBetweenReloads; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/TokenValidationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | public class TokenValidationException extends Exception 20 | { 21 | public TokenValidationException(String msg) 22 | { 23 | super(msg); 24 | } 25 | 26 | public TokenValidationException(String msg, Throwable t) 27 | { 28 | super(msg, t); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/TokenValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import java.io.Closeable; 20 | import java.io.IOException; 21 | 22 | public interface TokenValidator extends Closeable 23 | { 24 | JsonData validate(String token) throws TokenValidationException; 25 | 26 | @Override 27 | default void close() throws IOException {} 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/ValidationExceptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | class InvalidTokenFormatException extends TokenValidationException 20 | { 21 | private static final String _message = "Invalid token format"; 22 | 23 | InvalidTokenFormatException() 24 | { 25 | super(_message); 26 | } 27 | 28 | InvalidTokenFormatException(String msg) 29 | { 30 | super(msg); 31 | } 32 | } 33 | 34 | class MissingAlgorithmException extends TokenValidationException 35 | { 36 | private static final String _message = "No algorithm was provided in the token"; 37 | 38 | MissingAlgorithmException() 39 | { 40 | super(_message); 41 | } 42 | } 43 | 44 | class InvalidAudienceException extends TokenValidationException 45 | { 46 | private static final String _formattedMessage = "Audience %s does not match expected one %s"; 47 | 48 | InvalidAudienceException(String expectedAudience, String actualAudience) 49 | { 50 | super(String.format(_formattedMessage, actualAudience, expectedAudience)); 51 | } 52 | } 53 | 54 | class InvalidSignatureException extends TokenValidationException 55 | { 56 | private static final String _message = "The signature of the given token did not match the computed one"; 57 | 58 | InvalidSignatureException() 59 | { 60 | super(_message); 61 | } 62 | } 63 | 64 | class InvalidIssuanceInstantException extends TokenValidationException 65 | { 66 | private static final String _message = "The token is invalid because it was issued in the past"; 67 | 68 | InvalidIssuanceInstantException() 69 | { 70 | super(_message); 71 | } 72 | } 73 | 74 | class ExpiredTokenException extends TokenValidationException 75 | { 76 | private static final String _message = "The token is invalid because it is expired"; 77 | 78 | ExpiredTokenException() 79 | { 80 | super(_message); 81 | } 82 | } 83 | 84 | class RevokedTokenException extends TokenValidationException 85 | { 86 | private static final String _message = "The token is invalid because it has been revoked"; 87 | 88 | RevokedTokenException() 89 | { 90 | super(_message); 91 | } 92 | } 93 | 94 | class UnknownAlgorithmException extends InvalidTokenFormatException 95 | { 96 | private static final String _formattedMessage = "The algorithm %s is not recognized"; 97 | 98 | UnknownAlgorithmException(String unrecognizedAlgorithm) 99 | { 100 | super(String.format(_formattedMessage, unrecognizedAlgorithm)); 101 | } 102 | } 103 | 104 | class InvalidIssuerException extends TokenValidationException 105 | { 106 | private static final String _formattedMessage = "Issuer %s does not match expected one %s"; 107 | 108 | InvalidIssuerException(String expectedIssuer, String actualIssuer) 109 | { 110 | super(String.format(_formattedMessage, actualIssuer, expectedIssuer)); 111 | } 112 | } 113 | 114 | class UnknownSignatureVerificationKey extends TokenValidationException 115 | { 116 | private static final String _message = "The key used to sign the token is unknown and untrusted"; 117 | 118 | UnknownSignatureVerificationKey() 119 | { 120 | super(_message); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/io/curity/oauth/WebKeysClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import java.io.IOException; 20 | 21 | public interface WebKeysClient 22 | { 23 | String getKeys() throws IOException; 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/io/curity/oauth/AuthenticatedUserRequestWrapperTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.junit.runners.Parameterized; 22 | 23 | import javax.json.Json; 24 | import javax.json.JsonObject; 25 | import jakarta.servlet.http.HttpServletRequest; 26 | import jakarta.servlet.http.HttpServletResponse; 27 | import java.util.Arrays; 28 | import java.util.Collection; 29 | 30 | import static org.hamcrest.CoreMatchers.is; 31 | import static org.junit.Assert.assertThat; 32 | import static org.junit.Assert.assertTrue; 33 | import static org.mockito.Mockito.mock; 34 | import static org.mockito.Mockito.when; 35 | 36 | public class AuthenticatedUserRequestWrapperTest 37 | { 38 | @Test 39 | public void testCanAuthenticate() throws Exception 40 | { 41 | // GIVEN: an authenticated user 42 | JsonObject json = Json.createObjectBuilder().add("sub", "test-user").build(); 43 | JsonData jsonData = new JsonData(json); 44 | AuthenticatedUser user = AuthenticatedUser.from(jsonData); 45 | 46 | // AND: a request that wraps that authenticated user 47 | HttpServletRequest mockRequest = mock(HttpServletRequest.class); 48 | AuthenticatedUserRequestWrapper authenticatedUserRequest = new AuthenticatedUserRequestWrapper(mockRequest, user); 49 | 50 | // WHEN: authenticate is called on the request 51 | boolean actual = authenticatedUserRequest.authenticate(null); 52 | 53 | // THEN: the result is true because the user is authenticated 54 | assertTrue(actual); 55 | } 56 | 57 | @RunWith(value = Parameterized.class) 58 | public static class AuthenticatedWhenUserIsNullTest 59 | { 60 | private final int _statusCode; 61 | private final boolean _expectedResult; 62 | private final boolean _isCommitted; 63 | 64 | public AuthenticatedWhenUserIsNullTest(int statusCode, boolean isCommitted, boolean expectedResult) 65 | { 66 | _statusCode = statusCode; 67 | _isCommitted = isCommitted; 68 | _expectedResult = expectedResult; 69 | } 70 | 71 | @Parameterized.Parameters( 72 | name = "{index}: testAuthenticateWhenResponseIsCommitted: status={0}, isCommitted={1}, authenticated={2}") 73 | public static Collection data() 74 | { 75 | return Arrays.asList(new Object[][] { 76 | {HttpServletResponse.SC_UNAUTHORIZED, true, true}, 77 | {HttpServletResponse.SC_UNAUTHORIZED, false, false}, 78 | {HttpServletResponse.SC_FORBIDDEN, true, true}, 79 | {HttpServletResponse.SC_FORBIDDEN, false, false}, 80 | {HttpServletResponse.SC_SEE_OTHER, true, false}, 81 | {HttpServletResponse.SC_OK, false, false}, 82 | }); 83 | } 84 | 85 | /** 86 | * Test that authenticate returns the correct value when the user isn't authenticate but the response is committed. 87 | * 88 | *

When an authentication challenge has been sent (i.e., a 401 or 403), then the user is considered 89 | * authenticated (or more precisely, being authenticated). If the status code is some other value though, the 90 | * user isn't be authenticated, so the response should be false.

91 | * 92 | *

This behavior is defined in {@link HttpServletRequest#authenticate(HttpServletResponse)}:

93 | * 94 | *
95 | * Return false if authentication is incomplete and the underlying login mechanism has committed, in the 96 | * response, the message (e.g., challenge) and HTTP status code to be returned to the user. 97 | *
98 | * 99 | * @see HttpServletRequest#authenticate(HttpServletResponse) 100 | */ 101 | @Test 102 | public void testAuthenticateWhenResponseIsCommitted() throws Exception 103 | { 104 | // GIVEN: a request that doesn't have a token that can be used to authenticate the user 105 | HttpServletRequest mockRequest = mock(HttpServletRequest.class); 106 | AuthenticatedUserRequestWrapper authenticatedUserRequest = new AuthenticatedUserRequestWrapper(mockRequest, null); 107 | 108 | // AND: A request that is already committed and has the status code set to 401 109 | HttpServletResponse mockResponse = mock(HttpServletResponse.class); 110 | when(mockResponse.isCommitted()).thenReturn(_isCommitted); 111 | when(mockResponse.getStatus()).thenReturn(_statusCode); 112 | 113 | // WHEN: authenticate is called on the response 114 | boolean actual = authenticatedUserRequest.authenticate(mockResponse); 115 | 116 | // THEN: the result is true even though the user is yet authenticated 117 | assertThat(actual, is(_expectedResult)); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/io/curity/oauth/HttpClientProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import org.junit.Test; 20 | 21 | import jakarta.servlet.UnavailableException; 22 | import java.util.Map; 23 | 24 | import static org.hamcrest.CoreMatchers.instanceOf; 25 | import static org.junit.Assert.assertThat; 26 | 27 | public class HttpClientProviderTest 28 | { 29 | @Test 30 | public void provider() throws Exception 31 | { 32 | // GIVEN: The fake, test provider is wired up using the test resource file in META-INF/services 33 | 34 | // WHEN: the provider is fetched 35 | HttpClientProvider httpClientProvider = HttpClientProvider.provider(); 36 | 37 | // THEN: the one that the ServiceLoader returns is the expected type 38 | assertThat(httpClientProvider, instanceOf(FakeHttpClientProvider.class)); 39 | } 40 | 41 | public static class FakeHttpClientProvider extends HttpClientProvider 42 | { 43 | @Override 44 | public IntrospectionClient createIntrospectionClient(Map config) throws UnavailableException 45 | { 46 | return null; 47 | } 48 | 49 | @Override 50 | public WebKeysClient createWebKeysClient(Map config) throws UnavailableException 51 | { 52 | return null; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/io/curity/oauth/JwtTokenIssuer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import com.owlike.genson.Genson; 20 | import com.owlike.genson.GensonBuilder; 21 | import org.apache.commons.codec.digest.DigestUtils; 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | import org.jose4j.jwa.AlgorithmConstraints; 25 | import org.jose4j.jws.AlgorithmIdentifiers; 26 | import org.jose4j.jws.JsonWebSignature; 27 | import org.jose4j.jwt.ReservedClaimNames; 28 | import org.jose4j.lang.JoseException; 29 | 30 | import java.security.PrivateKey; 31 | import java.security.cert.Certificate; 32 | import java.security.cert.CertificateEncodingException; 33 | import java.time.Instant; 34 | import java.util.Arrays; 35 | import java.util.LinkedHashMap; 36 | import java.util.List; 37 | import java.util.Map; 38 | import java.util.UUID; 39 | 40 | public class JwtTokenIssuer 41 | { 42 | private static final Log _logger = LogFactory.getLog(JwtTokenIssuer.class); 43 | 44 | private final int _skewTolerance; 45 | private final String _issuer; 46 | private final String _algorithm; 47 | private final PrivateKey _privateKey; 48 | private final Certificate _cert; 49 | private final String _keyId; 50 | 51 | private final Genson _genson = new GensonBuilder().setHtmlSafe(false).create(); 52 | 53 | //Issue Unsigned token. 54 | public JwtTokenIssuer(String issuer) 55 | { 56 | this(issuer, 0, AlgorithmIdentifiers.NONE, null, null, null); 57 | } 58 | 59 | //Certs 60 | JwtTokenIssuer(String issuer, PrivateKey privateKey, Certificate cert) 61 | { 62 | this(issuer, 0, AlgorithmIdentifiers.RSA_USING_SHA256, privateKey, cert, null); 63 | } 64 | 65 | //JWKS 66 | public JwtTokenIssuer(String issuer, PrivateKey privateKey, String keyId) 67 | { 68 | this(issuer, 0, AlgorithmIdentifiers.RSA_USING_SHA256, privateKey, null, keyId); 69 | } 70 | 71 | //Certs 72 | public JwtTokenIssuer(String issuer, String algorithm, PrivateKey privateKey, Certificate cert) 73 | { 74 | this(issuer, 0, algorithm, privateKey, cert, null); 75 | } 76 | 77 | //JWKS 78 | public JwtTokenIssuer(String issuer, String algorithm, PrivateKey privateKey, String keyId) 79 | { 80 | this(issuer, 0, algorithm, privateKey, null, keyId); 81 | } 82 | 83 | //The full constructor 84 | private JwtTokenIssuer(String issuer, int skewTolerance, String algorithm, PrivateKey privateKey, Certificate 85 | cert, String keyId) 86 | { 87 | _issuer = issuer; 88 | _skewTolerance = skewTolerance * 60; 89 | _algorithm = algorithm; 90 | _privateKey = privateKey; 91 | _cert = cert; 92 | _keyId = keyId; 93 | } 94 | 95 | String issueToken(String subject, String audience, int lifetimeInMinutes, Map attributes) 96 | throws Exception 97 | { 98 | String[] audiences = stringToArray(audience); 99 | 100 | return issueToken(subject, Arrays.asList(audiences), lifetimeInMinutes, attributes); 101 | } 102 | 103 | private String issueToken(String subject, List audiences, int lifetimeInMinutes, Map 104 | attributes) 105 | throws Exception 106 | { 107 | Map claims = new LinkedHashMap<>(); 108 | 109 | //Store the initial attributes 110 | for (String key : attributes.keySet()) 111 | { 112 | claims.put(key, attributes.get(key)); 113 | } 114 | 115 | JsonWebSignature token = new JsonWebSignature(); 116 | 117 | if (AlgorithmIdentifiers.NONE.equals(this._algorithm) && _privateKey == null) 118 | { 119 | token.setAlgorithmConstraints(AlgorithmConstraints.ALLOW_ONLY_NONE); 120 | } 121 | 122 | long issuedAt = Instant.now().getEpochSecond(); 123 | long expirationTime = lifetimeInMinutes * 60 + issuedAt + _skewTolerance; 124 | 125 | claims.put(ReservedClaimNames.ISSUER, _issuer); 126 | claims.put(ReservedClaimNames.SUBJECT, subject); 127 | claims.put(ReservedClaimNames.AUDIENCE, arrayOrString(audiences)); 128 | 129 | claims.put(ReservedClaimNames.ISSUED_AT, issuedAt); 130 | claims.put(ReservedClaimNames.NOT_BEFORE, issuedAt - _skewTolerance); 131 | claims.put(ReservedClaimNames.EXPIRATION_TIME, expirationTime); 132 | claims.put(ReservedClaimNames.JWT_ID, UUID.randomUUID().toString()); 133 | 134 | String payload = _genson.serialize(claims); 135 | 136 | token.setHeader(ReservedClaimNames.ISSUER, _issuer); 137 | 138 | try 139 | { 140 | if (this._cert != null) 141 | { 142 | byte[] x5t = DigestUtils.sha(this._cert.getEncoded()); 143 | byte[] x5tS256 = DigestUtils.sha256(this._cert.getEncoded()); 144 | 145 | //Set DER encoded base64urlsafe string as header params 146 | String b64x5t = org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString(x5t); 147 | String b64x5tS256 = org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString(x5tS256); 148 | 149 | token.setHeader("x5t", b64x5t); 150 | token.setHeader("x5t#S256", b64x5tS256); 151 | 152 | _logger.trace("x5t: " + b64x5t); 153 | _logger.trace("x5t#256: " + b64x5tS256); 154 | } 155 | else if (this._keyId != null) 156 | { 157 | token.setKeyIdHeaderValue(this._keyId); 158 | } 159 | } 160 | catch (CertificateEncodingException ce) 161 | { 162 | throw new Exception("Unknown certificate encoding", ce); 163 | } 164 | 165 | token.setPayload(payload); 166 | 167 | if (_privateKey != null) 168 | { 169 | token.setKey(_privateKey); //Can be null for "none" algorithm 170 | } 171 | 172 | token.setAlgorithmHeaderValue(_algorithm); 173 | 174 | try 175 | { 176 | String serializedToken = token.getCompactSerialization(); 177 | 178 | _logger.trace("Serialized Token: " + serializedToken); 179 | 180 | if (_logger.isTraceEnabled()) 181 | { 182 | //With jose4j version 0.3.4 the getPayload will perform a Verify operation 183 | //This requires the Public Key to be set as the Key. 184 | if (this._cert != null) 185 | { 186 | token.setKey(_cert.getPublicKey()); 187 | String headers = token.getHeader(); 188 | String body = token.getPayload(); 189 | String maskedToken = serializedToken.length() >= 20 ? serializedToken.substring(0, 10) : ""; 190 | 191 | String message = String.format("Issuing token: %s******\nHeader = %s\nBody = %s", 192 | maskedToken, headers, body); 193 | 194 | _logger.trace(message); 195 | } 196 | 197 | _logger.trace(serializedToken); 198 | } 199 | 200 | return serializedToken; 201 | } 202 | catch (JoseException e) 203 | { 204 | _logger.error("Could not issue token", e); 205 | 206 | throw new Exception("Could not issue a JWT token. See inner exception for details", e); 207 | } 208 | } 209 | 210 | private String[] stringToArray(String str) 211 | { 212 | String[] ret; 213 | ret = str.split(" "); 214 | return ret; 215 | } 216 | 217 | private Object arrayOrString(List data) 218 | { 219 | if (data.size() == 1) 220 | { 221 | return data.get(0); 222 | } 223 | 224 | return data; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/test/java/io/curity/oauth/JwtWithCertTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import org.apache.commons.codec.digest.DigestUtils; 20 | import org.apache.logging.log4j.LogManager; 21 | import org.apache.logging.log4j.Logger; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.junit.runners.Parameterized; 26 | 27 | import javax.json.JsonObject; 28 | import javax.json.JsonString; 29 | import java.io.File; 30 | import java.io.FileInputStream; 31 | import java.io.InputStream; 32 | import java.net.URL; 33 | import java.security.KeyStore; 34 | import java.security.KeyStoreException; 35 | import java.security.PrivateKey; 36 | import java.security.PublicKey; 37 | import java.security.cert.Certificate; 38 | import java.security.interfaces.EdECPrivateKey; 39 | import java.util.HashMap; 40 | import java.util.Map; 41 | 42 | import static junit.framework.TestCase.assertEquals; 43 | import static junit.framework.TestCase.assertNotNull; 44 | import static junit.framework.TestCase.assertTrue; 45 | 46 | @RunWith(Parameterized.class) 47 | public class JwtWithCertTest 48 | { 49 | private static final Logger _logger = LogManager.getLogger(JwtWithCertTest.class); 50 | 51 | private final String SUBJECT = "testsubject"; 52 | private final String AUDIENCE = "foo:audience"; 53 | private final String ISSUER = "test:issuer"; 54 | private final int EXPIRATION = 200; 55 | private final String EXTRA_CLAIM = "TEST_KEY"; 56 | private final String EXTRA_CLAIM_VALUE = "TEST_VALUE"; 57 | 58 | private final String PATH_TO_KEY = "/Se.Curity.Test.p12"; 59 | private final String KEY_PWD = "Password1"; 60 | 61 | private String _testToken; 62 | private KeyStore _keyStore; 63 | 64 | @Parameterized.Parameter() 65 | public String _keyAlias; 66 | 67 | @Parameterized.Parameter(1) 68 | public String _algorithm; 69 | 70 | @Parameterized.Parameters 71 | public static Object[] keysToTest() { 72 | return new Object[][] { {"se.curity.test", "RS256"}, {"se.curity.test.ed25519", "EdDSA"}, {"se.curity.test.ed448", "EdDSA"} }; 73 | } 74 | 75 | @Before 76 | public void before() throws Exception 77 | { 78 | loadKeyStore(); 79 | 80 | PrivateKey key = getPrivateKey(); 81 | Certificate cert = getCertificate(); 82 | 83 | if (!_algorithm.equals("EdDSA")) { 84 | // Create test token on the fly 85 | JwtTokenIssuer issuer = new JwtTokenIssuer(ISSUER, _algorithm, key, cert); 86 | Map attributes = new HashMap<>(); 87 | attributes.put(EXTRA_CLAIM, EXTRA_CLAIM_VALUE); 88 | _testToken = issuer.issueToken(SUBJECT, AUDIENCE, EXPIRATION, attributes); 89 | } else { 90 | // Use hardcoded values until jose4j supports EdDSA 91 | String curveName = ((EdECPrivateKey) key).getParams().getName(); 92 | if ("Ed25519".equals(curveName)) { 93 | _testToken ="eyJraWQiOiItMTkwOTU3MjI1NyIsIng1dCI6IlNIZDRIQ1VkQThISlZHTTJVV3o1Tm1JUFRHMCIsIng1dCNTMjU2IjoiS0lZVnBHXzVXSnh0ZUdOUTVLR043M0xlQWNHS0w4MmMyWFhaR0M5RUNKVSIsImFsZyI6IkVkRFNBIn0.eyJqdGkiOiJlMzE2YjBmOS1mN2JlLTQ3M2QtOGMzNi05NGMwNzRlMjMzNjEiLCJkZWxlZ2F0aW9uSWQiOiIzZDE2NzM5Ni02NGEzLTQ2MmYtOGMyZS02MzdhYzM0NzdkMTMiLCJleHAiOjE5Njg0MDkwNzMsIm5iZiI6MTY1MzA0OTA3Mywic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6InRlc3Q6aXNzdWVyIiwic3ViIjoidGVzdHN1YmplY3QiLCJhdWQiOiJmb286YXVkaWVuY2UiLCJpYXQiOjE2NTMwNDkwNzMsInB1cnBvc2UiOiJhY2Nlc3NfdG9rZW4iLCJURVNUX0tFWSI6IlRFU1RfVkFMVUUifQ.NGWCDwzCPOx50-WBJRqKFvPy2562rqFjNS3Q9zmJqNhdxtZK3s7g7JWtgI_AwnJBnaPeC1ATMYyxKjionwzQAA"; 94 | } else { 95 | _testToken ="eyJraWQiOiIxNzE2OTk5OTA0IiwieDV0IjoiMUlSVEJMTFFlaUwyWVpMQjFWRER2Q1RHb3pjIiwieDV0I1MyNTYiOiJTbGVDbTlwRVI5a2ZiTjBYeGlqa1g4MmdyR0hUYXhOTkNCRHNUMHR1M3lBIiwiYWxnIjoiRWREU0EifQ.eyJqdGkiOiIyNjNiNmM2OS02NTExLTQ5YjktYWVlYi0yY2JkOGMyMGE3NGUiLCJkZWxlZ2F0aW9uSWQiOiIwY2NjZmMyZi1mY2EzLTRlOGQtOTgxYy05ZjU5MzIyNmYyNTEiLCJleHAiOjE5Njg0MDg5NzUsIm5iZiI6MTY1MzA0ODk3NSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6InRlc3Q6aXNzdWVyIiwic3ViIjoidGVzdHN1YmplY3QiLCJhdWQiOiJmb286YXVkaWVuY2UiLCJpYXQiOjE2NTMwNDg5NzUsInB1cnBvc2UiOiJhY2Nlc3NfdG9rZW4iLCJURVNUX0tFWSI6IlRFU1RfVkFMVUUifQ.2gcRnLTFnCsdkElgcecSjxvrKA3bKAFuUf5vhVapdLqxZvx6E1BblTzjaVjqy3OT0OzdN3p1q5kApJ5EjVUT0tdjHVxZMBtkosviYM5EL2UkJO_T3tA-on7h0lfcufxnhd_TUOlM_YTkJxFGSkOtLg4A"; 96 | } 97 | } 98 | } 99 | 100 | @Test 101 | public void testFindAndValidateWithOneCert() throws Exception 102 | { 103 | JwtValidator validator = new JwtValidatorWithCert(ISSUER, AUDIENCE, prepareKeyMap()); 104 | 105 | _logger.info("test token = {}", _testToken); 106 | 107 | JsonData validatedToken = validator.validate(_testToken); 108 | 109 | assertNotNull(validatedToken); 110 | } 111 | 112 | @Test 113 | public void testValidContentInToken() throws Exception 114 | { 115 | JwtValidator validator = new JwtValidatorWithCert(ISSUER, AUDIENCE, prepareKeyMap()); 116 | 117 | JsonData result = validator.validate(_testToken); 118 | 119 | _logger.info("test token = {}", _testToken); 120 | 121 | assertNotNull(result); 122 | 123 | JsonObject jsonObject = result.getJsonObject(); 124 | 125 | assertTrue(jsonObject.containsKey("sub")); 126 | assertTrue(jsonObject.containsKey(EXTRA_CLAIM)); 127 | 128 | assertEquals(SUBJECT, ((JsonString) jsonObject.get("sub")).getString()); 129 | assertEquals(EXTRA_CLAIM_VALUE, ((JsonString) jsonObject.get(EXTRA_CLAIM)).getString()); 130 | } 131 | 132 | /** 133 | * Load the private Keymap with the x5t256 thumbprint and the public key 134 | * The map only contains a single key 135 | * @return a map with a single entry of a certificate thumbprint and the corresponding public key 136 | * @throws Exception When key could not be loaded from certificate 137 | */ 138 | private Map prepareKeyMap() throws Exception 139 | { 140 | Map keys = new HashMap<>(); 141 | 142 | Certificate cert = getCertificate(); 143 | 144 | PublicKey key = cert.getPublicKey(); 145 | 146 | byte[] x5tS256 = DigestUtils.sha256(cert.getEncoded()); 147 | String b64x5tS256 = org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString(x5tS256); 148 | 149 | keys.put(b64x5tS256, key); 150 | 151 | return keys; 152 | } 153 | 154 | private void loadKeyStore() 155 | throws Exception 156 | { 157 | URL url = getClass().getResource(PATH_TO_KEY); 158 | assert url != null; 159 | File certFile = new File(url.getFile()); 160 | 161 | InputStream keyIS = new FileInputStream(certFile); 162 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 163 | keyStore.load(keyIS, KEY_PWD.toCharArray()); 164 | 165 | keyIS.close(); 166 | 167 | this._keyStore=keyStore; 168 | } 169 | 170 | private PrivateKey getPrivateKey() 171 | throws Exception 172 | { 173 | return (PrivateKey)this._keyStore.getKey(_keyAlias, KEY_PWD.toCharArray()); 174 | 175 | } 176 | 177 | private Certificate getCertificate() throws KeyStoreException { 178 | //Get key by alias (found in the p12 file using: 179 | //keytool -list -keystore test-root-ca.p12 -storepass foobar -storetype PKCS12 180 | return this._keyStore.getCertificate(_keyAlias); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/test/java/io/curity/oauth/JwtWithJwksTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import org.apache.logging.log4j.LogManager; 20 | import org.apache.logging.log4j.Logger; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.Parameterized; 25 | 26 | import javax.json.JsonObject; 27 | import javax.json.JsonReaderFactory; 28 | import javax.json.JsonString; 29 | import javax.json.spi.JsonProvider; 30 | import java.io.File; 31 | import java.io.FileInputStream; 32 | import java.io.InputStream; 33 | import java.net.URL; 34 | import java.security.KeyStore; 35 | import java.security.KeyStoreException; 36 | import java.security.PrivateKey; 37 | import java.security.cert.Certificate; 38 | import java.security.interfaces.EdECPrivateKey; 39 | import java.util.Collections; 40 | import java.util.HashMap; 41 | import java.util.Map; 42 | 43 | import static junit.framework.TestCase.assertEquals; 44 | import static junit.framework.TestCase.assertNotNull; 45 | import static junit.framework.TestCase.assertTrue; 46 | import static org.mockito.Mockito.mock; 47 | import static org.mockito.Mockito.when; 48 | 49 | @RunWith(Parameterized.class) 50 | public class JwtWithJwksTest 51 | { 52 | private static final Logger _logger = LogManager.getLogger(JwtWithJwksTest.class); 53 | 54 | private final String SUBJECT = "testsubject"; 55 | private final String AUDIENCE = "foo:audience"; 56 | private final String ISSUER = "test:issuer"; 57 | private final int EXPIRATION = 200; 58 | private final String EXTRA_CLAIM = "TEST_KEY"; 59 | private final String EXTRA_CLAIM_VALUE = "TEST_VALUE"; 60 | 61 | // Used for signing tokens 62 | private final String PATH_TO_KEY = "/Se.Curity.Test.p12"; 63 | private final String KEY_PWD = "Password1"; 64 | 65 | private String _testToken; 66 | private KeyStore _keyStore; 67 | 68 | @Parameterized.Parameter() 69 | public String _keyAlias; 70 | 71 | @Parameterized.Parameter(1) 72 | public String _algorithm; 73 | 74 | @Parameterized.Parameter(2) 75 | public String _keyId; 76 | 77 | @Parameterized.Parameters 78 | public static Object[] keysToTest() { 79 | return new Object[][] { {"se.curity.test", "RS256", "-38074812"}, {"se.curity.test.ed25519", "EdDSA", "-1909572257"}, {"se.curity.test.ed448", "EdDSA", "1716999904"} }; 80 | } 81 | 82 | @Before 83 | public void before() throws Exception 84 | { 85 | loadKeyStore(); 86 | 87 | PrivateKey key = getPrivateKey(); 88 | 89 | if (!_algorithm.equals("EdDSA")) { 90 | // Create test token on the fly 91 | JwtTokenIssuer issuer = new JwtTokenIssuer(ISSUER, _algorithm, key, _keyId); 92 | Map attributes = new HashMap<>(); 93 | attributes.put(EXTRA_CLAIM, EXTRA_CLAIM_VALUE); 94 | _testToken = issuer.issueToken(SUBJECT, AUDIENCE, EXPIRATION, attributes); 95 | } else { 96 | // Use hardcoded values until jose4j supports EdDSA 97 | String curveName = ((EdECPrivateKey) key).getParams().getName(); 98 | if ("Ed25519".equals(curveName)) { 99 | _testToken ="eyJraWQiOiItMTkwOTU3MjI1NyIsIng1dCI6IlNIZDRIQ1VkQThISlZHTTJVV3o1Tm1JUFRHMCIsIng1dCNTMjU2IjoiS0lZVnBHXzVXSnh0ZUdOUTVLR043M0xlQWNHS0w4MmMyWFhaR0M5RUNKVSIsImFsZyI6IkVkRFNBIn0.eyJqdGkiOiJlMzE2YjBmOS1mN2JlLTQ3M2QtOGMzNi05NGMwNzRlMjMzNjEiLCJkZWxlZ2F0aW9uSWQiOiIzZDE2NzM5Ni02NGEzLTQ2MmYtOGMyZS02MzdhYzM0NzdkMTMiLCJleHAiOjE5Njg0MDkwNzMsIm5iZiI6MTY1MzA0OTA3Mywic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6InRlc3Q6aXNzdWVyIiwic3ViIjoidGVzdHN1YmplY3QiLCJhdWQiOiJmb286YXVkaWVuY2UiLCJpYXQiOjE2NTMwNDkwNzMsInB1cnBvc2UiOiJhY2Nlc3NfdG9rZW4iLCJURVNUX0tFWSI6IlRFU1RfVkFMVUUifQ.NGWCDwzCPOx50-WBJRqKFvPy2562rqFjNS3Q9zmJqNhdxtZK3s7g7JWtgI_AwnJBnaPeC1ATMYyxKjionwzQAA"; 100 | } else { 101 | _testToken ="eyJraWQiOiIxNzE2OTk5OTA0IiwieDV0IjoiMUlSVEJMTFFlaUwyWVpMQjFWRER2Q1RHb3pjIiwieDV0I1MyNTYiOiJTbGVDbTlwRVI5a2ZiTjBYeGlqa1g4MmdyR0hUYXhOTkNCRHNUMHR1M3lBIiwiYWxnIjoiRWREU0EifQ.eyJqdGkiOiIyNjNiNmM2OS02NTExLTQ5YjktYWVlYi0yY2JkOGMyMGE3NGUiLCJkZWxlZ2F0aW9uSWQiOiIwY2NjZmMyZi1mY2EzLTRlOGQtOTgxYy05ZjU5MzIyNmYyNTEiLCJleHAiOjE5Njg0MDg5NzUsIm5iZiI6MTY1MzA0ODk3NSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6InRlc3Q6aXNzdWVyIiwic3ViIjoidGVzdHN1YmplY3QiLCJhdWQiOiJmb286YXVkaWVuY2UiLCJpYXQiOjE2NTMwNDg5NzUsInB1cnBvc2UiOiJhY2Nlc3NfdG9rZW4iLCJURVNUX0tFWSI6IlRFU1RfVkFMVUUifQ.2gcRnLTFnCsdkElgcecSjxvrKA3bKAFuUf5vhVapdLqxZvx6E1BblTzjaVjqy3OT0OzdN3p1q5kApJ5EjVUT0tdjHVxZMBtkosviYM5EL2UkJO_T3tA-on7h0lfcufxnhd_TUOlM_YTkJxFGSkOtLg4A"; 102 | } 103 | } 104 | } 105 | 106 | @Test 107 | public void testFindAndValidateWithOneJwk() throws Exception 108 | { 109 | JsonReaderFactory jsonReaderFactory = JsonProvider.provider().createReaderFactory(Collections.emptyMap()); 110 | WebKeysClient webKeysClient = mock(WebKeysClient.class); 111 | 112 | JwtValidatorWithJwk validator = new JwtValidatorWithJwk(0, webKeysClient, AUDIENCE, ISSUER,jsonReaderFactory); 113 | when(webKeysClient.getKeys()).thenReturn(prepareKeyMap().get(_keyId)); 114 | _logger.info("test token = {}", _testToken); 115 | 116 | JsonData validatedToken = validator.validate(_testToken); 117 | 118 | assertNotNull(validatedToken); 119 | } 120 | 121 | @Test 122 | public void testValidContentInToken() throws Exception 123 | { 124 | JsonReaderFactory jsonReaderFactory = JsonProvider.provider().createReaderFactory(Collections.emptyMap()); 125 | WebKeysClient webKeysClient = mock(WebKeysClient.class); 126 | 127 | JwtValidatorWithJwk validator = new JwtValidatorWithJwk(0, webKeysClient, AUDIENCE, ISSUER,jsonReaderFactory); 128 | when(webKeysClient.getKeys()).thenReturn(prepareKeyMap().get(_keyId)); 129 | _logger.info("test token = {}", _testToken); 130 | 131 | JsonData result = validator.validate(_testToken); 132 | 133 | _logger.info("test token = {}", _testToken); 134 | 135 | assertNotNull(result); 136 | 137 | JsonObject jsonObject = result.getJsonObject(); 138 | 139 | assertTrue(jsonObject.containsKey("sub")); 140 | assertTrue(jsonObject.containsKey(EXTRA_CLAIM)); 141 | 142 | assertEquals(SUBJECT, ((JsonString) jsonObject.get("sub")).getString()); 143 | assertEquals(EXTRA_CLAIM_VALUE, ((JsonString) jsonObject.get(EXTRA_CLAIM)).getString()); 144 | } 145 | 146 | /** 147 | * Load the private keymap with the kid and the jwks 148 | * The map only contains a single key 149 | * @return a map with a single entry representing a JWKS that contains the key with the keyid 150 | */ 151 | private Map prepareKeyMap() 152 | { 153 | Map keys = new HashMap<>(); 154 | 155 | keys.put("-38074812","{\"keys\":[{\"kty\":\"RSA\",\"kid\":\"-38074812\",\"use\":\"sig\",\"alg\":\"RS256\",\"n\":\"yMAHZiIfbAgmZJ-_4Gj-wdS8rvaKNBbnHz_krmd-kkX51bA1EsUc0CN672-xnUb_-E_-u_GoWhJzdjiBuz9XasSfQK8WyAwbc7MLkw40A7Zxl2sfsxGTod3qi1u8mjguoc9CbVqPdYe_9YPVxoK4CeJz6V8AsPcxVJxYq6os1rI9qFx_6a1JdQEhetGtkHLFvwo80UTzKXKhGXSu96WrXnkDE8Kw5TSKvh2gI_BX4QHXjE82xldJRJ8QIXGpRNbdyzGkUdjsrhmZl3ARC9IUlxmowkcEEIzjfbOKBVGrVcJ7rHb0GYNaKtMB_MlH1uAPDxl6qKeXOAZ8YEZ1r0ToPw\",\"e\":\"AQAB\",\"x5t\":\"MR-pGTa866RdZLjN6Vwrfay907g\"}]}"); 156 | keys.put("-1909572257", "{\"keys\":[{\"kty\":\"OKP\",\"kid\":\"-1909572257\",\"use\":\"sig\",\"alg\":\"EdDSA\",\"crv\":\"Ed25519\",\"x\":\"XWxGtApfcqmKI7p0OKnF5JSEWMVoLsytFXLEP7xZ_l8\",\"x5t\":\"SHd4HCUdA8HJVGM2UWz5NmIPTG0\"}]}"); 157 | keys.put("1716999904","{\"keys\":[{\"kty\":\"OKP\",\"kid\":\"1716999904\",\"use\":\"sig\",\"alg\":\"EdDSA\",\"crv\":\"Ed448\",\"x\":\"lDc565Rydl9MUCoOB9JpGV3pUSHm7FvuiuEMvrvRkS7PeYL41rPU6s2rMdLeHiXfSxvR1veh4C0A\",\"x5t\":\"1IRTBLLQeiL2YZLB1VDDvCTGozc\"}]}"); 158 | return keys; 159 | } 160 | 161 | private void loadKeyStore() 162 | throws Exception 163 | { 164 | URL url = getClass().getResource(PATH_TO_KEY); 165 | assert url != null; 166 | File certFile = new File(url.getFile()); 167 | 168 | InputStream keyIS = new FileInputStream(certFile); 169 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 170 | keyStore.load(keyIS, KEY_PWD.toCharArray()); 171 | 172 | keyIS.close(); 173 | 174 | this._keyStore=keyStore; 175 | } 176 | 177 | private PrivateKey getPrivateKey() 178 | throws Exception 179 | { 180 | return (PrivateKey)this._keyStore.getKey(_keyAlias, KEY_PWD.toCharArray()); 181 | 182 | } 183 | 184 | private Certificate getCertificate() throws KeyStoreException { 185 | //Get key by alias (found in the p12 file using: 186 | //keytool -list -keystore test-root-ca.p12 -storepass foobar -storetype PKCS12 187 | return this._keyStore.getCertificate(_keyAlias); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/test/java/io/curity/oauth/TimeBasedCacheTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Curity AB. 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 | * http://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 | 17 | package io.curity.oauth; 18 | 19 | import org.junit.Test; 20 | 21 | import java.time.Clock; 22 | import java.time.Duration; 23 | import java.time.Instant; 24 | import java.util.Collections; 25 | import java.util.Map; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | import java.util.function.Supplier; 28 | 29 | import static org.junit.Assert.assertNotNull; 30 | import static org.junit.Assert.assertNull; 31 | import static org.mockito.Mockito.mock; 32 | import static org.mockito.Mockito.when; 33 | 34 | public class TimeBasedCacheTest 35 | { 36 | @Test 37 | public void doesNotReloadCacheWithinTimeLimit() 38 | throws Exception 39 | { 40 | // fake clock returns current time, then the same thing again, 41 | // then 4 seconds later, then 8 seconds later. 42 | Clock fakeClock = mock(Clock.class); 43 | Instant now = Instant.now(); 44 | when(fakeClock.millis()).thenCallRealMethod(); 45 | when(fakeClock.instant()).thenReturn( 46 | now, // get("0") - first map loading 47 | now, // cache asks when the latest loading happened 48 | now.plus(Duration.ofSeconds(4)), // get("1") - no reloading 49 | // get("0") - should not even ask about the time as entry was found 50 | now.plus(Duration.ofSeconds(4)), // get("1") - no reloading 51 | now.plus(Duration.ofSeconds(8))); // get("1") - reload 52 | 53 | @SuppressWarnings("unchecked") 54 | Supplier fakeSupplier = mock(Supplier.class); 55 | when(fakeSupplier.get()).thenReturn( 56 | Collections.singletonMap("0", 0), 57 | Collections.singletonMap("1", 1)); 58 | 59 | // the map will always contain a single entry with the number of reloads as in 60 | // ("reloads" -> reloads) 61 | AtomicInteger reloads = new AtomicInteger(0); 62 | 63 | TimeBasedCache cache = new TimeBasedCache<>(Duration.ofSeconds(5), 64 | () -> Collections.singletonMap(Integer.toString(reloads.get()), reloads.getAndIncrement()), 65 | Collections.emptyMap(), fakeClock); 66 | 67 | // should have only ("0" -> 0) in the map in the beginning 68 | assertNotNull(cache.get("0")); 69 | assertNull(cache.get("1")); 70 | 71 | // second time we try, the map should not reload 72 | assertNotNull(cache.get("0")); 73 | assertNull(cache.get("1")); 74 | 75 | // when the first reload happens, the cache contains ("1" -> 1) 76 | assertNotNull(cache.get("1")); 77 | assertNull(cache.get("0")); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/services/io.curity.oauth.HttpClientProvider: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017 Curity AB. 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 | # http://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 | 17 | io.curity.oauth.HttpClientProviderTest$FakeHttpClientProvider -------------------------------------------------------------------------------- /src/test/resources/Se.Curity.Test.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curityio/oauth-filter-for-java/5ebd70f1a8db334981ccf084dd3a95964d4cb63b/src/test/resources/Se.Curity.Test.p12 -------------------------------------------------------------------------------- /src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | true 25 | 26 | 27 | --------------------------------------------------------------------------------