Fix SSO logout (#715)

This commit is contained in:
Simon
2025-09-25 20:46:49 +02:00
committed by GitHub
3 changed files with 54 additions and 49 deletions
@@ -2,7 +2,6 @@ import {useAuth} from "Frontend/util/auth";
import {GearFine, Question, SignOut, User} from "@phosphor-icons/react"; import {GearFine, Question, SignOut, User} from "@phosphor-icons/react";
import {Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@heroui/react"; import {Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@heroui/react";
import {useNavigate} from "react-router"; import {useNavigate} from "react-router";
import {ConfigEndpoint} from "Frontend/generated/endpoints";
import Avatar from "Frontend/components/general/Avatar"; import Avatar from "Frontend/components/general/Avatar";
import {CollectionElement} from "@react-types/shared"; import {CollectionElement} from "@react-types/shared";
import {isAdmin} from "Frontend/util/utils"; import {isAdmin} from "Frontend/util/utils";
@@ -11,14 +10,6 @@ export default function ProfileMenu() {
const auth = useAuth(); const auth = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
async function logout() {
if (auth.state.user?.managedBySso) {
window.location.href = (await ConfigEndpoint.getSsoLogoutUrl()) || "/";
} else {
await auth.logout();
}
}
const profileMenuItems = [ const profileMenuItems = [
{ {
label: "My Profile", label: "My Profile",
@@ -39,7 +30,7 @@ export default function ProfileMenu() {
{ {
label: "Sign Out", label: "Sign Out",
icon: <SignOut/>, icon: <SignOut/>,
onClick: logout, onClick: auth.logout,
color: "primary" color: "primary"
}, },
]; ];
@@ -1,60 +1,69 @@
package org.gameyfin.app.core.security package org.gameyfin.app.core.security
import com.vaadin.flow.spring.security.VaadinWebSecurity import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration
import com.vaadin.flow.spring.security.VaadinSecurityConfigurer
import com.vaadin.hilla.route.RouteUtil
import org.gameyfin.app.config.ConfigProperties import org.gameyfin.app.config.ConfigProperties
import org.gameyfin.app.config.ConfigService import org.gameyfin.app.config.ConfigService
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Conditional import org.springframework.context.annotation.Conditional
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.core.env.Environment import org.springframework.core.env.Environment
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer
import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.session.SessionRegistry import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.oauth2.client.registration.ClientRegistration import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.security.oauth2.core.AuthorizationGrantType import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@Import(
VaadinAwareSecurityContextHolderStrategyConfiguration::class
)
class SecurityConfig( class SecurityConfig(
private val environment: Environment, private val environment: Environment,
private val config: ConfigService, private val config: ConfigService,
private val ssoAuthenticationSuccessHandler: SsoAuthenticationSuccessHandler, private val ssoAuthenticationSuccessHandler: SsoAuthenticationSuccessHandler,
private val sessionRegistry: SessionRegistry private val sessionRegistry: SessionRegistry
) : VaadinWebSecurity() { ) {
companion object { companion object {
const val SSO_PROVIDER_KEY = "oidc" const val SSO_PROVIDER_KEY = "oidc"
} }
@Throws(Exception::class) @Bean
override fun configure(http: HttpSecurity) { fun filterChain(http: HttpSecurity, routeUtil: RouteUtil): SecurityFilterChain {
http.authorizeHttpRequests { auth ->
// Configure your static resources with public access before calling super.configure(HttpSecurity) as it adds final anyRequest matcher // Set default security policy that permits Hilla internal requests and denies all other
http.authorizeHttpRequests { auth: AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry -> auth.requestMatchers(routeUtil::isRouteAllowed).permitAll()
auth.requestMatchers("/login").permitAll() // Gameyfin static resources and public endpoints
.requestMatchers("/setup").permitAll() .requestMatchers(
.requestMatchers("/reset-password").permitAll() "/login",
.requestMatchers("/accept-invitation").permitAll() "/setup",
.requestMatchers("/public/**").permitAll() "/reset-password",
.requestMatchers("/images/**").permitAll() "/accept-invitation",
.requestMatchers("/favicon.ico").permitAll() "/public/**",
.requestMatchers("/favicon.svg").permitAll() "/images/**",
"/favicon.ico",
// Dynamic public access for certain endpoints "/favicon.svg"
auth.requestMatchers("/").access(DynamicPublicAccessAuthorizationManager(config)) ).permitAll()
.requestMatchers("/game/**").access(DynamicPublicAccessAuthorizationManager(config)) // Dynamic public access for certain endpoints
.requestMatchers("/library/**").access(DynamicPublicAccessAuthorizationManager(config)) .requestMatchers(
.requestMatchers("/search/**").access(DynamicPublicAccessAuthorizationManager(config)) "/",
.requestMatchers("/requests/**").access(DynamicPublicAccessAuthorizationManager(config)) "/game/**",
.requestMatchers("/download/**").access(DynamicPublicAccessAuthorizationManager(config)) "/library/**",
"/search/**",
"/requests/**",
"/download/**"
).access(DynamicPublicAccessAuthorizationManager(config))
} }
http.sessionManagement { sessionManagement -> http.sessionManagement { sessionManagement ->
@@ -67,11 +76,14 @@ class SecurityConfig(
// Not needed since the frontend is served by the backend // Not needed since the frontend is served by the backend
http.cors { cors -> cors.disable() } http.cors { cors -> cors.disable() }
super.configure(http)
setLoginView(http, "/login", "/")
if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) { if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) {
http.with(VaadinSecurityConfigurer.vaadin()) { configurer ->
// Redirect to SSO provider on logout
configurer.loginView("/login", config.get(ConfigProperties.SSO.OIDC.LogoutUrl))
}
// Use custom success handler to handle user registration // Use custom success handler to handle user registration
http.oauth2Login { oauth2Login -> oauth2Login.successHandler(ssoAuthenticationSuccessHandler) } http.oauth2Login { oauth2Login -> oauth2Login.successHandler(ssoAuthenticationSuccessHandler) }
// Prevent unnecessary redirects // Prevent unnecessary redirects
@@ -81,16 +93,18 @@ class SecurityConfig(
http.exceptionHandling { exceptionHandling -> http.exceptionHandling { exceptionHandling ->
exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint()) exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint())
} }
} else {
// Use default Vaadin login URLs
http.with(VaadinSecurityConfigurer.vaadin()) { configurer ->
configurer.loginView("/login")
}
} }
}
@Throws(Exception::class)
public override fun configure(web: WebSecurity) {
super.configure(web)
if ("dev" in environment.activeProfiles) { if ("dev" in environment.activeProfiles) {
web.ignoring().requestMatchers("/h2-console/**") http.authorizeHttpRequests { auth -> auth.requestMatchers("/h2-console/**").permitAll() }
} }
return http.build()
} }
@Bean @Bean
+5 -5
View File
@@ -3,12 +3,12 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=true org.gradle.caching=true
# Plugin versions # Plugin versions
kotlinVersion=2.2.0 kotlinVersion=2.2.20
kspVersion=2.2.0-2.0.2 kspVersion=2.2.20-2.0.3
vaadinVersion=24.8.7 vaadinVersion=24.9.0
springBootVersion=3.5.3 springBootVersion=3.5.6
springCloudVersion=2025.0.0 springCloudVersion=2025.0.0
springDependencyManagementVersion=1.1.7 springDependencyManagementVersion=1.1.7
# Dependency versions # Dependency versions
pf4jVersion=3.13.0 pf4jVersion=3.13.0
pf4jKspVersion=2.2.0-1.0.3 pf4jKspVersion=2.2.20-1.0.3