Spring Security를 사용하여 사용자 이름과 비밀번호로 인증을 구성하는 코드에 대해 자세히 설명드리겠습니다.
@Configuration 및 @EnableWebSecurity 어노테이션@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Configuration: 이 클래스가 Spring 설정 클래스임을 나타냅니다. Spring 애플리케이션에서 설정 클래스로 인식하게 합니다.@EnableWebSecurity: Spring Security를 활성화합니다. 이 어노테이션을 사용하면 Spring Security의 기본 보안 기능이 적용됩니다.SecurityFilterChain 빈 설정@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
SecurityFilterChain: Spring Security의 핵심 구성 요소로, HTTP 요청에 대해 필터 체인을 구성합니다.authorizeHttpRequests: HTTP 요청에 대한 권한 설정을 정의하는 메서드입니다.anyRequest().authenticated(): 모든 요청이 인증을 거쳐야 함을 의미합니다. 즉, 로그인하지 않은 사용자는 요청을 처리할 수 없습니다.httpBasic(Customizer.withDefaults()): HTTP Basic 인증 방식을 사용하겠다는 설정입니다.formLogin(Customizer.withDefaults()): 폼 로그인 방식을 사용하겠다는 설정입니다.이 부분은 Spring Security가 HTTP Basic 인증과 폼 로그인 방식으로 인증을 처리하도록 구성한 것입니다.
UserDetailsService 빈 설정@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
UserDetailsService: 사용자 정보를 제공하는 서비스 인터페이스입니다. Spring Security는 사용자 인증을 위해 UserDetailsService를 사용하여 사용자 정보를 조회합니다.User.withDefaultPasswordEncoder(): UserDetails 객체를 생성하는 유틸리티 메서드입니다.withDefaultPasswordEncoder(): 기본 비밀번호 인코더(Password Encoder)를 사용하여 비밀번호를 인코딩합니다. 이는 테스트용 설정으로, 실제 운영 환경에서는 권장되지 않습니다.username("user"): 사용자 이름을 "user"로 설정합니다.password("password"): 비밀번호를 "password"로 설정합니다.roles("USER"): 사용자의 권한을 "USER"로 설정합니다. 권한은 Spring Security에서 역할(Role) 개념으로 관리됩니다..build(): 위 설정에 따라 UserDetails 객체를 빌드합니다.위의 설정은 메모리에 사용자 정보를 저장하여 관리하는 InMemoryUserDetailsManager를 생성합니다. 이 예제에서는 사용자 이름이 "user"이고 비밀번호가 "password"인 사용자를 메모리에 저장합니다.
이 설정을 통해 Spring Security는:
이렇게 설정하면 애플리케이션이 작동할 때, 로그인 화면이 나타나며 사용자 이름과 비밀번호("user" / "password")로 인증을 진행할 수 있습니다.
이전 구성은 다음 기능들을 자동으로 등록합니다:
UserDetailsService를 메모리에 등록하고 SecurityFilterChain과 연결합니다.AuthenticationManager와 함께 DaoAuthenticationProvider를 등록합니다.AuthenticationManager 빈을 생성하고 싶습니다AuthenticationManager를 커스터마이징하고 싶습니다이 내용은 다양한 사용자 인증 방식 및 Spring Security에서의 설정 방법을 안내합니다.
Spring Security에서 AuthenticationManager 빈을 게시하는 것은 사용자 정의 인증을 설정할 때 중요한 작업입니다. 특히, 웹 애플리케이션의 기본 폼 로그인(Form Login) 방식이 아닌 REST API로 사용자 인증을 처리하고자 할 때, AuthenticationManager 빈을 직접 구성하여 사용하면 효과적입니다.
AuthenticationManager:
AuthenticationManager는 인증 작업을 총괄하는 인터페이스입니다. Spring Security의 인증 과정을 관리하며, 요청이 들어오면 사용자 이름과 비밀번호를 검사하고, 적절한 인증 제공자(예: DaoAuthenticationProvider)를 통해 사용자를 인증합니다.AuthenticationManager를 자동으로 생성해 관리하지만, 사용자 정의 인증이 필요한 경우 AuthenticationManager 빈을 수동으로 등록해야 할 수 있습니다.왜 AuthenticationManager 빈을 게시해야 하는가?
@Service나 @Controller 같은 계층에서 AuthenticationManager가 필요합니다.AuthenticationManager 빈을 게시하면 Spring의 다른 컴포넌트에서 주입받아 사용할 수 있으므로, REST 엔드포인트 등 다양한 사용자 정의 인증 시나리오에서 활용 가능합니다.AuthenticationManager 빈 설정하기AuthenticationManager 빈을 게시하는 예제 구성을 작성해보겠습니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("{noop}password")
.roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
@EnableWebSecurity:
authenticationManagerBean() 메서드:
@Bean으로 AuthenticationManager를 생성합니다. 이 메서드를 통해 생성된 AuthenticationManager는 빈으로 게시되어, Spring 컨텍스트 내의 다른 클래스에서 @Autowired로 주입받아 사용할 수 있습니다.@Service 클래스에서 AuthenticationManager를 주입받아 활용할 수 있습니다.configure(AuthenticationManagerBuilder auth):
AuthenticationManagerBuilder를 사용하여 AuthenticationManager에 필요한 인증 공급자를 설정합니다.inMemoryAuthentication()을 사용하여 메모리 내에 사용자 정보를 저장하는 간단한 인증을 설정했습니다. 실제 운영 환경에서는 jdbcAuthentication()이나 ldapAuthentication()을 통해 데이터베이스 또는 LDAP 서버에서 사용자 정보를 인증하도록 구성할 수 있습니다.configure(HttpSecurity http):
anyRequest().authenticated()로 설정하고, httpBasic()을 통해 HTTP Basic 인증을 사용합니다.AuthenticationManager 사용 예시이렇게 구성된 AuthenticationManager를 REST API에서 활용하는 방식의 예를 살펴보겠습니다.
@RestController
public class AuthController {
private final AuthenticationManager authenticationManager;
@Autowired
public AuthController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest loginRequest) {
try {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
authenticationManager.authenticate(token);
return ResponseEntity.ok("인증 성공!");
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("인증 실패");
}
}
}
AuthController:
AuthenticationManager 주입:
AuthenticationManager는 AuthController에 주입되어 authenticationManager.authenticate() 메서드로 사용자의 인증을 처리합니다.login 메서드:
POST 요청으로 사용자 인증을 수행합니다.UsernamePasswordAuthenticationToken을 생성하여 사용자의 아이디와 비밀번호를 포함하고, authenticationManager.authenticate(token)을 호출하여 인증을 수행합니다.이 구조를 통해 Spring Security의 AuthenticationManager를 REST API에서 사용자 인증 목적으로 활용할 수 있습니다.
이 설정 파일은 Spring Security를 통해 사용자 인증을 구성하는 예제입니다. 이 코드는 SecurityFilterChain과 AuthenticationManager를 설정하여 Spring Security가 사용자 인증을 관리할 수 있도록 돕습니다. 이제 이 코드의 각 부분을 자세히 설명하겠습니다.
@Configuration 및 @EnableWebSecurity@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Configuration: 이 클래스가 Spring 설정 클래스임을 나타내며, 이 클래스를 통해 Spring Security의 보안 설정을 구성합니다.@EnableWebSecurity: Spring Security의 웹 보안 기능을 활성화합니다. 이 어노테이션을 사용하면 Spring Security의 기본 보안 설정이 적용됩니다.securityFilterChain 빈 정의@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/login").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
SecurityFilterChain: HTTP 요청을 처리하는 필터 체인을 구성합니다. Spring Security는 모든 HTTP 요청을 필터링하며, 이 과정에서 보안 정책을 적용합니다.authorizeHttpRequests: 요청에 대한 접근 권한을 설정합니다.requestMatchers("/login").permitAll(): /login URL로 들어오는 요청은 누구나 접근할 수 있도록 허용합니다. 즉, 로그인 페이지는 인증이 필요하지 않습니다.anyRequest().authenticated(): 나머지 모든 요청은 인증이 필요하다는 설정입니다. 인증된 사용자만 접근할 수 있습니다.http.build(): 설정을 완료하고 SecurityFilterChain을 반환합니다.AuthenticationManager 빈 정의@Bean
public AuthenticationManager authenticationManager(
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);
return new ProviderManager(authenticationProvider);
}
AuthenticationManager: Spring Security에서 사용자 인증을 수행하는 핵심 인터페이스입니다. 이 빈을 생성하여 사용자 정의 인증을 설정할 수 있습니다.DaoAuthenticationProvider: AuthenticationProvider 인터페이스의 구현체로, 데이터베이스나 메모리에 저장된 사용자 정보를 사용하여 인증을 수행합니다.setUserDetailsService(userDetailsService): 사용자 정보를 불러오는 UserDetailsService를 설정합니다.setPasswordEncoder(passwordEncoder): 비밀번호를 암호화하고 비교할 PasswordEncoder를 설정합니다.ProviderManager: 여러 AuthenticationProvider를 관리하며 인증 요청을 처리합니다. 이 경우, DaoAuthenticationProvider 하나만을 사용하여 인증을 수행합니다.UserDetailsService 빈 정의@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
UserDetailsService: 사용자 정보를 제공하는 인터페이스입니다. Spring Security는 이 인터페이스를 통해 사용자의 인증 정보를 로드합니다.User.withDefaultPasswordEncoder(): 사용자 정보를 설정할 때 사용할 유틸리티 메서드입니다.username("user"): 사용자 이름을 "user"로 설정합니다.password("password"): 비밀번호를 "password"로 설정합니다.roles("USER"): 사용자의 권한을 "USER"로 설정합니다. Spring Security는 역할(Role) 개념을 사용하여 사용자 권한을 관리합니다..build(): 설정을 마친 후, UserDetails 객체를 생성합니다.InMemoryUserDetailsManager: 메모리에 사용자 정보를 저장하고 관리하는 클래스입니다. 이 예제에서는 메모리 내에 사용자 "user"를 생성하여 인증에 사용합니다.PasswordEncoder 빈 정의@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
PasswordEncoder: 비밀번호를 암호화하고 비교하는 기능을 제공합니다. Spring Security는 암호화를 통해 보안을 강화합니다.PasswordEncoderFactories.createDelegatingPasswordEncoder(): 여러 암호화 방식을 지원하는 PasswordEncoder를 생성합니다. 기본적으로 Spring Security는 bcrypt를 사용하여 비밀번호를 암호화하지만, 필요에 따라 다른 인코딩 방식을 지정할 수도 있습니다./login 경로는 누구나 접근할 수 있지만, 그 외의 모든 요청은 인증이 필요합니다.AuthenticationManager 빈: DaoAuthenticationProvider를 통해 사용자 정보를 확인하고 비밀번호를 비교하여 인증을 수행합니다.이 구성을 통해 Spring Security는 /login 페이지 외 모든 요청에 대해 인증을 요구하며, 메모리에 저장된 사용자 정보로 인증을 수행할 수 있게 됩니다.
위의 설정이 완료되면, 다음과 같이 AuthenticationManager를 사용하는 @RestController를 생성할 수 있습니다:
인증을 위한 @RestController 생성
이 LoginController 클래스는 Spring Security의 AuthenticationManager를 사용하여 REST API로 사용자 인증을 수행하는 컨트롤러입니다. 이 코드를 자세히 설명드리겠습니다.
@RestController@RestController
public class LoginController {
@RestController: Spring MVC의 RESTful 컨트롤러를 나타내는 어노테이션입니다. 이 클래스 내의 메서드들은 JSON 형식의 응답을 반환합니다.AuthenticationManager 주입private final AuthenticationManager authenticationManager;
public LoginController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
AuthenticationManager: 이 클래스는 AuthenticationManager를 주입받아 인증 작업을 수행합니다.LoginController는 생성자를 통해 AuthenticationManager를 받습니다. 이를 통해 AuthenticationManager를 주입받아 사용할 수 있습니다.login 메서드@PostMapping("/login")
public ResponseEntity<Void> login(@RequestBody LoginRequest loginRequest) {
@PostMapping("/login"): 이 메서드는 /login 경로로 들어오는 POST 요청을 처리합니다. @RequestBody LoginRequest loginRequest: 클라이언트가 요청 본문에 보낸 LoginRequest 객체를 파라미터로 받아옵니다.LoginRequest는 사용자의 username과 password를 포함한 요청 객체입니다.Authentication authenticationRequest =
UsernamePasswordAuthenticationToken.unauthenticated(loginRequest.username(), loginRequest.password());
Authentication authenticationResponse =
this.authenticationManager.authenticate(authenticationRequest);
UsernamePasswordAuthenticationToken.unauthenticated():UsernamePasswordAuthenticationToken은 사용자 이름과 비밀번호를 사용하여 인증할 때 사용되는 클래스입니다..unauthenticated() 메서드를 사용하여 아직 인증되지 않은 토큰을 생성합니다. 이 토큰에는 사용자가 입력한 username과 password가 포함됩니다.this.authenticationManager.authenticate(authenticationRequest):AuthenticationManager의 authenticate 메서드를 호출하여 authenticationRequest 객체를 인증합니다.authenticate 메서드는 authenticationRequest를 검사하고, 인증에 성공하면 인증된 Authentication 객체를 반환합니다.LoginRequest 내부 클래스public record LoginRequest(String username, String password) {
}
LoginRequest: username과 password를 포함하는 요청 객체입니다. 이 클래스는 Java의 record 문법을 사용하여 간단하게 정의되었습니다.record는 불변 데이터 객체를 정의할 때 유용하며, username과 password 두 필드만을 갖는 데이터 클래스를 간단하게 표현합니다.이 컨트롤러는 사용자의 로그인 요청을 받아 AuthenticationManager를 통해 인증을 처리합니다.
1. /login 경로로 들어오는 POST 요청을 처리하고, 사용자가 보낸 username과 password를 LoginRequest 객체로 받습니다.
2. 인증되지 않은 UsernamePasswordAuthenticationToken을 생성하고, AuthenticationManager의 authenticate 메서드를 통해 인증을 수행합니다.
이 예제에서는 필요하다면 인증된 사용자를 SecurityContextRepository에 저장하는 것이 필요합니다. 예를 들어, 요청 간에 SecurityContext를 유지하려면 HttpSession을 사용하여 SecurityContext를 저장할 수 있습니다. 이를 위해 HttpSessionSecurityContextRepository를 사용할 수 있습니다.
AuthenticationManager는 일반적으로 Spring Security에서 내부적으로 DaoAuthenticationProvider로 구성되며, 사용자 이름과 비밀번호를 통해 인증을 수행합니다. 하지만 특정 상황에서는 Spring Security에서 사용하는 AuthenticationManager를 사용자 정의하고 싶을 때가 있습니다. 예를 들어, 캐시된 사용자에 대해 자격 증명 삭제(credential erasure) 기능을 비활성화하고자 할 때가 있습니다.
이를 위해, Spring Security에서 글로벌 AuthenticationManager를 빌드하는 데 사용되는 AuthenticationManagerBuilder가 빈(bean)으로 제공된다는 점을 활용할 수 있습니다. 이 AuthenticationManagerBuilder를 통해 AuthenticationManager를 사용자 정의할 수 있습니다.
이 SecurityConfig 클래스는 Spring Security에서 AuthenticationManagerBuilder를 사용해 글로벌 AuthenticationManager의 설정을 사용자 정의하는 예제입니다. 이 설정을 통해 AuthenticationManager의 자격 증명 삭제 기능을 비활성화할 수 있습니다. 아래에 각 부분을 설명하겠습니다.
@Configuration 및 @EnableWebSecurity@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Configuration: 이 클래스가 Spring 설정 클래스임을 나타냅니다.@EnableWebSecurity: Spring Security의 웹 보안 기능을 활성화합니다. 이 어노테이션을 사용하면 Spring Security의 기본 보안 설정이 적용됩니다.securityFilterChain 빈 정의@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// ...
return http.build();
}
SecurityFilterChain: HTTP 요청을 처리하는 필터 체인을 구성합니다. Spring Security는 모든 HTTP 요청을 필터링하며, 이 과정에서 보안 정책을 적용합니다.http.build(): 설정을 완료하고 SecurityFilterChain을 반환합니다.userDetailsService 빈 정의@Bean
public UserDetailsService userDetailsService() {
// Return a UserDetailsService that caches users
// ...
}
UserDetailsService: 사용자 정보를 제공하는 인터페이스입니다. Spring Security는 이 인터페이스를 통해 사용자의 인증 정보를 로드합니다.UserDetailsService를 사용하여 사용자를 캐시할 수 있는 구성을 합니다. (구체적인 구현은 생략됨)UserDetailsService 빈은 AuthenticationManager가 사용자를 인증할 때 필요한 사용자 정보를 제공합니다.configure 메서드@Autowired
public void configure(AuthenticationManagerBuilder builder) {
builder.eraseCredentials(false);
}
@Autowired: AuthenticationManagerBuilder를 자동으로 주입받기 위한 어노테이션입니다.configure(AuthenticationManagerBuilder builder):AuthenticationManagerBuilder는 글로벌 AuthenticationManager의 설정을 담당하는 객체입니다.eraseCredentials(false): 자격 증명 삭제 기능을 비활성화합니다. Spring Security는 기본적으로 보안을 위해 인증이 완료된 후 민감한 자격 증명 정보(예: 비밀번호)를 메모리에서 제거하지만, 이 설정을 통해 해당 기능을 끌 수 있습니다.대안적으로, 로컬 AuthenticationManager를 설정하여 글로벌 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();
}
}