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