Skip to content

Commit 4b02d73

Browse files
NFC-47 Web eID for Mobile support for web-eid example
Signed-off-by: Sander Kondratjev <[email protected]>
1 parent b18fa03 commit 4b02d73

38 files changed

+1151
-438
lines changed

README.md

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Implement the session-backed challenge nonce store as follows:
4848
import org.springframework.beans.factory.ObjectFactory;
4949
import eu.webeid.security.challenge.ChallengeNonce;
5050
import eu.webeid.security.challenge.ChallengeNonceStore;
51-
import javax.servlet.http.HttpSession;
51+
import jakarta.servlet.http.HttpSession;
5252

5353
public class SessionBackedChallengeNonceStore implements ChallengeNonceStore {
5454

@@ -134,36 +134,103 @@ import eu.webeid.security.validator.AuthTokenValidatorBuilder;
134134
...
135135
```
136136

137-
## 6. Add a REST endpoint for issuing challenge nonces
137+
## 6. Add a filter for issuing challenge nonces
138138

139-
A REST endpoint that issues challenge nonces is required for authentication. The endpoint must support `GET` requests.
139+
Request Filters that issue challenge nonces for regular Web eID and Web eID for Mobile authentication flows are required for authentication.
140+
The filters must support POST requests.
140141

141-
In the following example, we are using the [Spring RESTful Web Services framework](https://spring.io/guides/gs/rest-service/) to implement the endpoint, see also the full implementation [here](example/blob/main/src/main/java/eu/webeid/example/web/rest/ChallengeController.java).
142+
The `WebEidChallengeNonceFilter` handles `/auth/challenge` requests and issues a new nonce for regular Web eID authentication flow.
143+
See the full implementation [here](example/src/main/java/eu/webeid/example/security/WebEidChallengeNonceFilter.java).
142144

143145
```java
144-
import org.springframework.web.bind.annotation.GetMapping;
145-
import org.springframework.web.bind.annotation.RequestMapping;
146-
import org.springframework.web.bind.annotation.RestController;
147-
import eu.webeid.security.challenge.ChallengeNonceGenerator;
148-
...
146+
public final class WebEidChallengeNonceFilter extends OncePerRequestFilter {
147+
private static final ObjectWriter OBJECT_WRITER = new ObjectMapper().writer();
148+
private final RequestMatcher requestMatcher;
149+
private final ChallengeNonceGenerator nonceGenerator;
150+
151+
public WebEidChallengeNonceFilter(String path, ChallengeNonceGenerator nonceGenerator) {
152+
this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, path);
153+
this.nonceGenerator = nonceGenerator;
154+
}
155+
156+
@Override
157+
protected void doFilterInternal(
158+
@NonNull HttpServletRequest request,
159+
@NonNull HttpServletResponse response,
160+
@NonNull FilterChain chain
161+
) throws ServletException, IOException {
162+
if (!requestMatcher.matches(request)) {
163+
chain.doFilter(request, response);
164+
return;
165+
}
166+
167+
var dto = new ChallengeDTO(nonceGenerator.generateAndStoreNonce().getBase64EncodedNonce());
168+
169+
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
170+
OBJECT_WRITER.writeValue(response.getWriter(), dto);
171+
}
172+
173+
public record ChallengeDTO(String nonce) {}
174+
}
175+
```
149176

150-
@RestController
151-
@RequestMapping("auth")
152-
public class ChallengeController {
177+
Similarly, the `WebEidMobileAuthInitFilter` handles `/auth/mobile/init` requests for Web eID for Mobile authentication flow by generating a challenge nonce and returning a deep link URI. This deep link contains both the challenge nonce and a login URI for the mobile authentication flow.
178+
See the full implementation [here](example/src/main/java/eu/webeid/example/security/WebEidMobileAuthInitFilter.java).
153179

154-
@Autowired // for brevity, prefer constructor dependency injection
155-
private ChallengeNonceGenerator nonceGenerator;
180+
```java
181+
public final class WebEidMobileAuthInitFilter extends OncePerRequestFilter {
182+
private static final ObjectWriter OBJECT_WRITER = new ObjectMapper().writer();
183+
private final RequestMatcher requestMatcher;
184+
private final ChallengeNonceGenerator nonceGenerator;
185+
private final String loginPath;
186+
187+
public WebEidMobileAuthInitFilter(String path, String loginPath, ChallengeNonceGenerator nonceGenerator) {
188+
this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, path);
189+
this.nonceGenerator = nonceGenerator;
190+
this.loginPath = loginPath;
191+
}
156192

157-
@GetMapping("challenge")
158-
public ChallengeDTO challenge() {
159-
// a simple DTO with a single 'nonce' field
160-
final ChallengeDTO challenge = new ChallengeDTO();
161-
challenge.setNonce(nonceGenerator.generateAndStoreNonce().getBase64EncodedNonce());
162-
return challenge;
193+
@Override
194+
protected void doFilterInternal(
195+
@NonNull HttpServletRequest request,
196+
@NonNull HttpServletResponse response,
197+
@NonNull FilterChain chain
198+
) throws IOException, ServletException {
199+
if (!requestMatcher.matches(request)) {
200+
chain.doFilter(request, response);
201+
return;
202+
}
203+
204+
var challenge = nonceGenerator.generateAndStoreNonce();
205+
206+
String loginUri = ServletUriComponentsBuilder.fromCurrentContextPath()
207+
.path(loginPath).build().toUriString();
208+
209+
String payloadJson = OBJECT_WRITER.writeValueAsString(
210+
new AuthPayload(challenge.getBase64EncodedNonce(), loginUri)
211+
);
212+
String encoded = Base64.getEncoder().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
213+
String eidAuthUri = "web-eid-mobile://auth#" + encoded;
214+
215+
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
216+
OBJECT_WRITER.writeValue(response.getWriter(), new AuthUri(eidAuthUri));
163217
}
218+
219+
record AuthPayload(String challenge, @JsonProperty("login_uri") String loginUri) {}
220+
record AuthUri(@JsonProperty("auth_uri") String authUri) {}
164221
}
165222
```
166223

224+
Both filters are registered in the Spring Security filter chain in ApplicationConfiguration
225+
See the full implementation [here](example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java):
226+
```java
227+
http
228+
.addFilterBefore(new WebEidMobileAuthInitFilter("/auth/mobile/init", "/auth/mobile/login", challengeNonceGenerator),
229+
UsernamePasswordAuthenticationFilter.class)
230+
.addFilterBefore(new WebEidChallengeNonceFilter("/auth/challenge", challengeNonceGenerator),
231+
UsernamePasswordAuthenticationFilter.class);
232+
```
233+
167234
Also, see general guidelines for implementing secure authentication services [here](https://github.com/SK-EID/smart-id-documentation/wiki/Secure-Implementation-Guide).
168235

169236
## 7. Implement authentication
@@ -172,11 +239,11 @@ Authentication consists of calling the `validate()` method of the authentication
172239

173240
When using [Spring Security](https://spring.io/guides/topicals/spring-security-architecture) with standard cookie-based authentication,
174241

175-
- implement a custom authentication provider that uses the authentication token validator for authentication as shown [here](example/blob/main/src/main/java/eu/webeid/example/security/AuthTokenDTOAuthenticationProvider.java),
242+
- implement a custom authentication provider that uses the authentication token validator for authentication as shown [here](example/blob/main/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java),
176243
- implement an AJAX authentication processing filter that extracts the authentication token and passes it to the authentication manager as shown [here](example/blob/main/src/main/java/eu/webeid/example/security/WebEidAjaxLoginProcessingFilter.java),
177244
- configure the authentication provider and authentication processing filter in the application configuration as shown [here](example/blob/main/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java).
178245

179-
The gist of the validation is [in the `authenticate()` method](example/blob/main/src/main/java/eu/webeid/example/security/AuthTokenDTOAuthenticationProvider.java#L74-L76) of the authentication provider:
246+
The gist of the validation is [in the `authenticate()` method](example/blob/main/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java#L74-L76) of the authentication provider:
180247

181248
```java
182249
try {

example/README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ This repository contains the code of a minimal Spring Boot web application that
100100
- Spring Security,
101101
- the Web eID authentication token validation library [_web-eid-authtoken-validation-java_](https://github.com/web-eid/web-eid-authtoken-validation-java),
102102
- the Web eID JavaScript library [_web-eid.js_](https://github.com/web-eid/web-eid.js),
103-
- the digital signing library [_DigiDoc4j_](https://github.com/open-eid/digidoc4j).
103+
- the digital signing library [_DigiDoc4j_](https://github.com/open-eid/digidoc4j),
104+
- the Android application [_MOPP-Android_](https://github.com/open-eid/MOPP-Android/).
104105

105106
The project uses Maven for managing the dependencies and building the application. Maven project configuration file `pom.xml` is in the root of the project.
106107

@@ -113,11 +114,13 @@ The source code folder `src` contains the application source code and resources
113114
The `src/main/java/eu/webeid/example` directory contains the Spring Boot application Java class and the following subdirectories:
114115

115116
- `config`: Spring and HTTP security configuration, Web eID authentication token validation library configuration, trusted CA certificates loading etc,
116-
- `security`: Web eID authentication token validation library integration with Spring Security via an `AuthenticationProvider` and `AuthenticationProcessingFilter`,
117+
- `security`: Web eID authentication token validation library integration with Spring Security
118+
- `AuthenticationProvider` and `AuthenticationProcessingFilter` for handling Web eID authentication tokens,
119+
- `WebEidChallengeNonceFilter` for issuing the challenge nonce required by the authentication flow,
120+
- `WebEidMobileAuthInitFilter` for issuing the challenge nonce and generating the deep link with the authentication request, used to initiate the mobile authentication flow,
121+
- `WebEidAjaxLoginProcessingFilter` and `WebEidLoginPageGeneratingFilter` for handling login requests.
117122
- `service`: Web eID signing service implementation that uses DigiDoc4j, and DigiDoc4j runtime configuration,
118-
- `web`: Spring Web MVC controller for the welcome page and Spring Web REST controllers that provide endpoints
119-
- for getting the challenge nonce used by the authentication token validation library,
120-
- for digital signing.
123+
- `web`: Spring Web MVC controller for the welcome page and Spring Web REST controller that provides a digital signing endpoint.
121124

122125
The `src/resources` directory contains the resources used by the application:
123126

example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@
2222

2323
package eu.webeid.example.config;
2424

25-
import eu.webeid.example.security.AuthTokenDTOAuthenticationProvider;
2625
import eu.webeid.example.security.WebEidAjaxLoginProcessingFilter;
26+
import eu.webeid.example.security.WebEidAuthenticationProvider;
27+
import eu.webeid.example.security.WebEidChallengeNonceFilter;
28+
import eu.webeid.example.security.WebEidMobileAuthInitFilter;
29+
import eu.webeid.example.security.ui.WebEidLoginPageGeneratingFilter;
30+
import eu.webeid.security.challenge.ChallengeNonceGenerator;
2731
import org.springframework.context.annotation.Bean;
2832
import org.springframework.context.annotation.Configuration;
2933
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@@ -34,29 +38,36 @@
3438
import org.springframework.security.web.SecurityFilterChain;
3539
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
3640
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
37-
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
38-
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
41+
import org.thymeleaf.ITemplateEngine;
42+
import org.thymeleaf.web.servlet.JakartaServletWebApplication;
3943

4044
@Configuration
4145
@EnableWebSecurity
4246
@EnableMethodSecurity(securedEnabled = true)
43-
public class ApplicationConfiguration implements WebMvcConfigurer {
47+
public class ApplicationConfiguration {
4448

4549
@Bean
46-
public SecurityFilterChain filterChain(HttpSecurity http, AuthTokenDTOAuthenticationProvider authTokenDTOAuthenticationProvider, AuthenticationConfiguration authConfig) throws Exception {
50+
public SecurityFilterChain filterChain(
51+
HttpSecurity http,
52+
WebEidAuthenticationProvider webEidAuthenticationProvider,
53+
AuthenticationConfiguration authConfig,
54+
ChallengeNonceGenerator challengeNonceGenerator,
55+
ITemplateEngine templateEngine,
56+
JakartaServletWebApplication webApp
57+
) throws Exception {
4758
return http
48-
.authenticationProvider(authTokenDTOAuthenticationProvider)
49-
.addFilterBefore(new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager()),
50-
UsernamePasswordAuthenticationFilter.class)
51-
.logout(logout -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
52-
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
53-
.build();
59+
.authorizeHttpRequests(auth -> auth
60+
.requestMatchers("/css/**", "/files/**", "/img/**", "/js/**", "/scripts/**").permitAll()
61+
.requestMatchers("/").permitAll()
62+
.anyRequest().authenticated()
63+
)
64+
.authenticationProvider(webEidAuthenticationProvider)
65+
.addFilterBefore(new WebEidMobileAuthInitFilter("/auth/mobile/init", "/auth/mobile/login", challengeNonceGenerator), UsernamePasswordAuthenticationFilter.class)
66+
.addFilterBefore(new WebEidChallengeNonceFilter("/auth/challenge", challengeNonceGenerator), UsernamePasswordAuthenticationFilter.class)
67+
.addFilterBefore(new WebEidLoginPageGeneratingFilter("/auth/mobile/login", "/auth/login", templateEngine, webApp), UsernamePasswordAuthenticationFilter.class)
68+
.addFilterBefore(new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager()), UsernamePasswordAuthenticationFilter.class)
69+
.logout(l -> l.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
70+
.headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
71+
.build();
5472
}
55-
56-
@Override
57-
public void addViewControllers(ViewControllerRegistry registry) {
58-
registry.addViewController("/").setViewName("index");
59-
registry.addViewController("/welcome").setViewName("welcome");
60-
}
61-
6273
}

example/src/main/java/eu/webeid/example/config/SameSiteCookieConfiguration.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,17 @@
2626
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
2727
import org.springframework.context.annotation.Bean;
2828
import org.springframework.context.annotation.Configuration;
29-
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
3029

3130
@Configuration
32-
public class SameSiteCookieConfiguration implements WebMvcConfigurer {
31+
public class SameSiteCookieConfiguration {
3332

3433
@Bean
3534
public TomcatContextCustomizer configureSameSiteCookies() {
3635
return context -> {
3736
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
38-
cookieProcessor.setSameSiteCookies("strict");
37+
// Set to "strict" if Web eID for Mobile flow is not used - this would restrict sending back the
38+
// authentication response in the Web eID for Mobile flow.
39+
cookieProcessor.setSameSiteCookies("lax");
3940
context.setCookieProcessor(cookieProcessor);
4041
};
4142
}

example/src/main/java/eu/webeid/example/security/dto/AuthTokenDTO.java renamed to example/src/main/java/eu/webeid/example/config/ThymeleafWebAppConfiguration.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,18 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package eu.webeid.example.security.dto;
23+
package eu.webeid.example.config;
2424

25-
import com.fasterxml.jackson.annotation.JsonProperty;
26-
import eu.webeid.security.authtoken.WebEidAuthToken;
25+
import jakarta.servlet.ServletContext;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.thymeleaf.web.servlet.JakartaServletWebApplication;
2729

28-
public class AuthTokenDTO {
29-
@JsonProperty("auth-token")
30-
private WebEidAuthToken token;
30+
@Configuration
31+
public class ThymeleafWebAppConfiguration {
3132

32-
public WebEidAuthToken getToken() {
33-
return token;
34-
}
35-
36-
public void setToken(WebEidAuthToken token) {
37-
this.token = token;
33+
@Bean
34+
public JakartaServletWebApplication jakartaServletWebApplication(ServletContext servletContext) {
35+
return JakartaServletWebApplication.buildApplication(servletContext);
3836
}
3937
}

example/src/main/java/eu/webeid/example/config/ValidationConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
package eu.webeid.example.config;
2424

25+
import eu.webeid.example.security.SessionBackedChallengeNonceStore;
2526
import org.slf4j.Logger;
2627
import org.slf4j.LoggerFactory;
2728
import org.springframework.beans.factory.ObjectFactory;

example/src/main/java/eu/webeid/example/config/SessionBackedChallengeNonceStore.java renamed to example/src/main/java/eu/webeid/example/security/SessionBackedChallengeNonceStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package eu.webeid.example.config;
23+
package eu.webeid.example.security;
2424

2525
import org.springframework.beans.factory.ObjectFactory;
2626
import eu.webeid.security.challenge.ChallengeNonce;

0 commit comments

Comments
 (0)