content: package org.example.authserver;
  
  import com.nimbusds.jose.jwk.source.JWKSource;
  import com.nimbusds.jose.proc.SecurityContext;
  import lombok.RequiredArgsConstructor;
  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.core.userdetails.User;
  import org.springframework.security.core.userdetails.UserDetails;
  import org.springframework.security.core.userdetails.UserDetailsService;
  import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  import org.springframework.security.crypto.password.PasswordEncoder;
  import org.springframework.security.oauth2.core.AuthorizationGrantType;
  import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
  import org.springframework.security.oauth2.core.OAuth2Token;
  import org.springframework.security.oauth2.core.oidc.OidcScopes;
  import org.springframework.security.oauth2.jwt.JwtEncoder;
  import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
  import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
  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.configurers.OAuth2AuthorizationServerConfigurer;
  import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
  import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
  import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
  import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
  import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
  import org.springframework.security.provisioning.InMemoryUserDetailsManager;
  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.CorsConfiguration;
  import org.springframework.web.cors.CorsConfigurationSource;
  import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  
  import java.time.Duration;
  import java.util.UUID;
  
  @RequiredArgsConstructor
  @Configuration
  public class SecurityConfig {
  
      private final static String GATEWAY_CLIENT_ID = "gateway-client";
      private final static String GATEWAY_CLIENT_HOST_URL = "http://localhost:8080";
      private final static String PUBLIC_CLIENT_ID = "public-client";
      private final static String PUBLIC_CLIENT_HOST_URL = "http://localhost:5173";
  
      @Bean
      @Order(1) // security filter chain for the authorization server
      public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
          // @formatter:off
  
          OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                  OAuth2AuthorizationServerConfigurer.authorizationServer();
  
          http
                  .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                  .with(authorizationServerConfigurer, authorizationServer ->
                          authorizationServer
                                  .oidc(Customizer.withDefaults()) // enable openid connect
                                  .clientAuthentication(clientAuthenticationConfigurer ->
                                         clientAuthenticationConfigurer
                                                 .authenticationConverter(new PublicClientRefreshTokenAuthenticationConverter())
                                                 .authenticationProvider(
                                                         new PublicClientRefreshTokenAuthenticationProvider(
                                                                 registeredClientRepository(),
                                                                 new InMemoryOAuth2AuthorizationService() // replace with your AuthorizationService implementation
                                                         )
                                                 )
                                  )
                  )
                  .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
  
          http
                  .cors(Customizer.withDefaults())
                  .exceptionHandling((exceptions) -> // If any errors occur redirect user to login page
                          exceptions.defaultAuthenticationEntryPointFor(
                                  new LoginUrlAuthenticationEntryPoint("/login"),
                                  new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                          )
                  )
                  // enable auth server to accept JWT for endpoints such as /userinfo
                  .oauth2ResourceServer(resourceServer -> resourceServer.jwt(Customizer.withDefaults()));
          // @formatter:on
  
          return http.build();
      }
  
      @Bean
      @Order(2) // security filter chain for the rest of your application and any custom endpoints you may have
      public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
          // @formatter:off
          http
                  .cors(Customizer.withDefaults())
                  .formLogin(formLogin -> formLogin.loginPage("/login").permitAll()) // Enable form login
                  .oauth2Login(oauth2Login -> oauth2Login.loginPage("/login").permitAll()) // Enable oauth2 federated identity login
                  .authorizeHttpRequests(authorize ->
                          authorize
                                  .requestMatchers("/webjars/**", "/images/**", "/css/**", "/assets/**", "/favicon.ico").permitAll()
                                  .anyRequest().authenticated()
                  );
          // @formatter:on
  
          return http.build();
      }
  
      @Bean
      public RegisteredClientRepository registeredClientRepository() {
          // @formatter:off
          RegisteredClient webClient = RegisteredClient
                  .withId(UUID.randomUUID().toString())
                  .clientId(GATEWAY_CLIENT_ID)
                  .clientSecret(passwordEncoder().encode("secret"))
                  .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                  .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                  .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                  .redirectUri(GATEWAY_CLIENT_HOST_URL + "/login/oauth2/code/" + GATEWAY_CLIENT_ID)
                  .postLogoutRedirectUri(GATEWAY_CLIENT_HOST_URL + "/logout")
                  .scope(OidcScopes.OPENID)  // openid scope is mandatory for authentication
                  .scope(OidcScopes.PROFILE)
                  .scope(OidcScopes.EMAIL)
                  .build();
          // @formatter:on
  
          // @formatter:off
          RegisteredClient publicWebClient = RegisteredClient
                  .withId(UUID.randomUUID().toString())
                  .clientId(PUBLIC_CLIENT_ID)
                  .clientAuthenticationMethod(ClientAuthenticationMethod.NONE) // authentication method set to 'NONE'
                  .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                  .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                  .redirectUri(PUBLIC_CLIENT_HOST_URL + "/callback")
                  .postLogoutRedirectUri(PUBLIC_CLIENT_HOST_URL + "/logout")
                  .scope(OidcScopes.OPENID)
                  .scope(OidcScopes.PROFILE)
                  .scope(OidcScopes.EMAIL)
                  .clientSettings(ClientSettings.builder().requireProofKey(true).build()) // enable PKCE
                  .tokenSettings(
                          TokenSettings.builder()
                                  .accessTokenTimeToLive(Duration.ofSeconds(70))
                                  .reuseRefreshTokens(false)
                                  .build()
                  )
                  .build();
          // @formatter:on
  
          return new InMemoryRegisteredClientRepository(webClient, publicWebClient);
      }
  
      @Bean
      UserDetailsService users() {
          // @formatter:off
          UserDetails user = User.builder()
                  .username("prime")
                  .password("agen")
                  .passwordEncoder(passwordEncoder()::encode)
                  .roles("USER")
                  .build();
          // @formatter:on
  
          // @formatter:off
          UserDetails me = User.builder()
                  .username("afeefrazickamir@gmail.com")
                  .password("pass")
                  .passwordEncoder(passwordEncoder()::encode)
                  .roles("USER", "ADMIN")
                  .build();
          // @formatter:on
  
          return new InMemoryUserDetailsManager(user, me);
      }
  
      @Bean
      public PasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder();
      }
  
      @Bean
      OAuth2TokenGenerator<OAuth2Token> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
          JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource);
          JwtGenerator jwtAccessTokenGenerator = new JwtGenerator(jwtEncoder);
          jwtAccessTokenGenerator.setJwtCustomizer(new Oauth2AccessTokenCustomizer(users()));
  
          return new DelegatingOAuth2TokenGenerator(jwtAccessTokenGenerator, new OAuth2PublicClientRefreshTokenGenerator());
      }
  
      @Bean
      CorsConfigurationSource corsConfigurationSource() {
          UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
          CorsConfiguration config = new CorsConfiguration();
          config.addAllowedOrigin(PUBLIC_CLIENT_HOST_URL);
          config.addAllowedHeader("*");
          config.addAllowedMethod("*");
          config.setAllowCredentials(true);
          source.registerCorsConfiguration("/**", config);
          return source;
      }
  }
  