Skip to content

Commit 7ed4df1

Browse files
committed
update lab 4
1 parent d7f7377 commit 7ed4df1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+275
-94
lines changed

docs/images/jwt_generator.png

31.4 KB
Loading

docs/images/jwt_generator_result.png

205 KB
Loading

lab1/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ OAuth2/OIDC compliant resource server with customized mapping of token claims to
116116

117117
#### Explore the initial application
118118

119-
Please navigate your Java IDE to the lab1/library-server-initial project and at first explore this project a bit.
119+
Please navigate your Java IDE to the __lab1/library-server-initial__ project and at first explore this project a bit.
120120
Then start the application by running the class _com.example.library.server.Lab1InitialLibraryServerApplication_.
121121

122122
To test if the application works as expected, either
@@ -277,7 +277,7 @@ This configuration above...
277277

278278
#### Step 2: Run and test basic resource server
279279

280-
After removing all password relics from our application it should be possible to re-start
280+
Now it should be possible to re-start
281281
the reconfigured application _com.example.library.server.Lab1InitialLibraryServerApplication_.
282282

283283
Now, the requests you have tried when starting this lab using basic authentication won't work any more
@@ -706,7 +706,7 @@ This ends part 1 of this lab. We continue with part 2 to replace the automatic m
706706
own custom mapping.
707707

708708
__<u>Important Note</u>__: If you could not manage to finish part 1 then just use the
709-
project _library-server-complete-custom_ for the next labs.
709+
project __lab1/library-server-complete-custom__ for the next labs.
710710

711711
<hr>
712712

@@ -722,7 +722,7 @@ to authorities.
722722
723723
![Spring IO Workshop 2019](../docs/images/automatic_role_mapping.png)
724724
725-
To have a look open the project _library-server-complete-automatic_.
725+
To have a look open the project __lab1/library-server-complete-automatic__.
726726
727727
<hr>
728728

lab2/library-client-complete/README.md

-33
This file was deleted.

lab2/library-client-initial/README.md

-33
This file was deleted.

lab3/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -269,5 +269,5 @@ If time still allows you can continue with [Lab 4](../lab4/README.md) to see how
269269
configure the resource server from [Lab 1](../lab1/README.md) with a custom static private/public key pair
270270
and create your own JWT tokens using the private key.
271271

272-
This is quite helpful in testing environments, e.g. doing load/performmance testing and preventing
273-
from load testing the identity server.
272+
This is quite helpful in testing environments, e.g. doing load/performance testing and preventing
273+
from load testing the identity server as well.

lab4/README.md

+254-16
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,271 @@
1-
# Lab 3: Creating an OAuth 2.0/OIDC compliant Client
1+
# Lab 4: Creating a static OAuth 2.0/OIDC resource server
22

3+
In this fourth and final lab you will see how you can
4+
configure the resource server from [Lab 1](../lab1/README.md) with a custom static private/public key pair
5+
and create an application to generate your own JWT tokens using the corresponding signing private key.
36

7+
This is quite helpful in testing environments, e.g. doing load/performance testing and preventing
8+
from load testing the identity server as well.
9+
10+
In this lab we will not really implement anything but try how to use such static resource server
11+
with custom generated JWt tokens.
12+
13+
<u>Note:</u>
14+
The contents of this lab are build upon the preview of [Spring Security 5.2.0 Milestone 2](https://spring.io/blog/2019/04/16/spring-security-5-2-0-m2-released).
15+
16+
## Lab Contents
17+
18+
* [Lab 4 contents](#lab-contents)
19+
* [Lab 4 Tutorial](#lab-4-tutorial)
20+
* [Step 1: Implement a resource server with static public key](#step-1-resource-server-with-static-token-validation)
21+
* [Step 2: Generate custom JWT with the JWT generator app](#step-2-run-jwt-generator-web-application)
22+
* [Step 3: Run and test basic resource server](#step-3-run-and-test-static-resource-server)
23+
24+
The [Keycloak](https://keycloak.org) identity provider is not required any more for this lab .
25+
26+
## Lab 4 Tutorial
27+
28+
Lab-4 is actually split into three steps:
29+
30+
1. Look into a resource server with __static public key__ to verify JWT tokens
31+
2. Generate custom JWT tokens for different user identities to be used at the resource server of step 1
32+
3. Make requests to the resource server of step 1 with generated JWT from step 2
33+
34+
### Contents of lab 4 folder
35+
36+
In the lab 4 folder you find 3 applications:
37+
38+
* __library-server-static-complete__: This application is the complete static resource server
39+
* __jwt-generator__: This application is the JWT generator to generate custom JWT tokens
40+
41+
### Step 1: Resource server with static token validation
42+
43+
Now, let's start with step 1 of this lab. Here we will have a look into the required changes we need
44+
compared to the resource server of [Lab 1](../lab1/README.md) to support static public keys for token signature validation.
45+
46+
In [Lab 1](../lab1/README.md) we have seen how Spring security 5 uses the
47+
[OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig) specification
48+
to completely configure the resource server to use our keycloak instance.
49+
50+
As we will now locally validate the incoming JWT access tokens using a static public key we do not
51+
need the discovery entries (especially the JWKS uri) any more.
52+
53+
You can see the changes in _application.yml_, here no _issuer uri_ property is required any more.
54+
Instead we specify a location reference to a file containing a public key to verify JWT tokens.
55+
56+
This looks like this:
57+
58+
```yaml
59+
spring:
60+
jpa:
61+
open-in-view: false
62+
jackson:
63+
date-format: com.fasterxml.jackson.databind.util.StdDateFormat
64+
default-property-inclusion: non_null
65+
security:
66+
oauth2:
67+
resourceserver:
68+
jwt:
69+
publicKeyLocation: classpath:library_server.pub
70+
```
71+
72+
Now we have to use this public key to configure the _JwtDecoder_ to use this for validating
73+
JWT tokens instead of contacting keycloak.
74+
75+
This requires a small change in the class _com.example.library.server.config.WebSecurityConfiguration_:
76+
77+
Open the class _com.example.library.server.config.WebSecurityConfiguration_ and look at the
78+
changes:
79+
80+
```java
81+
package com.example.library.server.config;
82+
83+
import com.example.library.server.security.AudienceValidator;
84+
import com.example.library.server.security.LibraryUserDetailsService;
85+
import com.example.library.server.security.LibraryUserJwtAuthenticationConverter;
86+
import com.example.library.server.security.LibraryUserRolesJwtAuthenticationConverter;
87+
import org.springframework.beans.factory.annotation.Value;
88+
import org.springframework.context.annotation.Bean;
89+
import org.springframework.context.annotation.Configuration;
90+
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
91+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
92+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
93+
import org.springframework.security.config.http.SessionCreationPolicy;
94+
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
95+
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
96+
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
97+
import org.springframework.security.oauth2.jwt.Jwt;
98+
import org.springframework.security.oauth2.jwt.JwtDecoder;
99+
import org.springframework.security.oauth2.jwt.JwtValidators;
100+
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
101+
102+
import java.security.interfaces.RSAPublicKey;
103+
104+
@Configuration
105+
@EnableGlobalMethodSecurity(prePostEnabled = true)
106+
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
107+
108+
private final LibraryUserDetailsService libraryUserDetailsService;
109+
110+
@Value("${spring.security.oauth2.resourceserver.jwt.publicKeyLocation}")
111+
private RSAPublicKey key;
112+
113+
public WebSecurityConfiguration(LibraryUserDetailsService libraryUserDetailsService) {
114+
this.libraryUserDetailsService = libraryUserDetailsService;
115+
}
116+
117+
@Override
118+
protected void configure(HttpSecurity http) throws Exception {
119+
http.sessionManagement()
120+
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
121+
.and()
122+
.csrf()
123+
.disable()
124+
.authorizeRequests()
125+
.anyRequest()
126+
.fullyAuthenticated()
127+
.and()
128+
.oauth2ResourceServer()
129+
.jwt()
130+
.jwtAuthenticationConverter(libraryUserJwtAuthenticationConverter());
131+
}
132+
133+
@Bean
134+
JwtDecoder jwtDecoder() {
135+
NimbusJwtDecoder jwtDecoder =
136+
NimbusJwtDecoder.withPublicKey(this.key).build();
137+
138+
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator();
139+
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer("test_issuer");
140+
OAuth2TokenValidator<Jwt> withAudience =
141+
new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
142+
143+
jwtDecoder.setJwtValidator(withAudience);
144+
145+
return jwtDecoder;
146+
}
147+
148+
@Bean
149+
LibraryUserJwtAuthenticationConverter libraryUserJwtAuthenticationConverter() {
150+
return new LibraryUserJwtAuthenticationConverter(libraryUserDetailsService);
151+
}
152+
}
4153
```
5-
http --form http://localhost:8080/auth/realms/workshop/protocol/openid-connect/token grant_type=password \
6-
username=bwayne password=wayne client_id=library-client client_secret=9584640c-3804-4dcd-997b-93593cfb9ea7
7-
```
8154

155+
This configuration above looks like the one as in [Lab 1](../lab1/README.md) with one important change:
9156

10157
```
11-
http localhost:9091/library-service/books 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cC...'
158+
@Value("${spring.security.oauth2.resourceserver.jwt.publicKeyLocation}")
159+
private RSAPublicKey key;
160+
161+
...
162+
163+
NimbusJwtDecoder jwtDecoder =
164+
NimbusJwtDecoder.withPublicKey(this.key).build();
165+
```
166+
167+
Here we use the public key (using RSA crypto algorithm) we read from the _publicKeyLocation_
168+
and create a NimbusJwtDecoder using this public key instead of configuring a JwtDecoder
169+
from issuer uri.
170+
171+
With this configuration in place we have already a working resource server
172+
that can handle JWt access tokens transmitted via http bearer token header.
173+
Spring Security also validates by default:
174+
175+
* the JWT signature against the given static public key
176+
* the JWT _iss_ claim against the configured issuer uri
177+
* that the JWT is not expired, if the JWT contains such entry
178+
179+
<hr>
180+
181+
### Step 2: Run JWT generator web application
182+
183+
Please navigate your Java IDE to the __lab4/jwt-generator__ project.
184+
Then start the application by running the class _com.example.jwt.generator.JwtGeneratorApplication_.
185+
186+
After starting navigate your browser to [localhost:9093](http://localhost:9093).
187+
188+
Then you should see a screen like the following one.
189+
190+
![Spring IO Workshop 2019](../docs/images/jwt_generator.png)
191+
192+
To generate an JWT access token with the correct user identity and role information
193+
please fill the shown form with one of the following users and roles:
194+
195+
| Username | Email | Role |
196+
| ---------| ------------------------ | --------------- |
197+
| bwayne | [email protected] | library_user |
198+
| bbanner | [email protected] | library_user |
199+
| pparker | [email protected] | library_curator |
200+
| ckent | [email protected] | library_admin |
201+
202+
After filling the form click on the button _Generate JWT_ then you should get another web page
203+
with the generate access token. This should look like this one.
204+
205+
![Spring IO Workshop 2019](../docs/images/jwt_generator_result.png)
206+
207+
To continue with this lab copy the contents of the JWT and use this JWT as access token to
208+
make a request to the resource server in the next step.
209+
210+
### Step 3: Run and test static resource server
211+
212+
Please navigate your Java IDE to the __lab4/library-server-static-complete__ project and at first explore this project a bit.
213+
Then start the application by running the class _com.example.library.server.CompleteStaticLibraryServerApplication_.
214+
215+
Same as in [Lab 1](../lab1/README.md) we require bearer tokens in JWT format to authenticate at our resource server.
216+
217+
To do this we will need to run the copied access token from the JWT generator web application in the previous step.
218+
219+
To make a request for a list of users we have to
220+
specify the access token as part of a _Authorization_ header of type _Bearer_ like this:
221+
222+
httpie:
223+
224+
```bash
225+
http localhost:9091/library-server/users \
226+
'Authorization: Bearer [access_token]'
12227
```
13228

229+
curl:
230+
231+
```bash
232+
curl -H 'Authorization: Bearer [access_token]' \
233+
-v http://localhost:9091/library-server/users | jq
14234
```
15-
HTTP/1.1 200 OK
16-
Content-Type: application/json
17235

236+
You have to replace _[access_token]_ with the one you have obtained from the
237+
JWt generator application.
238+
239+
Navigate your web browser to [jwt.io](https://jwt.io) and paste your access token into the
240+
_Encoded_ text field.
241+
242+
![Spring IO Workshop 2019](../docs/images/jwt_io.png)
243+
244+
If you scroll down a bit on the right hand side then you will see the following block
245+
(depending on which user you have specified when generating a JWT):
246+
247+
```json
18248
{
19-
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgO...",
20-
"expires_in": 300,
21-
"not-before-policy": 1556650611,
22-
"refresh_expires_in": 1800,
23-
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIg...",
24-
"scope": "profile email user",
25-
"session_state": "c92a82d1-8e6d-44d7-a2f3-02f621066968",
26-
"token_type": "bearer"
249+
"scope": "library_admin email profile",
250+
"email_verified": true,
251+
"name": "Clark Kent",
252+
"groups": [
253+
"library_admin"
254+
],
255+
"preferred_username": "ckent",
256+
"given_name": "Clark",
257+
"family_name": "Kent",
258+
"email": "[email protected]"
27259
}
28260
```
261+
As you can see our user has the scopes _library_admin_, _email_ and _profile_.
262+
These scopes are now mapped to the Spring Security authorities
263+
_SCOPE_library_admin_, _SCOPE_email_ and _SCOPE_profile_.
29264

265+
![Spring IO Workshop 2019](../docs/images/jwt_io_decoded.png)
30266

267+
This request should succeed with an '200' OK status and return a list of users.
31268

269+
<hr>
32270

33-
271+
This concludes the final Lab 4 and the whole hands-on workshop part.

0 commit comments

Comments
 (0)