
의존성 추가
@Table(name = "users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Getter
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "email", nullable = false, unique = true)
private String email;
@Column(name = "password")
private String password;
@Builder
public User(String email, String password, String auth) {
this.email = email;
this.password = password;
}
@Override //사용자가 가지고 있는 권한 목록을 반환
//사용자 이외의 권한이 없기에 user 권한만 담아 반환
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("user"));
}
//사용자의 id(고유한 값) 반환
//사용자를 식별할 수 있는 사용자 이름을 반환(
@Override
public String getUsername() {
return email;
}
//사용자의 비밀번호 반환
@Override
public String getPassword() {
return password;
}
//계정 만료 여부 반환
@Override
public boolean isAccountNonExpired() {
//만료되었는지 확인하는 로직
return true; //true -> 만료되지 않음
}
//계정 잠금 여부 반환
@Override
public boolean isAccountNonLocked() {
//계정 잠금되었는지 확인 하는 로직
return true; //true -> 잠금되지 않았음
}
//패스워드 만료 여부 반환
@Override
public boolean isCredentialsNonExpired() {
//패스워드가 만료되었는지 확인
return true;
}
//계정 사용 가능 여부 반환
@Override
public boolean isEnabled() {
//계정이 사용 가능한지 확인하는 로직
return true;
}
}
UserDetails 를 상속받은 User 클래스를 생성한다. UserDetails는 사용자의 정보를 담는 인터페이스이다. 다양한 메서드를 오버라이드 해서 사용할 수 있다.
@Service
@RequiredArgsConstructor
public class UserDetailService implements UserDetailsService {
private final UserRepository userRepository;
//사용자 이름(email)으로 사용자 정보를 가져오는 메서드
@Override
public User loadUserByUsername(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException(email));
}
}
UserDetailsService를 상속받은 UserDetailService를 생성한다. 고유한 값인 email로 사용자 정보를 가져오는 메서드를 오버라이드하여 DB에서 찾아온다.

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
private final UserDetailService userService;
//특정 HTTP 요청에 대한 웹 기반 보안 구성
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests((authorizeHttpRequests) -> //인가 설정
authorizeHttpRequests
.requestMatchers("/login", "/signup", "/user").permitAll()
.anyRequest().authenticated()
)
.formLogin((formLogin) -> //폼기반 로그인 설정
formLogin
.loginPage("/login")
.defaultSuccessUrl("/articles"))
.logout((logout) -> //로그아웃 설정
logout.logoutSuccessUrl("/login")
.invalidateHttpSession(true))
.csrf(AbstractHttpConfigurer::disable) //csrf 비활성화
.build();
}
만약 JWT같은 토큰방식 인증을 사용한다면,
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
이와 같이 Chain에 추가하여 스프링 시큐리티가 세션을 생성하지도 않고 존재해도 사용하지 않도록 무상태로 세션정책을 사용한다.
코드를 살펴보면, /login, /signup, /user url 요청의 경우 모두에게 허용하고, 다른 request는 인증이 필요하도록 설정한다.
폼 기반 로그인을 사용하는데, /login 페이지에서 로그인에 성공하면 /articles 로 리다이렉트되고, 로그아웃이 성공하면, /login 으로 리다이렉트되고 세션이 삭제된다.
csrf는 일단 비활성화 하여 GET이외의 HTTP METHOD도 접근 가능하도록 하였다.
WebSecurityConfig 파일에 추가한다.
//인증 관리자 관련 성정
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http,
BCryptPasswordEncoder bCryptPasswordEncoder,
UserDetailService userService) throws Exception {
AuthenticationManagerBuilder sharedObject = http.getSharedObject(AuthenticationManagerBuilder.class);
sharedObject
.userDetailsService(userService) //사용자 정보 조회
.passwordEncoder(bCryptPasswordEncoder);
return sharedObject.build();
}
//패스워드 인코더로 사용할 빈 등록
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
AuthenticationManager를 빈으로 등록한다. 사용자 인증을 처리하고, UserDetailsService를 통해 사용자 정보를 가져오며, BCryptPasswordEncoder를 사용하여 비밀번호를 확인한다. BCryptPasswordEncoder를 빈으로 등록하여 비밀번호 인코더를 설정한다.
스프링 시큐리티 기본 코드는 이렇다. 나머지 파일들을 마저 작성한다.
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public Long save(UserRequestDTO.AddUserRequest dto) {
return userRepository.save(User.builder()
.email(dto.getEmail())
.password(bCryptPasswordEncoder.encode(dto.getPassword()))
.build())
.getId();
}
}
@Controller
@RequiredArgsConstructor
public class UserApiController {
private final UserService userService;
@PostMapping("/user")
public String signup(UserRequestDTO.AddUserRequest request) {
userService.save(request);
return "redirect:/login";
}
@GetMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder.getContext().getAuthentication());
return "redirect:/login";
}
}