Skip to content

Commit c431334

Browse files
committed
Some minor code and documentation improvments.
Signed-off-by: Paulo Silva <paulos@criticalblue.com>
1 parent 8336ae6 commit c431334

File tree

5 files changed

+94
-45
lines changed

5 files changed

+94
-45
lines changed

README.md

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ Approov JWT token.
3434

3535
A custom payload claim in an Approov token is a base64 encoded sha256 hash of
3636
some unique identifier we may want to tie with the Approov token to enhance
37-
the security on that request, like an OAUTH2 token, but you are free to use what
38-
so ever you may want.
37+
the security on that request, like an Authorization token, but you are free to
38+
use what so ever you may want.
3939

4040
Dummy example for the JWT token middle part, the payload:
4141

@@ -56,24 +56,24 @@ The custom payload claim in an Approov token is the one in the `pay` key:
5656
>
5757
> Please bear in mind that the custom payload claim is not meant to pass
5858
> application data to the API server, or any data that changes in every request,
59-
> but if this is an hard requirement for your use case, please contact us for
59+
> but if this is a hard requirement for your use case, please contact us for
6060
> further discussion.
6161
6262

6363
## SYSTEM CLOCK
6464

6565
In order to correctly check for the expiration times of the Approov tokens is
66-
very important that the Java server is synchronizing automatically the system
67-
clock over the network with an authoritative time source. In Linux this is usual
68-
done with a NTP server.
66+
important that the system clock for the Java server is synchronized
67+
automatically over the network with an authoritative time source. In Linux this
68+
is usual done with an NTP server.
6969

7070

7171
## REQUIREMENTS
7272

7373
We will use Java `11.0.3` with the Spring Boot `2.1.3.RELEASE`, and Gradle
7474
`5.2.1` to compile, build and run this demo.
7575

76-
Docker is required for the ones wanting to use the Java docker stack provided
76+
Docker is only required for developers wanting to use the Java docker stack provided
7777
by the [stack](./stack) bash script, that is a wrapper around docker commands.
7878

7979
```bash
@@ -110,15 +110,15 @@ the API, but feel free to use any other tool of your preference.
110110
We recommend the use of the included Docker stack to play with this Approov
111111
integration.
112112

113-
For details in how to use it you need to follow the setup instructions in the
113+
For details on how to use it you need to follow the setup instructions in the
114114
[Approov Shapes Demo Server](./docs/approov-shapes-demo-server.md#development-environment)
115115
walk-through, but feel free to use your local environment to play with this
116116
Approov integration.
117117

118118

119119
## THE POSTMAN COLLECTION
120120

121-
Import this [Postman collection](https://gitlab.com/snippets/1799104/raw) that
121+
Import this [Postman collection](https://gitlab.com/snippets/1852520/raw) that
122122
contains all the API endpoints for the Approov Shapes Demo Server and we
123123
strongly recommend you to follow
124124
[this demo walk-through](./docs/approov-shapes-demo-server.md) after finishing
@@ -127,8 +127,8 @@ the Approov integration that we are about to start.
127127
The Approov tokens used in the headers of this Postman collection where
128128
generated by [this helper script](./bin/generate-token) and
129129
they cover all necessary scenarios, but feel free to use the script to generate
130-
some more valid and invalid tokens, with different expire times and custom
131-
payload claims. Some examples of using it can be found [here](./docs/approov-shapes-demo-server.md#approov-tokens-generation).
130+
some more valid and invalid tokens, with different expiration times and custom
131+
payload claims. Some examples of using it can be found [here](./docs/approov-shapes-demo-server.md#Approov-Tokens-generation).
132132

133133

134134
## DEPENDENCIES
@@ -153,6 +153,7 @@ We will learn how to integrate Approov in a skeleton generated with Spring Boot,
153153
where we added 3 endpoints:
154154

155155
* `/` - Not protected with Approov.
156+
* `/hello` - Not protected with Approov.
156157
* `/shapes` - Approov protected.
157158
* `/forms` - Approov protected, and with a check for the Approov custom payload claim.
158159

@@ -174,7 +175,7 @@ When implementing Approov is required to always check if the signature and
174175
expiration time of the Approov token is valid, and optionally to check if the
175176
custom payload claim matches the one in the header.
176177

177-
For the required and optional checks we always need to configure the Spring
178+
For both the required and optional checks we always need to configure the Spring
178179
framework security with the `ApproovAuthenticationProvider(approovConfig)`.
179180

180181
Now we need to configure what endpoints will perform the required and optional
@@ -193,9 +194,9 @@ tell if the custom payload claim is to be checked or not, and this is done with
193194
the boolean flag `checkCustomPayload`.
194195

195196
In order to be able to have endpoints that perform only the required checks in
196-
the Approov token, while at same time having others endpoints where both the
197+
the Approov token, while at the same time having others endpoints where both the
197198
required and optional checks must take place, we need to configure the Spring
198-
framework security context with static sub classes of the main `WebSecurityConfig`
199+
framework security context with static subclasses of the main `WebSecurityConfig`
199200
class, and this sub classes also need to implement the abstract
200201
`WebSecurityConfigurerAdapter` class. This subclasses will be annotated with a
201202
configuration order `@Order(n)`, thus their configuration order is important. So
@@ -265,7 +266,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
265266
CorsConfiguration configuration = new CorsConfiguration();
266267
configuration.setAllowedOrigins(Arrays.asList("http://localhost:5000"));
267268
configuration.setAllowedMethods(Arrays.asList("GET"));
268-
configuration.addAllowedHeader("Approov-Token");
269269
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
270270
source.registerCorsConfiguration("/**", configuration);
271271
return source;
@@ -311,8 +311,8 @@ custom payload claim in the Approov token.
311311
As already mentioned we will need to add to the `WebSecurityConfig` a subclass
312312
for the endpoints we want to secure with only the required checks for an Approov
313313
token, another for the endpoints secured with the required and optional checks
314-
for an Aprroov token, and finally a subclass for the endpoints that do not
315-
require authentication.
314+
for an Aprroov token, and finally a subclass for endpoints that do not require
315+
authentication.
316316

317317
So let's prepare the `WebSecurityConfig` with only a subclass that maintains the
318318
access to all endpoints without any authentication.
@@ -348,7 +348,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
348348
CorsConfiguration configuration = new CorsConfiguration();
349349
configuration.setAllowedOrigins(Arrays.asList("http://localhost:5000"));
350350
configuration.setAllowedMethods(Arrays.asList("GET"));
351-
configuration.addAllowedHeader("approov-token");
352351
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
353352
source.registerCorsConfiguration("/**", configuration);
354353
return source;
@@ -390,6 +389,39 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
390389
}
391390
```
392391

392+
#### CORS Configuration
393+
394+
In order to integrate Approov we will need to use an `Approov-Token`, thus we
395+
need to allow it in the CORS configuration.
396+
397+
If our Approov integration also uses the Approov custom payload check, then we
398+
also need to allow the header from where we want to retrieve the value we bind
399+
to the Approov token custom payload in the mobile app, that in this demo is the
400+
`Authorization` header.
401+
402+
So we add to the CORS configuration this 2 new lines:
403+
404+
```java
405+
configuration.addAllowedHeader("Authorization");
406+
configuration.addAllowedHeader("Approov-Token");
407+
```
408+
409+
That will give us this new CORS configuration:
410+
411+
```java
412+
@Bean
413+
CorsConfigurationSource corsConfigurationSource() {
414+
CorsConfiguration configuration = new CorsConfiguration();
415+
configuration.setAllowedOrigins(Arrays.asList("http://localhost:5000"));
416+
configuration.setAllowedMethods(Arrays.asList("GET"));
417+
configuration.addAllowedHeader("Authorization");
418+
configuration.addAllowedHeader("Approov-Token");
419+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
420+
source.registerCorsConfiguration("/**", configuration);
421+
return source;
422+
}
423+
```
424+
393425
#### Protecting the `/shapes` endpoint
394426

395427
To protect the `/shapes` endpoint we will add the subclass `ApproovWebSecurityConfig`:
@@ -555,7 +587,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
555587
CorsConfiguration configuration = new CorsConfiguration();
556588
configuration.setAllowedOrigins(Arrays.asList("http://localhost:5000"));
557589
configuration.setAllowedMethods(Arrays.asList("GET"));
558-
configuration.addAllowedHeader("approov-token");
590+
configuration.addAllowedHeader("Authorization");
591+
configuration.addAllowedHeader("Approov-Token");
559592
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
560593
source.registerCorsConfiguration("/**", configuration);
561594
return source;
@@ -617,12 +650,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
617650
.securityContext()
618651
.securityContextRepository(new ApproovSecurityContextRepository(approovConfig, true))
619652
.and()
620-
.exceptionHandling()
621-
.authenticationEntryPoint(new ApproovAuthenticationEntryPoint())
653+
.exceptionHandling()
654+
.authenticationEntryPoint(new ApproovAuthenticationEntryPoint())
622655
.and()
623-
.antMatcher("/forms")
624-
.authorizeRequests()
625-
.antMatchers(HttpMethod.GET, "/forms").authenticated();
656+
.antMatcher("/forms")
657+
.authorizeRequests()
658+
.antMatchers(HttpMethod.GET, "/forms").authenticated();
626659
}
627660
}
628661

@@ -658,8 +691,7 @@ If we compare the initial implementation with the final result for the class
658691

659692
```java
660693
--- untitled (Previous)
661-
+--- untitled (Previous)
662-
+++ untitled (Current)
694+
+++ /home/sublime/workspace/java/spring/src/main/java/com/criticalblue/approov/jwt/WebSecurityConfig.java
663695
@@ -1,6 +1,7 @@
664696
package com.criticalblue.approov.jwt;
665697

@@ -668,16 +700,16 @@ If we compare the initial implementation with the final result for the class
668700
import org.springframework.security.config.annotation.web.builders.WebSecurity;
669701
import org.springframework.security.config.http.SessionCreationPolicy;
670702
import org.springframework.web.cors.CorsConfiguration;
671-
@@ -25,7 +26,7 @@
703+
@@ -25,6 +26,8 @@
672704
CorsConfiguration configuration = new CorsConfiguration();
673705
configuration.setAllowedOrigins(Arrays.asList("http://localhost:5000"));
674706
configuration.setAllowedMethods(Arrays.asList("GET"));
675-
- configuration.addAllowedHeader("Approov-Token");
676-
+ configuration.addAllowedHeader("approov-token");
707+
+ configuration.addAllowedHeader("Authorization");
708+
+ configuration.addAllowedHeader("Approov-Token");
677709
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
678710
source.registerCorsConfiguration("/**", configuration);
679711
return source;
680-
@@ -36,27 +37,86 @@
712+
@@ -35,27 +38,86 @@
681713
web.ignoring().antMatchers("/error");
682714
}
683715
@@ -727,7 +759,8 @@ If we compare the initial implementation with the final result for the class
727759
+ .authorizeRequests()
728760
+ .antMatchers(HttpMethod.GET, "/shapes").authenticated();
729761
+ }
730-
+ }
762+
}
763+
-}
731764
+
732765
+ @Configuration
733766
+ @Order(2)
@@ -750,12 +783,12 @@ If we compare the initial implementation with the final result for the class
750783
+ .securityContext()
751784
+ .securityContextRepository(new ApproovSecurityContextRepository(approovConfig, true))
752785
+ .and()
753-
+ .exceptionHandling()
754-
+ .authenticationEntryPoint(new ApproovAuthenticationEntryPoint())
786+
+ .exceptionHandling()
787+
+ .authenticationEntryPoint(new ApproovAuthenticationEntryPoint())
755788
+ .and()
756-
+ .antMatcher("/forms")
757-
+ .authorizeRequests()
758-
+ .antMatchers(HttpMethod.GET, "/forms").authenticated();
789+
+ .antMatcher("/forms")
790+
+ .authorizeRequests()
791+
+ .antMatchers(HttpMethod.GET, "/forms").authenticated();
759792
+ }
760793
+ }
761794
+
@@ -780,8 +813,8 @@ If we compare the initial implementation with the final result for the class
780813
+ .authorizeRequests()
781814
+ .antMatchers(HttpMethod.GET, "/**").permitAll();
782815
+ }
783-
}
784-
}
816+
+ }
817+
+}
785818
```
786819
787820

docs/approov-shapes-demo-server.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,15 @@ Feel free to try all the options...
232232
To start the server we want to issue the command:
233233

234234
```bash
235-
./gradlew bootRun
235+
source .env && ./gradlew bootRun
236236
```
237237

238-
After the Java server is up and running it will be available at http://localhost:5000.
238+
> **NOTE**:
239+
>
240+
> If you decide to run the Java server from your IDE, then you need to set all
241+
> the environments variables in the `.env` file in your IDE.
239242
243+
After the Java server is up and running it will be available at http://localhost:5000.
240244

241245
### Endpoint Not Protected by Approov
242246

src/main/java/com/criticalblue/approov/jwt/WebSecurityConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ CorsConfigurationSource corsConfigurationSource() {
2626
CorsConfiguration configuration = new CorsConfiguration();
2727
configuration.setAllowedOrigins(Arrays.asList("http://localhost:5000"));
2828
configuration.setAllowedMethods(Arrays.asList("GET"));
29+
configuration.addAllowedHeader("Authorization");
2930
configuration.addAllowedHeader("Approov-Token");
3031
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
3132
source.registerCorsConfiguration("/**", configuration);

src/main/java/com/criticalblue/approov/jwt/authentication/ApproovAuthenticationProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class ApproovAuthenticationProvider implements AuthenticationProvider {
2424
* @param approovConfig Extracted from the .env file in the root of the package.
2525
*/
2626
public ApproovAuthenticationProvider(ApproovConfig approovConfig) {
27-
this.approovSecret = Base64.decodeBase64(approovConfig.getBase64Secret());
27+
this.approovSecret = Base64.decodeBase64(approovConfig.getApproovBase64Secret());
2828
}
2929

3030
@Override

src/main/java/com/criticalblue/approov/jwt/authentication/ApproovConfig.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ final public class ApproovConfig {
99

1010
private String approovHeaderName = "Approov-Token";
1111

12-
private final String approovClaimHeaderName;
12+
private String approovBase64Secret;
1313

14-
private String base64Secret = System.getenv("APPROOV_BASE64_SECRET");
14+
private final String approovClaimHeaderName;
1515

1616
private boolean toAbortRequestOnInvalidToken;
1717

@@ -21,6 +21,7 @@ final public class ApproovConfig {
2121
* Constructs the Approov Config singleton with values retrieved from the .env file in the root of the project.
2222
*/
2323
private ApproovConfig() {
24+
this.approovBase64Secret = retrieveApproovBase64Secret();
2425
this.approovClaimHeaderName = retrieveStringValueFromEnv("APPROOV_CLAIM_HEADER_NAME", "Authorization");
2526
this.toAbortRequestOnInvalidToken = retrieveBooleanValueFromEnv("APPROOV_ABORT_REQUEST_ON_INVALID_TOKEN", true);
2627
this.toAbortRequestOnInvalidCustomPayloadClaim = retrieveBooleanValueFromEnv("APPROOV_ABORT_REQUEST_ON_INVALID_CUSTOM_PAYLOAD_CLAIM", true);
@@ -38,8 +39,8 @@ String getApproovClaimHeaderName() {
3839
return approovClaimHeaderName;
3940
}
4041

41-
String getBase64Secret() {
42-
return base64Secret;
42+
String getApproovBase64Secret() {
43+
return approovBase64Secret;
4344
}
4445

4546
boolean isToAbortRequestOnInvalidToken() {
@@ -50,6 +51,16 @@ boolean isToAbortRequestOnInvalidCustomPayloadClaim() {
5051
return toAbortRequestOnInvalidCustomPayloadClaim;
5152
}
5253

54+
private String retrieveApproovBase64Secret() {
55+
approovBase64Secret = System.getenv("APPROOV_BASE64_SECRET");
56+
57+
if (approovBase64Secret == null) {
58+
throw new ApproovAuthenticationException("Cannot retrieve APPROOV_BASE64_SECRET from the environment.");
59+
}
60+
61+
return approovBase64Secret;
62+
}
63+
5364
private String retrieveStringValueFromEnv(String key, String defaultValue) {
5465

5566
String value = System.getenv(key);

0 commit comments

Comments
 (0)