Skip to content

Spring boot 3.4+ Support #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Feb 13, 2025
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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
# 2.0.0
* Added support for Spring Security 6.4+ / Spring Boot 3.4+ #100
* Spring now
* uses a Regex-based templating system
* no longer uses bootstrap
* provides One-Time token/OTT and Passkey logins
* Changes to ``Extendable``-subsystem
* Now uses the new Regex-based templating system
* Correct a bunch of problems in Spring Security including
* One-Time token/OTT and Passkeys are ignored when computing if the whole filter is enabled
* [Passkeys] Removed invalid XML comment in scripts block
* [Passkeys] Fixed incorrectly closed HTML-form/div-tag
* [HtmlTemplating] Compile ``UNUSED_PLACEHOLDER_PATTERN`` regex once and not for each request
* [HtmlTemplating] Render: Optimization: Use entrySet instead of keySet + getValue
* Add correct setter for ``generateOneTimeTokenUrl``
* Improved naming of methods
* Changes to ``Advanced``-subsystem
* Keeps using Bootstrap
* By default bootstrap is still loaded from ``cdn.jsdelivr.net`` but you can (and should) provide your own version
* Keeps using the old templating system (without Regex)
* Not all values are escaped by default as is with Spring's Regex based system
* Usually they don't need to be escaped in the first place as they are set on the server side and can't be modified by a user
* This is A LOT FASTER (in tests around 50x) than Spring's new Regex based system
* Adopted changes; Added new configuration options
* [Passkeys] Fixed a problem where more than one header results in invalid generated JavaScript code

# 1.0.3
* Updated dependencies
* Abstracted code
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ public SecurityFilterChain configure(final HttpSecurity http) throws Exception

A more detailed scenario is available in the [demo](./spring-security-advanced-authentication-ui-demo/).

> [!NOTE]
> By default [Bootstrap](https://github.com/twbs/bootstrap) is loaded from ``cdn.jsdelivr.net``.<br/>
> Due to privacy and stability reasons you should ship your own version!<br/>
> An example how this can be done is shown in the demo.

> [!NOTE]
> The ``Advanced``-subsystem uses the pre-``Spring Security 6.4`` / ``Spring Boot 3.4`` templating system (without Regex).<br/>
> * In contrast to Spring's new Regex based system not all values are escaped by default
> * Usually they don't need to be escaped in the first place as they are set on the server side and can't be modified by a user
> * This is A LOT FASTER (in tests around 50x) than Spring's new Regex based system

## Installation
[Installation guide for the latest release](https://github.com/xdev-software/spring-security-advanced-authentication-ui/releases/latest#Installation)

Expand Down
2 changes: 0 additions & 2 deletions dev_infra/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3"

services:
# Docs: https://docs.duendesoftware.com
oidc-server-mock:
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>software.xdev</groupId>
<artifactId>spring-security-advanced-authentication-ui-root</artifactId>
<version>1.0.4-SNAPSHOT</version>
<version>2.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<organization>
Expand Down
14 changes: 14 additions & 0 deletions spring-security-advanced-authentication-ui-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,17 @@
* Start the [development infrastructure](../dev_infra/)
* Run the application
* Open ``http://localhost:8080``

## Special Login information

### Username + Password

Example user:
* Username: ``test``
* Password: ``test``

### Passkeys

The browser needs to support passkeys and you also need an appropriate store (usually the OS handles this).

NOTE: Passkeys are lost when rebooting the server
19 changes: 16 additions & 3 deletions spring-security-advanced-authentication-ui-demo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<parent>
<groupId>software.xdev</groupId>
<artifactId>spring-security-advanced-authentication-ui-root</artifactId>
<version>1.0.4-SNAPSHOT</version>
<version>2.0.0-SNAPSHOT</version>
</parent>

<artifactId>spring-security-advanced-authentication-ui-demo</artifactId>
<version>1.0.4-SNAPSHOT</version>
<version>2.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<organization>
Expand All @@ -28,7 +28,7 @@

<mainClass>software.xdev.Application</mainClass>

<org.springframework.boot.version>3.3.5</org.springframework.boot.version>
<org.springframework.boot.version>3.4.2</org.springframework.boot.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -63,6 +63,19 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<!-- Required to showcase passkeys -->
<dependency>
<groupId>com.webauthn4j</groupId>
<artifactId>webauthn4j-core</artifactId>
<version>0.28.5.RELEASE</version>
</dependency>

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

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package software.xdev.controllers;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -14,19 +14,19 @@ public class RootController
@GetMapping
public Result respond()
{
if(SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal() instanceof final DefaultOidcUser oidcUser)
{
return new Result(oidcUser.getFullName(), oidcUser.getEmail(), "/logout");
}
return null;
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new Result(
"/logout",
"/webauthn/register",
authentication != null ? authentication.getClass().getName() : null,
authentication);
}

public record Result(
String name,
String email,
String logoutUrl
String logoutUrl,
String passKeyRegistrationUrl,
String authenticationClass,
Object authentication
)
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
import org.springframework.security.web.savedrequest.NullRequestCache;
Expand All @@ -28,12 +33,14 @@
@EnableConfigurationProperties(AdditionalOAuth2ClientProperties.class)
public class MainWebSecurity
{
@Bean(name = "mainSecurityFilterChainBean")
public SecurityFilterChain configure(
private static final Logger LOG = LoggerFactory.getLogger(MainWebSecurity.class);

protected void customizeLogin(
final HttpSecurity http,
final AdditionalOAuth2ClientProperties additionalOAuth2ClientProperties) throws Exception
{
http.with(new AdvancedLoginPageAdapter<>(http), c -> c
http.with(
new AdvancedLoginPageAdapter<>(http), c -> c
.customizePages(p -> p
// No remote communication -> Use local resources
.setHeaderElements(List.of(
Expand All @@ -60,18 +67,48 @@ public SecurityFilterChain configure(
+ " href='https://xdev.software' target='_blank'>"
+ " XDEV Software"
+ " </a>"
+ "</p>")))
+ "</p>")));
}

@Bean(name = "mainSecurityFilterChainBean")
public SecurityFilterChain configure(
final HttpSecurity http,
final AdditionalOAuth2ClientProperties additionalOAuth2ClientProperties) throws Exception
{
this.customizeLogin(http, additionalOAuth2ClientProperties);

http
.headers(h -> h
.referrerPolicy(r -> r.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN))
.contentSecurityPolicy(csp -> csp.policyDirectives(this.getCSP())))
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(c -> c.tokenGenerationSuccessHandler(
(request, response, oneTimeToken) ->
LOG.info(
"OneTimeToken should be sent for {} with value {}",
oneTimeToken.getUsername(),
oneTimeToken.getTokenValue())))
.webAuthn(c -> c.rpName("Spring Security Localhost Relying Party")
.rpId("localhost")
.allowedOrigins("http://localhost:8080"))
.oauth2Login(c -> c.defaultSuccessUrl("/"))
.authorizeHttpRequests(urlRegistry -> urlRegistry.anyRequest().authenticated())
.requestCache(c -> c.requestCache(new NullRequestCache()));

return http.build();
}

@Bean
@SuppressWarnings({"java:S6437", "deprecation"})
public UserDetailsService userDetailsService()
{
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("test")
.password("test")
.roles("USER")
.build());
}

// Example CSP
protected String getCSP()
{
Expand Down
6 changes: 3 additions & 3 deletions spring-security-advanced-authentication-ui/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>software.xdev</groupId>
<artifactId>spring-security-advanced-authentication-ui</artifactId>
<version>1.0.4-SNAPSHOT</version>
<version>2.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-security-advanced-authentication-ui</name>
Expand Down Expand Up @@ -88,13 +88,13 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.3.5</version>
<version>3.4.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.3.5</version>
<version>3.4.2</version>
<scope>provided</scope>
</dependency>

Expand Down
Loading