Untitled1.class

Username/Password Authentication 본문

공부/Spring Security

Username/Password Authentication

kitti-zini 2025. 5. 13. 15:46

사용자를 인증하는 가장 일반적인 방법 중 하나는 사용자 이름과 비밀번호를 검증하는 것이다. Spring Security는 사용자 이름과 비밀번호를 이용한 인증을 위한 포괄적인 지원을 제공한다.

다음을 사용하여 사용자 이름 및 비밀번호 인증을 구성할 수 있다:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			)
			.httpBasic(Customizer.withDefaults())
			.formLogin(Customizer.withDefaults());

		return http.build();
	}

	@Bean
	public UserDetailsService userDetailsService() {
		UserDetails userDetails = User.withDefaultPasswordEncoder()
			.username("user")
			.password("password")
			.roles("USER")
			.build();

		return new InMemoryUserDetailsManager(userDetails);
	}

}

이 구성은 메모리 내 UserDetailsService를 SecurityFilterChain에 자동으로 등록하고, DaoAuthenticationProvider를 기본 AuthenticationManager에 등록하며, Form login 및 HTTP Basic 인증을 황성화한다.

Publish an AuthenticationManager bean

@Service 또는 Spring MVC @Controller와 같은 사용자 지정 인증을 위해 AuthenticationManager bean을 게시하는 것은 매우 일반적인 요구사항이다. 예를 들어, Form login 대신 REST API를 통해 사용자를 인증하고 싶을 수 있다.

다음 구성을 사용하여 사용자 지정 인증 시나리오에 대해 이러한 AuthenticationManager를 게시할 수 있다:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((authorize) -> authorize
				.requestMatchers("/login").permitAll()
				.anyRequest().authenticated()
			);

		return http.build();
	}

	@Bean
	public AuthenticationManager authenticationManager(
			UserDetailsService userDetailsService,
			PasswordEncoder passwordEncoder) {
		DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
		authenticationProvider.setUserDetailsService(userDetailsService);
		authenticationProvider.setPasswordEncoder(passwordEncoder);

		return new ProviderManager(authenticationProvider);
	}

	@Bean
	public UserDetailsService userDetailsService() {
		UserDetails userDetails = User.withDefaultPasswordEncoder()
			.username("user")
			.password("password")
			.roles("USER")
			.build();

		return new InMemoryUserDetailsManager(userDetails);
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return PasswordEncoderFactories.createDelegatingPasswordEncoder();
	}

}

이 구성을 적용하면 다음과 같이 AuthenticationManager를 사용하는 @RestController를 만들 수 있다:

@RestController
public class LoginController {

	private final AuthenticationManager authenticationManager;

	public LoginController(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	@PostMapping("/login")
	public ResponseEntity<Void> login(@RequestBody LoginRequest loginRequest) {
		Authentication authenticationRequest =
			UsernamePasswordAuthenticationToken.unauthenticated(loginRequest.username(), loginRequest.password());
		Authentication authenticationResponse =
			this.authenticationManager.authenticate(authenticationRequest);
		// ...
	}

	public record LoginRequest(String username, String password) {
	}

}

이 예시에서는 필요한 경우 인증된 사용자를 SecurityContextRepository에 저장하는 것이 사용자의 책임이다. 예를 들어, HttpSession을 사용하여 요청 간에 SecurityContext를 유지하는 경우 HttpSessionSecurityContextRepository를 사용할 수 있다.

Customize the AuthenticationManager

일반적으로 Spring Security는 사용자 이름/비밀번호 인증을 위해 DaoAuthenticationProvider로 구성된 AuthenticationManager를 내부적으로 빌드한다. 경우에 따라 Spring Security에서 사용하는 AuthenticationManager instance를 사용자 정의해야 할 수도 있다. 예를 들어, 캐시된 사용자에 대한 자격 증명 삭제 기능을 비활성화해야 할 수도 있다.

이를 위해 Spring Security의 전역 AuthenticationManager를 빌드하는 데 사용되는 AuthenticationManagerBuilder가 bean으로 게시된다는 점을 활용할 수 있다. 다음과 같이 Builder를 구성할 수 있다:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		// ...
		return http.build();
	}

	@Bean
	public UserDetailsService userDetailsService() {
		// Return a UserDetailsService that caches users
		// ...
	}

	@Autowired
	public void configure(AuthenticationManagerBuilder builder) {
		builder.eraseCredentials(false);
	}

}

또는 전역 AuthenticationManager를 재정의하도록 local AuthenticationManager를 구성할 수도 있다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			)
			.httpBasic(Customizer.withDefaults())
			.formLogin(Customizer.withDefaults())
			.authenticationManager(authenticationManager());

		return http.build();
	}

	private AuthenticationManager authenticationManager() {
		DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
		authenticationProvider.setUserDetailsService(userDetailsService());
		authenticationProvider.setPasswordEncoder(passwordEncoder());

		ProviderManager providerManager = new ProviderManager(authenticationProvider);
		providerManager.setEraseCredentialsAfterAuthentication(false);

		return providerManager;
	}

	private UserDetailsService userDetailsService() {
		UserDetails userDetails = User.withDefaultPasswordEncoder()
			.username("user")
			.password("password")
			.roles("USER")
			.build();

		return new InMemoryUserDetailsManager(userDetails);
	}

	private PasswordEncoder passwordEncoder() {
		return PasswordEncoderFactories.createDelegatingPasswordEncoder();
	}

}

참조

https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/index.html