Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/maven-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 11
java-version: 17

- name: Cache Maven packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-v11-${{ secrets.CACHE_VERSION }}-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2-v11-${{ secrets.CACHE_VERSION }}
key: ${{ runner.os }}-m2-v17-${{ secrets.CACHE_VERSION }}-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2-v17-${{ secrets.CACHE_VERSION }}

- name: Build
run: mvn --batch-mode compile
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Web eID only works over a HTTPS connection with a trusted HTTPS certificate.
You can either setup a reverse HTTPS proxy during development or, alternatively, configure
HTTPS support directly in the bundled web server. HTTPS configuration is described in more detail in section _[HTTPS support](#https-support)_ below.

You can use, for example, [_ngrok_](https://ngrok.com/) to get a reverse HTTPS proxy. Download _ngrok_ and run it in a terminal window by providing the protocol and Spring Boot application port arguments as follows:
You can use, for example, [_ngrok_](https://ngrok.com/) or [_localtunnel_](https://theboroer.github.io/localtunnel-www/) to get a reverse HTTPS proxy. Download _ngrok_ and run it in a terminal window by providing the protocol and Spring Boot application port arguments as follows:

ngrok http 8080

Expand All @@ -35,7 +35,7 @@ web-eid-auth-token:

### 3. Configure the trusted certificate authority certificates

The algorithm, which performs the validation of the Web eID authentication token, needs to know which intermediate certificate authorities (CA) are trusted to issue the eID authentication certificates. CA certificates are loaded either from `.cer` files in the profile-specific subdirectory of the [`certs`resource directory](src/main/resources/certs) or the [truststore file](src/main/resources/certs/prod/trusted_certificates.jks). By default, Estonian eID test CA certificates are included in the `dev` profile and production CA certificates in the `prod` profile.
The algorithm, which performs the validation of the Web eID authentication token, needs to know which intermediate certificate authorities (CA) are trusted to issue the eID authentication certificates. CA certificates are loaded either from `.cer` files in the profile-specific subdirectory of the [`certs` resource directory](src/main/resources/certs) or the [truststore file](src/main/resources/certs/prod/trusted_certificates.jks). By default, Estonian eID test CA certificates are included in the `dev` profile and production CA certificates in the `prod` profile.

In case you need to provide your own CA certificates, either add the `.cer` files to the `src/main/resources/certs/{dev,prod}` profile-specific directory or add the certificates to the truststore file.

Expand All @@ -49,7 +49,7 @@ You can specify the profile as a command-line argument to the Maven wrapper comm

### 5. Run the application

Spring Boot web applications can be run from the command-line. You need to have the Java Development Kit 8 installed for building the application package and running the application.
Spring Boot web applications can be run from the command-line. You need to have the Java Development Kit 17 installed for building the application package and running the application.

Build and run the application with the following command in a terminal window:

Expand Down
36 changes: 8 additions & 28 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<version>3.2.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.webeid.example</groupId>
<groupId>eu.webeid.example</groupId>
<artifactId>web-eid-springboot-example</artifactId>
<version>3.0.0-SNAPSHOT</version>
<name>web-eid-springboot-example</name>
<description>Example Spring Boot project that demonstrates how to use Web eID for authentication and digital
<description>Example Spring Boot application that demonstrates how to use Web eID for authentication and digital
signing
</description>

<properties>
<java.version>11</java.version>
<maven-surefire-plugin.version>2.22.1</maven-surefire-plugin.version>
<webeid.version>3.0.0</webeid.version>
<digidoc4j.version>5.2.0</digidoc4j.version>
<java.version>17</java.version>
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
<webeid.version>3.0.1</webeid.version>
<digidoc4j.version>5.3.0</digidoc4j.version>
<jmockit.version>1.44</jmockit.version>
</properties>

Expand All @@ -31,20 +31,12 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>

<dependency>
<groupId>org.digidoc4j</groupId>
Expand All @@ -57,22 +49,10 @@
<version>${webeid.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,53 +24,36 @@

import eu.webeid.example.security.AuthTokenDTOAuthenticationProvider;
import eu.webeid.example.security.WebEidAjaxLoginProcessingFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ApplicationConfiguration extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
final AuthTokenDTOAuthenticationProvider authTokenDTOAuthenticationProvider;

public ApplicationConfiguration(AuthTokenDTOAuthenticationProvider authTokenDTOAuthenticationProvider) {
this.authTokenDTOAuthenticationProvider = authTokenDTOAuthenticationProvider;
}

@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(authTokenDTOAuthenticationProvider);
@EnableMethodSecurity(securedEnabled = true)
public class ApplicationConfiguration implements WebMvcConfigurer {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthTokenDTOAuthenticationProvider authTokenDTOAuthenticationProvider, AuthenticationConfiguration authConfig) throws Exception {
return http
.authenticationProvider(authTokenDTOAuthenticationProvider)
.addFilterBefore(new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager()),
UsernamePasswordAuthenticationFilter.class)
.logout(logout -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
.build();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterBefore(
new WebEidAjaxLoginProcessingFilter("/auth/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/challenge", "/auth/login", "/")
.permitAll()
.antMatchers("/welcome")
.authenticated()
.and()
.logout()
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
.and()
.headers()
.frameOptions().sameOrigin();
// @formatter:on
}

public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/welcome").setViewName("welcome");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import eu.webeid.security.challenge.ChallengeNonce;
import eu.webeid.security.challenge.ChallengeNonceStore;

import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSession;

public class SessionBackedChallengeNonceStore implements ChallengeNonceStore {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import eu.webeid.security.validator.AuthTokenValidator;
import eu.webeid.security.validator.AuthTokenValidatorBuilder;

import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
package eu.webeid.example.security;

import eu.webeid.example.security.dto.AuthTokenDTO;
import eu.webeid.security.authtoken.WebEidAuthToken;
import eu.webeid.security.challenge.ChallengeNonceStore;
import eu.webeid.security.exceptions.AuthTokenException;
import eu.webeid.security.validator.AuthTokenValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
Expand All @@ -34,15 +37,9 @@
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Component;
import eu.webeid.security.authtoken.WebEidAuthToken;
import eu.webeid.security.challenge.ChallengeNonceStore;
import eu.webeid.security.exceptions.AuthTokenException;
import eu.webeid.security.validator.AuthTokenValidator;

import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

Expand All @@ -56,10 +53,13 @@ public class AuthTokenDTOAuthenticationProvider implements AuthenticationProvide

private static final Logger LOG = LoggerFactory.getLogger(AuthTokenDTOAuthenticationProvider.class);

@Autowired
private AuthTokenValidator tokenValidator;
@Autowired
private ChallengeNonceStore challengeNonceStore;
private final AuthTokenValidator tokenValidator;
private final ChallengeNonceStore challengeNonceStore;

public AuthTokenDTOAuthenticationProvider(AuthTokenValidator tokenValidator, ChallengeNonceStore challengeNonceStore) {
this.tokenValidator = tokenValidator;
this.challengeNonceStore = challengeNonceStore;
}

@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,50 @@
package eu.webeid.example.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.databind.ObjectReader;
import eu.webeid.example.security.ajax.AjaxAuthenticationFailureHandler;
import eu.webeid.example.security.ajax.AjaxAuthenticationSuccessHandler;
import eu.webeid.example.security.dto.AuthTokenDTO;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;

import java.io.IOException;

public class WebEidAjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger LOG = LoggerFactory.getLogger(WebEidAjaxLoginProcessingFilter.class);
private final ObjectReader OBJECT_READER = new ObjectMapper().readerFor(AuthTokenDTO.class);
private final SecurityContextRepository securityContextRepository;

public WebEidAjaxLoginProcessingFilter(
String defaultFilterProcessesUrl,
AuthenticationManager authenticationManager
String defaultFilterProcessesUrl,
AuthenticationManager authenticationManager
) {
super(defaultFilterProcessesUrl);
this.setAuthenticationManager(authenticationManager);
this.setAuthenticationSuccessHandler(new AjaxAuthenticationSuccessHandler());
this.setAuthenticationFailureHandler(new AjaxAuthenticationFailureHandler());
setSessionAuthenticationStrategy(new SessionFixationProtectionStrategy());
this.securityContextRepository = new HttpSessionSecurityContextRepository();
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException {
throws AuthenticationException, IOException {
if (!HttpMethod.POST.name().equals(request.getMethod())) {
LOG.warn("HttpMethod not supported: {}", request.getMethod());
throw new AuthenticationServiceException("HttpMethod not supported: " + request.getMethod());
Expand All @@ -69,11 +78,16 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
}

LOG.info("attemptAuthentication(): Reading request body");
final ObjectMapper objectMapper = new ObjectMapper();
final AuthTokenDTO authTokenDTO = objectMapper.readValue(request.getReader(), AuthTokenDTO.class);
final AuthTokenDTO authTokenDTO = OBJECT_READER.readValue(request.getReader());
LOG.info("attemptAuthentication(): Creating token");
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(null, authTokenDTO);
LOG.info("attemptAuthentication(): Calling authentication manager");
return getAuthenticationManager().authenticate(token);
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
}
}
11 changes: 11 additions & 0 deletions src/main/java/eu/webeid/example/security/WebEidAuthentication.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,15 @@ private static String getPrincipalNameFromCertificate(X509Certificate userCertif
}
}

@Override
public boolean equals(Object o) {
if (!super.equals(o)) return false;
WebEidAuthentication that = (WebEidAuthentication) o;
return Objects.equals(idCode, that.idCode);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), idCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;

public class AjaxAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
Expand Down
Loading