Skip to content

Commit c2c8aa6

Browse files
NFC-47 Update requestMatchers to match actual static resource folders. Remove unnecessary permitAll matchers for filter-handled auth endpoints. Unify filter registration. Remove cookieFlagsInitializer. Format isMobileDevice block for readability. Rename matcher to requestMatcher in filters for consistency and clarity. Remove unnecessary WebAuthenticationDetails from token in login filter. Remove redundant ChallengeNonceStore from WebEidMobileAuthInitFilter. Fix tests. Use signing cert for ObjectMother.
1 parent 7c1f2a5 commit c2c8aa6

File tree

10 files changed

+52
-112
lines changed

10 files changed

+52
-112
lines changed

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

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,40 +31,37 @@
3131
import eu.webeid.security.challenge.ChallengeNonceStore;
3232
import org.springframework.context.annotation.Bean;
3333
import org.springframework.context.annotation.Configuration;
34-
import org.springframework.http.HttpMethod;
3534
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
3635
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3736
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
3837
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
3938
import org.springframework.security.web.SecurityFilterChain;
40-
import org.springframework.security.web.access.intercept.AuthorizationFilter;
4139
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
4240
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
43-
import org.springframework.security.web.csrf.CsrfFilter;
4441

4542
@Configuration
4643
@EnableWebSecurity
4744
public class ApplicationConfiguration {
4845

4946
@Bean
50-
public SecurityFilterChain filterChain(HttpSecurity http, AuthTokenDTOAuthenticationProvider authTokenDTOAuthenticationProvider, AuthenticationConfiguration authConfig, ChallengeNonceGenerator challengeNonceGenerator, ChallengeNonceStore challengeNonceStore) throws Exception {
51-
52-
var filter = new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager());
53-
47+
public SecurityFilterChain filterChain(
48+
HttpSecurity http,
49+
AuthTokenDTOAuthenticationProvider authTokenDTOAuthenticationProvider,
50+
AuthenticationConfiguration authConfig,
51+
ChallengeNonceGenerator challengeNonceGenerator
52+
) throws Exception {
5453
return http
5554
.authorizeHttpRequests(auth -> auth
56-
.requestMatchers("/css/**", "/js/**", "/images/**", "/webjars/**", "/favicon.*", "/icon-*").permitAll()
55+
.requestMatchers("/css/**", "/files/**", "/img/**", "/js/**", "/scripts/**").permitAll()
5756
.requestMatchers("/", "/error").permitAll()
58-
.requestMatchers(HttpMethod.GET, "/auth/eid/login").permitAll()
59-
.requestMatchers("/auth/login").permitAll()
6057
.requestMatchers("/welcome").hasRole("USER")
6158
.anyRequest().authenticated()
6259
)
6360
.authenticationProvider(authTokenDTOAuthenticationProvider)
64-
.addFilterBefore(new WebEidLoginPageGeneratingFilter(), UsernamePasswordAuthenticationFilter.class)
65-
.addFilterBefore(new WebEidChallengeNonceFilter(challengeNonceGenerator), AuthorizationFilter.class)
66-
.addFilterAfter(new WebEidMobileAuthInitFilter(challengeNonceGenerator, challengeNonceStore), CsrfFilter.class)
67-
.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
61+
.addFilterBefore(new WebEidMobileAuthInitFilter("/auth/mobile/auth/init", challengeNonceGenerator), UsernamePasswordAuthenticationFilter.class)
62+
.addFilterBefore(new WebEidChallengeNonceFilter("/auth/challenge", challengeNonceGenerator), UsernamePasswordAuthenticationFilter.class)
63+
.addFilterBefore(new WebEidLoginPageGeneratingFilter("/auth/mobile/login"), UsernamePasswordAuthenticationFilter.class)
64+
.addFilterBefore(new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager()), UsernamePasswordAuthenticationFilter.class)
6865
.headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
6966
.logout(l -> l.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
7067
.build();

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@
2424

2525
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
2626
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
27-
import org.springframework.boot.web.servlet.ServletContextInitializer;
2827
import org.springframework.context.annotation.Bean;
2928
import org.springframework.context.annotation.Configuration;
30-
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
3129

3230
@Configuration
33-
public class SameSiteCookieConfiguration implements WebMvcConfigurer {
31+
public class SameSiteCookieConfiguration {
3432

3533
@Bean
3634
public TomcatContextCustomizer configureSameSiteCookies() {
@@ -40,12 +38,4 @@ public TomcatContextCustomizer configureSameSiteCookies() {
4038
context.setCookieProcessor(cookieProcessor);
4139
};
4240
}
43-
44-
@Bean
45-
public ServletContextInitializer cookieFlagsInitializer() {
46-
return servletContext -> {
47-
servletContext.getSessionCookieConfig().setSecure(true);
48-
servletContext.getSessionCookieConfig().setHttpOnly(true);
49-
};
50-
}
5141
}

example/src/main/java/eu/webeid/example/security/WebEidAjaxLoginProcessingFilter.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import org.springframework.security.core.AuthenticationException;
4141
import org.springframework.security.core.context.SecurityContextHolder;
4242
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
43-
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
4443
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
4544
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
4645
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
@@ -55,8 +54,8 @@ public class WebEidAjaxLoginProcessingFilter extends AbstractAuthenticationProce
5554
private final SecurityContextRepository securityContextRepository;
5655

5756
public WebEidAjaxLoginProcessingFilter(
58-
String defaultFilterProcessesUrl,
59-
AuthenticationManager authenticationManager
57+
String defaultFilterProcessesUrl,
58+
AuthenticationManager authenticationManager
6059
) {
6160
super(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, defaultFilterProcessesUrl));
6261
this.setAuthenticationManager(authenticationManager);
@@ -68,7 +67,7 @@ public WebEidAjaxLoginProcessingFilter(
6867

6968
@Override
7069
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
71-
throws AuthenticationException, IOException {
70+
throws AuthenticationException, IOException {
7271
final String contentType = request.getHeader("Content-type");
7372
if (contentType == null || !contentType.startsWith("application/json")) {
7473
LOG.warn("Content type not supported: {}", contentType);
@@ -80,7 +79,6 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
8079
LOG.info("attemptAuthentication(): Creating token");
8180
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(null, authTokenDTO);
8281
LOG.info("attemptAuthentication(): Calling authentication manager");
83-
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
8482
return getAuthenticationManager().authenticate(token);
8583
}
8684

example/src/main/java/eu/webeid/example/security/WebEidChallengeNonceFilter.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,21 @@
1919
import java.io.IOException;
2020

2121
public final class WebEidChallengeNonceFilter extends OncePerRequestFilter {
22-
2322
private static final ObjectWriter JSON = new ObjectMapper().writer();
24-
private final RequestMatcher matcher =
25-
PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, "/auth/challenge");
23+
private final RequestMatcher requestMatcher;
2624

2725
private final ChallengeNonceGenerator nonceGenerator;
2826

29-
public WebEidChallengeNonceFilter(ChallengeNonceGenerator nonceGenerator) {
27+
public WebEidChallengeNonceFilter(String path, ChallengeNonceGenerator nonceGenerator) {
28+
this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, path);
3029
this.nonceGenerator = nonceGenerator;
3130
}
3231

3332
@Override
3433
protected void doFilterInternal(@NonNull HttpServletRequest request,
3534
@NonNull HttpServletResponse response,
3635
@NonNull FilterChain chain) throws ServletException, IOException {
37-
if (!matcher.matches(request)) {
36+
if (!requestMatcher.matches(request)) {
3837
chain.doFilter(request, response);
3938
return;
4039
}

example/src/main/java/eu/webeid/example/security/WebEidMobileAuthInitFilter.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import com.fasterxml.jackson.databind.ObjectMapper;
2626
import eu.webeid.security.challenge.ChallengeNonceGenerator;
27-
import eu.webeid.security.challenge.ChallengeNonceStore;
2827
import jakarta.servlet.FilterChain;
2928
import jakarta.servlet.ServletException;
3029
import jakarta.servlet.http.HttpServletRequest;
@@ -43,29 +42,25 @@
4342

4443
public final class WebEidMobileAuthInitFilter extends OncePerRequestFilter {
4544
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
46-
private final RequestMatcher matcher =
47-
PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, "/auth/mobile/auth/init");
45+
private final RequestMatcher requestMatcher;
4846

4947
private final ChallengeNonceGenerator nonceGenerator;
50-
private final ChallengeNonceStore challengeNonceStore;
5148

52-
public WebEidMobileAuthInitFilter(ChallengeNonceGenerator nonceGenerator,
53-
ChallengeNonceStore challengeNonceStore) {
49+
public WebEidMobileAuthInitFilter(String path, ChallengeNonceGenerator nonceGenerator) {
50+
this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, path);
5451
this.nonceGenerator = nonceGenerator;
55-
this.challengeNonceStore = challengeNonceStore;
5652
}
5753

5854
@Override
5955
protected void doFilterInternal(@NonNull HttpServletRequest request,
6056
@NonNull HttpServletResponse response,
6157
@NonNull FilterChain chain) throws IOException, ServletException {
62-
if (!matcher.matches(request)) {
58+
if (!requestMatcher.matches(request)) {
6359
chain.doFilter(request, response);
6460
return;
6561
}
6662

6763
var challenge = nonceGenerator.generateAndStoreNonce();
68-
challengeNonceStore.put(challenge);
6964

7065
String loginUri = ServletUriComponentsBuilder.fromCurrentContextPath()
7166
.path("/auth/eid/login").build().toUriString();

example/src/main/java/eu/webeid/example/security/ui/WebEidLoginPageGeneratingFilter.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
import jakarta.servlet.ServletException;
2727
import jakarta.servlet.http.HttpServletRequest;
2828
import jakarta.servlet.http.HttpServletResponse;
29-
import org.slf4j.Logger;
30-
import org.slf4j.LoggerFactory;
3129
import org.springframework.http.HttpMethod;
3230
import org.springframework.lang.NonNull;
3331
import org.springframework.security.web.csrf.CsrfToken;
@@ -38,8 +36,12 @@
3836
import java.io.IOException;
3937

4038
public final class WebEidLoginPageGeneratingFilter extends OncePerRequestFilter {
41-
private static final Logger LOG = LoggerFactory.getLogger(WebEidLoginPageGeneratingFilter.class);
42-
private final RequestMatcher requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, "/auth/eid/login");
39+
private final RequestMatcher requestMatcher;
40+
41+
public WebEidLoginPageGeneratingFilter(String path) {
42+
this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, path);
43+
}
44+
4345
private static final String LOGIN_PAGE_HTML = """
4446
<!doctype html>
4547
<html lang="en">
@@ -77,10 +79,7 @@ public final class WebEidLoginPageGeneratingFilter extends OncePerRequestFilter
7779
})
7880
.catch(e => {
7981
console.error("Login failed", e);
80-
const errorDiv = document.createElement("div");
81-
errorDiv.style.color = "red";
82-
errorDiv.textContent = "Authentication failed.";
83-
document.body.appendChild(errorDiv);
82+
window.location.replace("/?mobileAuthError=login_failed");
8483
});
8584
})();
8685
</script>
@@ -97,9 +96,6 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull Ht
9796
return;
9897
}
9998

100-
var session = request.getSession(false);
101-
LOG.info("LOGIN PAGE: rendering login page for sessionId={}", session != null ? session.getId() : "null");
102-
10399
var csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
104100
if (csrf == null) {
105101
csrf = (CsrfToken) request.getAttribute("_csrf");

example/src/main/resources/templates/index.html

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,12 +272,13 @@ <h3><a id="for-developers"></a>For developers</h3>
272272
[csrfHeaderName]: csrfToken,
273273
},
274274
credentials: "include"
275-
});
276-
await checkHttpError(resp);
277-
const { eidAuthUri } = await resp.json();
278-
window.location.href = eidAuthUri;
279-
return;
280-
}
275+
});
276+
277+
await checkHttpError(resp);
278+
const { eidAuthUri } = await resp.json();
279+
window.location.href = eidAuthUri;
280+
return;
281+
}
281282

282283
const challengeResponse = await fetch("/auth/challenge", {
283284
method: "POST",

example/src/test/java/eu/webeid/example/AuthenticationRestControllerTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import static eu.webeid.security.challenge.ChallengeNonceGenerator.NONCE_LENGTH;
3939
import static org.assertj.core.api.Assertions.assertThat;
4040
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
41-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
4241
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
4342
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
4443
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -55,7 +54,9 @@ class AuthenticationEndpointsFilterTest {
5554

5655
@Test
5756
void challengeReturnsNonceWithExpectedBase64Length() throws Exception {
58-
MvcResult result = mvc.perform(get("/auth/challenge").accept(MediaType.APPLICATION_JSON))
57+
MvcResult result = mvc.perform(post("/auth/challenge")
58+
.with(csrf())
59+
.accept(MediaType.APPLICATION_JSON))
5960
.andExpect(status().isOk())
6061
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
6162
.andReturn();
@@ -77,7 +78,7 @@ void mobileInitBuildsDeepLinkWithEmbeddedChallenge() throws Exception {
7778
.andReturn();
7879

7980
JsonNode json = mapper.readTree(result.getResponse().getContentAsByteArray());
80-
String eidAuthUri = json.get("eidAuthUri").asText();
81+
String eidAuthUri = json.get("auth_uri").asText();
8182

8283
assertThat(eidAuthUri).startsWith("web-eid-mobile://auth#");
8384

example/src/test/java/eu/webeid/example/WebApplicationTest.java

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@
2222

2323
package eu.webeid.example;
2424

25-
import eu.webeid.example.service.SigningService;
26-
import eu.webeid.example.service.dto.FileDTO;
27-
import eu.webeid.example.service.dto.SignatureDTO;
2825
import eu.webeid.example.testutil.Dates;
2926
import eu.webeid.example.testutil.HttpHelper;
3027
import eu.webeid.example.testutil.ObjectMother;
@@ -47,9 +44,6 @@
4744
import eu.webeid.example.service.dto.DigestDTO;
4845
import eu.webeid.security.challenge.ChallengeNonce;
4946
import eu.webeid.security.util.DateAndTime;
50-
import eu.webeid.security.validator.certvalidators.SubjectCertificateNotRevokedValidator;
51-
52-
import java.security.cert.X509Certificate;
5347

5448
import static org.junit.jupiter.api.Assertions.assertEquals;
5549
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -86,30 +80,13 @@ public void testRoot() throws Exception {
8680

8781
@Test
8882
public void testHappyFlow_LoginPrepareSignDownload() throws Exception {
89-
90-
// Arrange
91-
new MockUp<SubjectCertificateNotRevokedValidator>() {
92-
@Mock
93-
public void validateCertificateNotRevoked(X509Certificate subjectCertificate) {
94-
// Do not call real OCSP service in tests.
95-
}
96-
};
97-
9883
new MockUp<AsicSignatureFinalizer>() {
9984
@Mock
10085
public void validateOcspResponse(XadesSignature xadesSignature) {
10186
// Do not call real OCSP service in tests.
10287
}
10388
};
10489

105-
new MockUp<SigningService>() {
106-
@Mock
107-
public FileDTO signContainer(SignatureDTO signatureDTO) {
108-
// Return a dummy FileDTO in tests
109-
return new FileDTO("example-for-signing.asice");
110-
}
111-
};
112-
11390
MockHttpSession session = new MockHttpSession();
11491
session.setAttribute("challenge-nonce", new ChallengeNonce(ObjectMother.VALID_CHALLENGE_NONCE, DateAndTime.utcNow().plusMinutes(1)));
11592

@@ -146,28 +123,13 @@ public static MockMultipartFile mockMultipartFile() {
146123

147124
@Test
148125
public void testHappyFlow_V11LoginAuthToken() throws Exception {
149-
new MockUp<SubjectCertificateNotRevokedValidator>() {
150-
@Mock
151-
public void validateCertificateNotRevoked(X509Certificate subjectCertificate) {
152-
// Do not call real OCSP service in tests.
153-
}
154-
};
155-
156126
new MockUp<AsicSignatureFinalizer>() {
157127
@Mock
158128
public void validateOcspResponse(XadesSignature xadesSignature) {
159129
// Do not call real OCSP service in tests.
160130
}
161131
};
162132

163-
new MockUp<SigningService>() {
164-
@Mock
165-
public FileDTO signContainer(SignatureDTO signatureDTO) {
166-
// Return a dummy FileDTO in tests
167-
return new FileDTO("example-for-signing.asice");
168-
}
169-
};
170-
171133
MockHttpSession session = new MockHttpSession();
172134
session.setAttribute("challenge-nonce", new ChallengeNonce(ObjectMother.VALID_CHALLENGE_NONCE, DateAndTime.utcNow().plusMinutes(1)));
173135

0 commit comments

Comments
 (0)