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;
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<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
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.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()
)

View File

@ -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> 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<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 " +
"WHERE u.name = :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);
User user = userRepository.findByName(name)
// TODO: Handle user name conflict
User user = userRepository.findByProviderId(providerId)
.orElseGet(() -> createNewUser(email, name, registrationId, providerId));
// 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: {}",
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