Partially implement resource server tweak some settings

This commit is contained in:
2025-06-15 00:41:05 +05:00
parent 76bc5d4853
commit 55397b87c6
8 changed files with 81 additions and 45 deletions

View File

@ -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());
}
}

View File

@ -1,23 +1,18 @@
package com.backend.user.service.anyame.config; package com.backend.user.service.anyame.config;
import com.backend.user.service.anyame.component.AuthorizationServerProperties; import com.backend.user.service.anyame.component.AuthorizationServerProperties;
import com.nimbusds.jose.jwk.source.JWKSource; import com.backend.user.service.anyame.component.CustomOpaqueTokenAuthenticationConverter;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes; 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.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; 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.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.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; 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.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; 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.Duration;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.UUID; import java.util.UUID;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig { public class AuthorizationServerConfig {
private final AuthorizationServerProperties authorizationServerProperties; private final AuthorizationServerProperties authorizationServerProperties;
private final CustomOpaqueTokenAuthenticationConverter opaqueTokenAuthenticationConverter;
public AuthorizationServerConfig(AuthorizationServerProperties authorizationServerProperties) { public AuthorizationServerConfig(AuthorizationServerProperties authorizationServerProperties, CustomOpaqueTokenAuthenticationConverter opaqueTokenAuthenticationConverter) {
this.authorizationServerProperties = authorizationServerProperties; this.authorizationServerProperties = authorizationServerProperties;
this.opaqueTokenAuthenticationConverter = opaqueTokenAuthenticationConverter;
} }
@Bean @Bean
@Order(1) @Order(1)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception { public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
OAuth2AuthorizationServerConfigurer.authorizationServer(); OAuth2AuthorizationServerConfigurer.authorizationServer();
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
return http return http
.cors(c -> c.configurationSource(configurationSource)) .csrf(c -> c.ignoringRequestMatchers(endpointsMatcher))
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) .securityMatcher(endpointsMatcher)
.with(authorizationServerConfigurer, (authorizationServer) -> .with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer authorizationServer
.oidc(Customizer.withDefaults()) .oidc(withDefaults())
) )
.authorizeHttpRequests((authorize) -> .authorizeHttpRequests((authorize) ->
authorize authorize
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.oauth2ResourceServer(c -> c
.opaqueToken(opaqueTokenConfigurer -> opaqueTokenConfigurer
.authenticationConverter(opaqueTokenAuthenticationConverter)
)
)
.exceptionHandling((exceptions) -> exceptions .exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor( .defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"), new LoginUrlAuthenticationEntryPoint("/login"),
@ -69,7 +76,7 @@ public class AuthorizationServerConfig {
public RegisteredClientRepository registeredClientRepository() { public RegisteredClientRepository registeredClientRepository() {
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString()) RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("oidc-client") .clientId("oidc-client")
.clientSecret("{noop}secret") .clientSecret("$2a$12$IdGgEQv2Zmtx.dEHvUhxJ.Pi3x9lufrvcfkQ8e4t2pwhD7F8swEJu")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
@ -90,10 +97,6 @@ public class AuthorizationServerConfig {
return new InMemoryRegisteredClientRepository(oidcClient); return new InMemoryRegisteredClientRepository(oidcClient);
} }
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean @Bean
public AuthorizationServerSettings authorizationServerSettings() { public AuthorizationServerSettings authorizationServerSettings() {

View File

@ -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();
}
}

View File

@ -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.CustomOAuth2UserService;
import com.backend.user.service.anyame.service.CustomUserDetailsService; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; 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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfigurationSource;
import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.Customizer.withDefaults;
@ -32,9 +30,8 @@ public class SecurityConfig {
@Bean @Bean
@Order(2) @Order(2)
public SecurityFilterChain filterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http return http
.cors(c -> c.configurationSource(configurationSource))
.authorizeHttpRequests(c -> .authorizeHttpRequests(c ->
c.anyRequest().authenticated() c.anyRequest().authenticated()
) )

View File

@ -1,38 +1,31 @@
package com.backend.user.service.anyame.config; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration @Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@Override @Bean
public void addCorsMappings(CorsRegistry registry) { public FilterRegistrationBean<CorsFilter> corsFilter() {
registry.addMapping("/**") UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
.allowedOrigins("http://localhost:5173")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
@Bean(name = "corsConfigurationSource")
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration(); 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); 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); source.registerCorsConfiguration("/**", config);
return source; FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
} }
} }

View File

@ -16,4 +16,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
"LEFT JOIN FETCH u.roles " + "LEFT JOIN FETCH u.roles " +
"WHERE u.name = :name") "WHERE u.name = :name")
Optional<User> findByNameWithRoles(@Param("name") String name); Optional<User> findByNameWithRoles(@Param("name") String name);
Optional<User> findByProviderId(String name);
} }

View File

@ -70,7 +70,8 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ
registrationId, email, name, providerId); registrationId, email, name, providerId);
User user = userRepository.findByName(name) // TODO: Handle user name conflict
User user = userRepository.findByProviderId(providerId)
.orElseGet(() -> createNewUser(email, name, registrationId, providerId)); .orElseGet(() -> createNewUser(email, name, registrationId, providerId));
// TODO: Should be toggleable nor documented behaviour // TODO: Should be toggleable nor documented behaviour

View File

@ -72,7 +72,8 @@ public class CustomOIDCUserService extends OidcUserService {
log.info("OIDC User - Provider: {}, Email: {}, Name: {}, Subject: {}", log.info("OIDC User - Provider: {}, Email: {}, Name: {}, Subject: {}",
registrationId, 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)); .orElseGet(() -> createNewUser(email, name, registrationId, subject, picture, preferredUsername));
// TODO: Should be toggleable nor documented behaviour // TODO: Should be toggleable nor documented behaviour