스프링 보안 새 인증 서버(0.3.1) - 3부
먼저 엔터티부터 시작하겠습니다.
RegisteredClient에서 사용하는 라이브러리에서 제공하는 RegisteredClientRepository 클래스를 검사하여 엔터티를 모델링하는 방법을 알 수 있습니다.다음은
RegisteredClient 클래스의 필드입니다.이를 기반으로 클라이언트 엔터티를 원하는 대로 모델링할 수 있지만 결국에는 클라이언트 엔터티에서
RegisteredClient를 구성할 수 있어야 합니다. 우리는 UserDetails에 대한 인터페이스가 없기 때문에 단순히 인터페이스를 구현하고 이 엔터티가 RegisteredClient라고 말할 수 없지만 대신 내가 한 것은 RegisteredClient를 빌드하는 팩토리 클래스를 제공하는 것입니다. 엔티티에서.클라이언트 엔터티:
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Client {
  @Id
  private String id;
  private String secret;
  @ElementCollection(fetch = FetchType.EAGER)
  private Set<AuthorizationGrantType> grantTypes;
  private ClientAuthenticationMethod authenticationMethod;
  @OneToMany(mappedBy = "client", fetch = FetchType.EAGER)
  private Set<ClientRedirectUrl> redirectUris;
  @ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
  @JoinTable(name = "client_scope_mapping",
    joinColumns = @JoinColumn(name = "client_id", referencedColumnName = "id"),
    inverseJoinColumns = @JoinColumn(name = "scope_id", referencedColumnName = "id")
  )
  private Collection<ClientScope> scopes;
  public Client(CreateClientRequest request) {
    this.id = request.getId();
    this.secret = request.getSecret();
    this.grantTypes = request.getGrantTypes();
    this.authenticationMethod = request.getAuthenticationMethod();
  }
  public static RegisteredClient toRegisteredClient(Client client) {
    RegisteredClient.Builder builder = RegisteredClient.withId(client.getId())
      .clientId(client.getId())
      .clientSecret(client.getSecret())
      .clientAuthenticationMethod(client.getAuthenticationMethod())
      .authorizationGrantTypes(
          authorizationGrantTypes -> authorizationGrantTypes.addAll(client.getGrantTypes()))
      .redirectUris(
          redirectUris -> redirectUris.addAll(client.getRedirectUris()
              .stream()
              .map(ClientRedirectUrl::getUrl)
              .collect(Collectors.toSet())))
      .scopes(scopes -> scopes.addAll(client.getScopes()
          .stream()
          .map(ClientScope::getScope)
          .collect(Collectors.toSet())));
    return builder.build();
  }
}
각 클라이언트에 대한 보조금 유형을 데이터베이스에
@ElementCollection로 직렬화된 AuthorizationGrantType 세트를 저장하는 tinyblob로 저장하기로 결정했습니다. 사용 가능한 모든 보조금 유형을 보관할 별도의 테이블을 갖도록 선택할 수 있습니다. 그런 다음 각 클라이언트에 필요한 것을 조인하면 조인 테이블도 필요할 것입니다.그런 다음 redirectUris(
ClientRedirectUrl)가 있는 @OneToMany와 범위(ClientScope)가 있는 @ManyToMany가 있습니다.ClientRedirectUrl 엔티티:
@Entity
@Getter
@NoArgsConstructor
@Table(name = "client_redirect_uri")
public class ClientRedirectUrl {
  @Id
  private String id;
  private String url;
  @Setter
  @ManyToOne
  @JoinColumn(name = "client_id", referencedColumnName = "id")
  private Client client;
  public ClientRedirectUrl(String url, Client client) {
    this.id = RandomStringUtils.randomAlphanumeric(10);
    this.url = url;
    this.client = client;
  }
}
ClientScope 엔터티:
@Entity
@Getter
@NoArgsConstructor
@Setter
@Table(name = "client_scope")
public class ClientScope {
  @Id
  private String id;
  private String scope;
  public ClientScope(String scope) {
    this.id = RandomStringUtils.randomAlphanumeric(10);
    this.scope = scope;
  }
}
이것은 최종 스키마 다이어그램입니다.

다음으로, 첫 번째 파트에서 생성한 Oauth2Config 클래스에서 메모리 내 클라이언트 리포지토리를 제공하는 빈을 제거해야 합니다. 대신
RegisteredClientRepository 를 구현할 @Service 를 제공할 것입니다.고객 서비스:
@Service
@RequiredArgsConstructor
public class ClientService implements RegisteredClientRepository {
  private final ClientRepository clientRepository;
  private final ClientRedirectUrlRepository clientRedirectUrlRepository;
  private final ClientScopeRepository clientScopeRepository;
  @Override
  public void save(RegisteredClient registeredClient) {
  }
  @Override
  public RegisteredClient findById(String id) {
    var client = this.clientRepository.findById(id).orElseThrow();
    return Client.toRegisteredClient(client);
  }
  @Override
  public RegisteredClient findByClientId(String clientId) {
    var client = this.clientRepository.findById(clientId).orElseThrow();
    return Client.toRegisteredClient(client);
  }
  public ClientResponse createClient(CreateClientRequest request) {
    var scopes = request.getScopes().stream().map(ClientScope::new).collect(Collectors.toSet());
    scopes.forEach(this.clientScopeRepository::save);
    var client = new Client(request);
    client.setScopes(scopes);
    this.clientRepository.save(client);
    client.setRedirectUris(request.getRedirectUris().stream()
        .map(url -> new ClientRedirectUrl(url, client))
        .collect(Collectors.toSet()));
    client.getRedirectUris().forEach(this.clientRedirectUrlRepository::save);
    return new ClientResponse(client);
  }
}
이제 Oauth2Config 클래스는 다음과 같습니다.
@Configuration
@RequiredArgsConstructor
public class Oauth2Config {
  private final ClientService clientService;
  @Bean
  @Order(HIGHEST_PRECEDENCE)
  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
      throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http
        // Redirect to the login page when not authenticated from the
        // authorization endpoint
        .exceptionHandling((exceptions) -> exceptions
            .authenticationEntryPoint(
                new LoginUrlAuthenticationEntryPoint("/login"))
        );
    return http.build();
  }
  @Bean
  public JWKSource<SecurityContext> jwkSource() {
    KeyPair keyPair = generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    RSAKey rsaKey = new RSAKey.Builder(publicKey)
        .privateKey(privateKey)
        .keyID(UUID.randomUUID().toString())
        .build();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
  }
  private static KeyPair generateRsaKey() {
    KeyPair keyPair;
    try {
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
      keyPairGenerator.initialize(2048);
      keyPair = keyPairGenerator.generateKeyPair();
    }
    catch (Exception ex) {
      throw new IllegalStateException(ex);
    }
    return keyPair;
  }
  @Bean
  public ProviderSettings providerSettings() {
    return ProviderSettings.builder().build();
  }
}
또한 새 클라이언트를 생성하기 위한 엔드포인트를 생성하고 기본 구성에서 이에 대한 요청도 허용해야 했기 때문에 이제 다음과 같이 보입니다.
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
  private final UserService userService;
  @Bean
  public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
      throws Exception {
    http
        .authorizeRequests()
        .antMatchers("/users/**").permitAll()
        .antMatchers("/clients/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .csrf().ignoringAntMatchers("/users/**", "/clients/**")
        .and()
        .formLogin(Customizer.withDefaults());
    return http.build();
  }
  @Bean
  public AuthenticationManager authenticationManagerBean() throws Exception {
    var provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userService);
    provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance()); //temporary
    return new ProviderManager(provider);
  }
  @Bean
  public UserDetailsService userDetailsService() {
    return this.userService;
  }
}
필자의 경우처럼 와일드카드에 대해 permitAll을 허용하는 것은 좋은 생각이 아닐 수 있지만 다시 필요에 따라 수정할 수 있습니다.
인증을 다시 테스트해 보겠습니다. 먼저 클라이언트를 생성합니다.

그런 다음 사용자를 만듭니다.

그런 다음 권한을 부여합니다.
http://localhost:8080/oauth2/authorize?response_type=code&client_id=client&scope=openid&redirect_uri=http://spring.io&code_challenge=YHRCg0i58poWtvPg_xiSHFBqCahjxCqTyUbhYTAk5ME&code_challenge_method=S256
자신의 code_challenge를 사용하려면 검증자가 필요합니다.
토큰 받기:

작동합니다. 인증 매처 구성, 비밀번호 인코더 또는 인코더 부족과 같이 변경해야 할 몇 가지 분명한 사항이 있지만 요점을 알 수 있습니다.
당신은 전체 코드here를 찾을 수 있지만, 오픈 소스 버전을 만들려고 할 것이기 때문에 나중에 변경될 것이라는 공정한 경고가 있습니다.
Reference
이 문제에 관하여(스프링 보안 새 인증 서버(0.3.1) - 3부), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/nerminkarapandzic/spring-security-new-authorization-server-031-part-3-3331텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
                                
                                
                                
                                
                                
                                우수한 개발자 콘텐츠 발견에 전념
                                (Collection and Share based on the CC Protocol.)