[Spring Security] 스프링 시큐리티 적용하기

수깡·2024년 2월 26일

Spring Security

목록 보기
4/11

🌟 build.gradle


의존성 추가

🌟 User Entity 생성

@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는 사용자의 정보를 담는 인터페이스이다. 다양한 메서드를 오버라이드 해서 사용할 수 있다.

🌟 UserDetailService 생성

@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에서 찾아온다.

🌟config -> security -> WebSecurityConfig 파일 추가

@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();
    }
  • @EnableWebSecurity : 모든 요청 URL이 스프링 시큐리티의 제어를 받도록 함. 내부적으로 SpringSecurityChain이 동작하여 URL 필터가 적용된다.
  • authorizeHttpRequests : http 요청에 대한 인가 설정 구성. 다양한 인가 규칙을 정의하고 경로별로 다른 권한 설정이 가능하다.
  • formLogin : 폼 기반 로그인을 설정한다. defaultSuccessUrl으로 로그인 성공 시 리다이렉트를 지정할 수 있다. logoutSuccessUrl도 마찬가지.
  • invaliateHttpSession : 로그아웃 이후 세션을 전체 삭제할지 여부, true면 세션 삭제.

만약 JWT같은 토큰방식 인증을 사용한다면,

                .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

이와 같이 Chain에 추가하여 스프링 시큐리티가 세션을 생성하지도 않고 존재해도 사용하지 않도록 무상태로 세션정책을 사용한다.

코드를 살펴보면, /login, /signup, /user url 요청의 경우 모두에게 허용하고, 다른 request는 인증이 필요하도록 설정한다.
폼 기반 로그인을 사용하는데, /login 페이지에서 로그인에 성공하면 /articles 로 리다이렉트되고, 로그아웃이 성공하면, /login 으로 리다이렉트되고 세션이 삭제된다.
csrf는 일단 비활성화 하여 GET이외의 HTTP METHOD도 접근 가능하도록 하였다.

🌟 AuthenticationManager

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를 빈으로 등록하여 비밀번호 인코더를 설정한다.

스프링 시큐리티 기본 코드는 이렇다. 나머지 파일들을 마저 작성한다.

🌟 UserService

@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();
    }
}

🌟 UserApiController

@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";
    }

}

0개의 댓글