From 55397b87c6eceb86f0c6710b2b5958e0dc512225 Mon Sep 17 00:00:00 2001 From: bivashy Date: Sun, 15 Jun 2025 00:41:05 +0500 Subject: [PATCH] Partially implement resource server tweak some settings --- ...tomOpaqueTokenAuthenticationConverter.java | 25 +++++++++++++ .../config/AuthorizationServerConfig.java | 37 ++++++++++--------- .../anyame/config/ConfigUtilities.java | 14 +++++++ .../service/anyame/config/SecurityConfig.java | 5 +-- .../user/service/anyame/config/WebConfig.java | 37 ++++++++----------- .../anyame/repository/UserRepository.java | 2 + .../service/CustomOAuth2UserService.java | 3 +- .../anyame/service/CustomOIDCUserService.java | 3 +- 8 files changed, 81 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/backend/user/service/anyame/component/CustomOpaqueTokenAuthenticationConverter.java create mode 100644 src/main/java/com/backend/user/service/anyame/config/ConfigUtilities.java diff --git a/src/main/java/com/backend/user/service/anyame/component/CustomOpaqueTokenAuthenticationConverter.java b/src/main/java/com/backend/user/service/anyame/component/CustomOpaqueTokenAuthenticationConverter.java new file mode 100644 index 0000000..69f7967 --- /dev/null +++ b/src/main/java/com/backend/user/service/anyame/component/CustomOpaqueTokenAuthenticationConverter.java @@ -0,0 +1,25 @@ +package com.backend.user.service.anyame.component; + +import com.backend.user.service.anyame.service.UserAuthorityService; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; +import org.springframework.stereotype.Component; + +@Component +public class CustomOpaqueTokenAuthenticationConverter implements OpaqueTokenAuthenticationConverter { + private final OpaqueTokenIntrospector introspector; + private final UserAuthorityService authorityService; + + public CustomOpaqueTokenAuthenticationConverter(OpaqueTokenIntrospector introspector, UserAuthorityService authorityService) { + this.introspector = introspector; + this.authorityService = authorityService; + } + + @Override + public Authentication convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) { + return new BearerTokenAuthentication(authenticatedPrincipal, introspectedToken, authenticatedPrincipal.getAuthorities()); + } +} \ No newline at end of file diff --git a/src/main/java/com/backend/user/service/anyame/config/AuthorizationServerConfig.java b/src/main/java/com/backend/user/service/anyame/config/AuthorizationServerConfig.java index c2444e1..037363d 100644 --- a/src/main/java/com/backend/user/service/anyame/config/AuthorizationServerConfig.java +++ b/src/main/java/com/backend/user/service/anyame/config/AuthorizationServerConfig.java @@ -1,23 +1,18 @@ package com.backend.user.service.anyame.config; import com.backend.user.service.anyame.component.AuthorizationServerProperties; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.springframework.beans.factory.annotation.Qualifier; +import com.backend.user.service.anyame.component.CustomOpaqueTokenAuthenticationConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.MediaType; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; @@ -26,36 +21,48 @@ import org.springframework.security.oauth2.server.authorization.settings.TokenSe import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; -import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.security.web.util.matcher.RequestMatcher; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.UUID; +import static org.springframework.security.config.Customizer.withDefaults; + @Configuration(proxyBeanMethods = false) public class AuthorizationServerConfig { private final AuthorizationServerProperties authorizationServerProperties; + private final CustomOpaqueTokenAuthenticationConverter opaqueTokenAuthenticationConverter; - public AuthorizationServerConfig(AuthorizationServerProperties authorizationServerProperties) { + public AuthorizationServerConfig(AuthorizationServerProperties authorizationServerProperties, CustomOpaqueTokenAuthenticationConverter opaqueTokenAuthenticationConverter) { this.authorizationServerProperties = authorizationServerProperties; + this.opaqueTokenAuthenticationConverter = opaqueTokenAuthenticationConverter; } @Bean @Order(1) - public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception { + public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer.authorizationServer(); + + RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); + return http - .cors(c -> c.configurationSource(configurationSource)) - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) + .csrf(c -> c.ignoringRequestMatchers(endpointsMatcher)) + .securityMatcher(endpointsMatcher) .with(authorizationServerConfigurer, (authorizationServer) -> authorizationServer - .oidc(Customizer.withDefaults()) + .oidc(withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) + .oauth2ResourceServer(c -> c + .opaqueToken(opaqueTokenConfigurer -> opaqueTokenConfigurer + .authenticationConverter(opaqueTokenAuthenticationConverter) + ) + ) .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), @@ -69,7 +76,7 @@ public class AuthorizationServerConfig { public RegisteredClientRepository registeredClientRepository() { RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("oidc-client") - .clientSecret("{noop}secret") + .clientSecret("$2a$12$IdGgEQv2Zmtx.dEHvUhxJ.Pi3x9lufrvcfkQ8e4t2pwhD7F8swEJu") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) @@ -90,10 +97,6 @@ public class AuthorizationServerConfig { return new InMemoryRegisteredClientRepository(oidcClient); } - @Bean - public JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } @Bean public AuthorizationServerSettings authorizationServerSettings() { diff --git a/src/main/java/com/backend/user/service/anyame/config/ConfigUtilities.java b/src/main/java/com/backend/user/service/anyame/config/ConfigUtilities.java new file mode 100644 index 0000000..2455b72 --- /dev/null +++ b/src/main/java/com/backend/user/service/anyame/config/ConfigUtilities.java @@ -0,0 +1,14 @@ +package com.backend.user.service.anyame.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; + +@Configuration(proxyBeanMethods = false) +public class ConfigUtilities { + @Bean + public OAuth2AuthorizationService oAuth2AuthorizationService() { + return new InMemoryOAuth2AuthorizationService(); + } +} \ No newline at end of file diff --git a/src/main/java/com/backend/user/service/anyame/config/SecurityConfig.java b/src/main/java/com/backend/user/service/anyame/config/SecurityConfig.java index 60549a8..cc5bed7 100644 --- a/src/main/java/com/backend/user/service/anyame/config/SecurityConfig.java +++ b/src/main/java/com/backend/user/service/anyame/config/SecurityConfig.java @@ -2,7 +2,6 @@ package com.backend.user.service.anyame.config; import com.backend.user.service.anyame.service.CustomOAuth2UserService; import com.backend.user.service.anyame.service.CustomUserDetailsService; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -12,7 +11,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.web.cors.CorsConfigurationSource; import static org.springframework.security.config.Customizer.withDefaults; @@ -32,9 +30,8 @@ public class SecurityConfig { @Bean @Order(2) - public SecurityFilterChain filterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception { + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http - .cors(c -> c.configurationSource(configurationSource)) .authorizeHttpRequests(c -> c.anyRequest().authenticated() ) diff --git a/src/main/java/com/backend/user/service/anyame/config/WebConfig.java b/src/main/java/com/backend/user/service/anyame/config/WebConfig.java index c8e94ea..c9e4f21 100644 --- a/src/main/java/com/backend/user/service/anyame/config/WebConfig.java +++ b/src/main/java/com/backend/user/service/anyame/config/WebConfig.java @@ -1,38 +1,31 @@ package com.backend.user.service.anyame.config; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.List; - @Configuration -@EnableWebMvc public class WebConfig implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins("http://localhost:5173") - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") - .allowedHeaders("*") - .allowCredentials(true); - } - - @Bean(name = "corsConfigurationSource") - public CorsConfigurationSource corsConfigurationSource() { + @Bean + public FilterRegistrationBean corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(List.of("http://localhost:5173")); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); - config.setAllowedHeaders(List.of("*")); + config.setAllowCredentials(true); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + config.addAllowedOrigin("http://127.0.0.1:5173,http://localhost:5173"); + config.addAllowedHeader(CorsConfiguration.ALL); + config.addExposedHeader(CorsConfiguration.ALL); + config.addAllowedMethod(CorsConfiguration.ALL); + source.registerCorsConfiguration("/**", config); - return source; + FilterRegistrationBean bean = new FilterRegistrationBean<>(new CorsFilter(source)); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + return bean; } } \ No newline at end of file diff --git a/src/main/java/com/backend/user/service/anyame/repository/UserRepository.java b/src/main/java/com/backend/user/service/anyame/repository/UserRepository.java index 40f910d..93f8813 100644 --- a/src/main/java/com/backend/user/service/anyame/repository/UserRepository.java +++ b/src/main/java/com/backend/user/service/anyame/repository/UserRepository.java @@ -16,4 +16,6 @@ public interface UserRepository extends JpaRepository { "LEFT JOIN FETCH u.roles " + "WHERE u.name = :name") Optional findByNameWithRoles(@Param("name") String name); + + Optional findByProviderId(String name); } \ No newline at end of file diff --git a/src/main/java/com/backend/user/service/anyame/service/CustomOAuth2UserService.java b/src/main/java/com/backend/user/service/anyame/service/CustomOAuth2UserService.java index 19fa28e..dce222c 100644 --- a/src/main/java/com/backend/user/service/anyame/service/CustomOAuth2UserService.java +++ b/src/main/java/com/backend/user/service/anyame/service/CustomOAuth2UserService.java @@ -70,7 +70,8 @@ public class CustomOAuth2UserService implements OAuth2UserService createNewUser(email, name, registrationId, providerId)); // TODO: Should be toggleable nor documented behaviour diff --git a/src/main/java/com/backend/user/service/anyame/service/CustomOIDCUserService.java b/src/main/java/com/backend/user/service/anyame/service/CustomOIDCUserService.java index 24f0d6b..c1958c6 100644 --- a/src/main/java/com/backend/user/service/anyame/service/CustomOIDCUserService.java +++ b/src/main/java/com/backend/user/service/anyame/service/CustomOIDCUserService.java @@ -72,7 +72,8 @@ public class CustomOIDCUserService extends OidcUserService { log.info("OIDC User - Provider: {}, Email: {}, Name: {}, Subject: {}", registrationId, email, name, subject); - User user = userRepository.findByName(name) + // TODO: Handle user name conflict + User user = userRepository.findByProviderId(subject) .orElseGet(() -> createNewUser(email, name, registrationId, subject, picture, preferredUsername)); // TODO: Should be toggleable nor documented behaviour