스프링 보안 새 인증 서버(0.3.1) - 2부
엔터티부터 시작하겠습니다.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class AppUser implements UserDetails {
@Id
private String id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_authority",
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))
private Collection<Authority> authorities;
private Boolean isAccountExpired = false;
private Boolean isAccountLocked = false;
private Boolean isCredentialsExpired = false;
private Boolean isEnabled = true;
public AppUser(String username, String password,
Collection<Authority> authorities) {
this.id = UUID.randomUUID().toString();
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return !this.isAccountExpired;
}
@Override
public boolean isAccountNonLocked() {
return !this.isAccountLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return !this.isCredentialsExpired;
}
@Override
public boolean isEnabled() {
return this.isEnabled;
}
}
Spring 보안에서
ProviderManager
와 함께 DaoAuthenticationProvider
를 사용할 것이므로 엔터티에서 UserDetails
를 구현합니다.권한에 대한
@ManyToMany
관계도 확인할 수 있습니다.Authority
엔터티를 살펴보겠습니다.@Entity
@NoArgsConstructor
@Getter
@Setter
public class Authority implements GrantedAuthority {
@Id
private String id;
private String name;
public Authority(String name) {
this.id = UUID.randomUUID().toString();
this.name = name;
}
@Override
public String getAuthority() {
return this.name;
}
}
여기에는 많지 않으며 권한에는 이름과 ID만 있습니다. 우리는
GrantedAuthority
를 구현해야 했습니다. 왜냐하면 UserDetails
권한이 Collection<? extends GrantedAuthority>
이기 때문입니다. Hibernate는 또한 AppUser
엔티티의 @JoinTable 주석을 기반으로 조인 테이블을 생성합니다.기본 보안 구성에 대한 구성은 이전 부분에서 변경해야 합니다. 이제 어떻게 보이는지 살펴보겠습니다.
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final UserService userService;
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeRequests()
.antMatchers("/users/**").permitAll()
.anyRequest().authenticated()
.and()
.csrf().ignoringAntMatchers("/users/**")
.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;
}
}
먼저 사용자를 생성할 방법이 필요하기 때문에/users/** 패턴에 대한 모든 요청을 허용했습니다. 새 사용자를 생성하기 위한 엔드포인트만 있지만 실제 시나리오에서는 이와 같은 모든 요청을 허용하지 않을 것입니다. 그런 다음 csrf도 비활성화해야 합니다. 왜냐하면 스파에서 이 엔드포인트를 호출하기를 원할 것이기 때문입니다. 로그인 페이지와 같은 스프링 앱의 일부로 등록 페이지를 생성하려면 csrf를 떠나야 합니다.
다음으로 새로운 빈
AuthenticationManager
을 반환하고 공급자로 ProviderManager
만 전달하는 빈DaoAuthenticationProvider
을 노출합니다. 그 전에 우리는 이전에 주입한 DaoAuthenticationProvider
의 구현을 사용하도록 UserDetailsService
지시합니다. 구현은 다음과 같습니다.@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
private final AppUserRepository appUserRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.appUserRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
public UserResponse save(CreateUserRequest request) {
var user = new AppUser(request.getUsername(), request.getPassword(), List.of());
this.appUserRepository.save(user);
return new UserResponse(user);
}
}
또한
NoOpPasswordEncoder
를 사용하지 않을 수도 있지만 단순성을 위해 여기에서 사용했습니다. 거기에서 다른 것PasswordEncoder
을 간단히 사용할 수 있으며 빈으로 노출하고 사용자를 생성할 때 저장하기 전에 암호를 인코딩할 수도 있습니다.이제 변경 사항을 테스트해 보겠습니다. 이전 부분과 동일하게 다음 링크를 엽니다.
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 및 검증자를 생성하고 교체해야 합니다.
/login 페이지로 리디렉션되지만 지금은 데이터베이스에 사용자가 없기 때문에 아직 로그인할 수 없습니다. 먼저 사용자를 생성해 보겠습니다.
이와 같은 응답에 암호를 반환하면 안 됩니다. 이는 데모용일 뿐입니다.
이제 사용자 이름과 암호를 사용하여 로그인하면 'spring.io?code=...'로 리디렉션되고 코드를 복사하고 토큰 끝점을 호출해야 합니다.
다시 말하지만, 자신의 code_verifier와 이전 리디렉션에서 얻은 코드를 입력했는지 확인하세요.
이제 데이터베이스에 사용자가 있지만 클라이언트는 여전히 메모리에 저장되어 있습니다. 다음 부분에서는 클라이언트를 위해 동일한 작업을 수행합니다.
Reference
이 문제에 관하여(스프링 보안 새 인증 서버(0.3.1) - 2부), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/nerminkarapandzic/spring-security-new-authorization-server-031-part-2-hj6텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)