개발자 취업준비/springboot

스프링 시큐리티 실전 적용하기1 스프링시큐리티 설정

naspeciallist 2025. 3. 25. 20:46

 


이 글은 스프링부트3 백엔드 개발자 되기 책을 바탕으로 공부한 내용을 정리한 게시글 입니다.

 

스프링시큐리티는 스프링 어플리케이션의 보안(인증,인가)를 담당하는 스프링 하위 프레임워크입니다. 지금부터 스프링 시큐리티를 사용해 인증 및 인가의 기능을 구현하겠습니다.

 

 

1. 회원 도메인 만들기


 

먼저 회원정보를 저장 할 테이블을 만들겠습니다. 

 

일단 스프링 시큐리티를 사용하기 위해 의존성을 추가해줘야 합니다.

dependencies {
	// 스프링 시큐리티를 사용하기 위한 스타터 추가
    implementation 'org.springframework.boot:spring-boot-starter-security'
    // 타임리프에서 스프링 시큐리티를 사용하기 위한 의존성 추가
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
    // 스프링 시큐리티를 테스트하기 위한 의존성 추가
    testImplementation 'org.springframework.security:spring-security-test'
}

 

 

그 다음에는 엔티티를 만들겠습니다. 회원 엔티티와 매핑할 테이블의 구조는 아래와 같습니다. 아래 구조를 참고해 회원 엔티티를 만들겠습니다.

 

컬럼명 자료형 null 허용 설명
id BIGING N 기본키 일련번호, 기본키
email VARCHAR(255) N   이메일
password VARCHAR(255) N   패스워드(암호화하여저장)
created_at DATETIME N   생성 일자
updated_at DATETIME N   수정일자

 

User.java라는 파일을 생성하고 스프링 시큐리티에서 제공하는 UserDetails 클래스를 상속하는 User 클래스를 만듭니다.

 

[UserDetails란?]

  • org.springframework.security.core.userdetails.UserDetails는 스프링 시큐리티에서 사용자 인증 정보를 담기 위한 인터페이스입니다.
  • 이 인터페이스를 구현해서 직접 커스텀 사용자 클래스를 만들고,스프링 시큐리티에 해당 클래스를 전달해 인증·인가에 사용합니다.
  • 해당 객체를 이용하면 필수 오버라이드 메서드를 여러개를 사용하여 인증 정보를 가져와야 합니다.

아래 User엔티티를 구현할 코드인 User클래스에 UserDetails를 상속하여 해당 객체의 인증정보를 가져올수 있게 구현하겠습니다.

@Table(name = "users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
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", nullable = false)
    private String password;

    @Builder
    public User(String email, String password, String auth) {
        this.email = email;
        this.password = password;
    }


    @Override // 권한 반환
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("user"));
    }
	
    @Override // 사용자의 id를 반환(고유한 값)
    public String getUsername() {
        return email;
    }

    @Override // 사용자의 패스워드 반환
    public String getPassword() {
        return password;
    }

    @Override 계정 만료 여부 반환
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override //계정 잠금 여부 반환
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override // 패스워드 만료 여부 반환
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override // 계정 사용 가능 여부 반환
    public boolean isEnabled() {
        return true;
    }
    }

 

 

위에 설명했던 것처럼 UserDetails를 구현하여 User에 대한 인증,인가를 처리하기 위해 많은 메소드를 오버라이드 하였습니다. 각 오버라이드 한 메서드에 대해서 자세하게 설명하겠습니다.

 

1. getAuthorities()

반환 타입은 Collection<? extendsGrantedAuthority> 입니다. 사용자가 가지고 있는 권한의 목록을 반환합니다. 위 코드에는 사용자 이외의 권한이 없기 때문에 user권한만 담아 반환합니다.

 

2. getUsername()

반환 타입은 String입니다. 사용자가 식별할 수 있는 사용자의 이름을 반환합니다. 이때 사용되는 사용자의 이름은 반드시 고유해야 합니다. 위의 코드는 유니크속성이 적용된 이메일을 반환합니다.

 

3. getPassword()

반환 타입은 String 입니다. 사용자의 비밀번호를 반환합니다. 이때 저장되어 있는 비밀번호는 암호화해서 저장해야 합니다.

 

4. isAccountNotExpired()

반환 타입은 boolean입니다. 계정이 만료되었는지 확인하는 메서드 입니다. 만료여부를 확인하고 만료되지 않았을 때 true를 반환합니다.

 

5. isAccountNotLocked() 

반환타입은 boolean 입니다. 계정이 잠금되었는지 확인하는 메서드입니다. 계정의 잠금여부를 확인하고 만약 잠금되지 않았다면 true를 반환합니다.

 

6. isCredentialsNonExpired()

반환타입은 boolean입니다. 비밀번호가 만료되었는지 확인하는 메서드입니다. 비밀번호의 만료여부를 확인하고 만약 만료가 되지 않았다면 true를 반환합니다.

 

7. isEnabled()

반환타입은 boolean입니다. 계정이 사용가능한지 확인하는 메서드 입니다. 만약 사용가능하다면 true를 반환합니다.

 

 

 

2. 리포지토리 만들기


 

 

이제 만들어 놓은 User엔티티에 대해 데이터 작업(CRUD)를 처리해줄 리포지토리를 작성하겠습니다.

UserRepository.java 파일을 만들고 아래와 같이 인터페이스를 생성하겠습니다.

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

 

 

 

3. 서비스 코드 만들기


 

엔티티와 리포지터리가 완성되었으니 스프링 시큐리티에서 로그인을 진행 할 때 사용자의 정보를 가져오는 코드를 작성하겠습니다. UserDetailService.java 파일을 생성하여 로직을 작성하겠습니다.

 

@RequiredArgsConstructor
@Service
public class UserDetailService implements UserDetailsService { //스프링시큐리티에서 사용자의 정보를 가져오는 인터페이스

    private final UserRepository userRepository;

    @Override // 사용자 이름(email)으로 사용자의 정보를 가져오는 메서드
    public User loadUserByUsername(String email) {
        return userRepository.findByEmail(email)
                 .orElseThrow(() -> new IllegalArgumentException((email)));
    }
}

 

스프링 시큐리티에서 사용자의 정보를 가져오는 UserDetailsService인터페이스를 구현합니다. UserDetailsService는 스프링 시큐리티에서 제공하는 인터페이스 입니다. 사용자의 인증정보를 어떻게 가져올것인지에 대해 정의하는 인터페이스이며 스프링 시큐리티는 로그인 시 이 인터페이스의 loadUserByUsername() 메서드를 호출해서 사용자의 정보를 가져옵니다.

 

 

4. 시큐리티 설정하기


 

인증을 위한 도메인과 리포지터리 서비스가 완성되었으니 실제 인증처리를 하는 시큐리티 설정 파일 WebSecurityConfig.java를 작성하겠습니다.

 

@RequiredArgsConstructor
@Configuration
public class WebSecurityConfig {

    private final UserDetailService userService;

    @Bean // 스프링 시큐리티 기능 비활성화
    public WebSecurityCustomizer configure() {
        return (web) -> web.ignoring()
                .requestMatchers(toH2Console())
                .requestMatchers("/static/**");
    }

    @Bean // 특정 HTTP 요청에 대한 웹 기반 보안 구성
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeRequests() // 인증, 인가 설정
                    .requestMatchers("/login", "/signup", "/user").permitAll()
                    .anyRequest().authenticated()
                .and()
                .formLogin() // 폼 기반 로그인 설정
                    .loginPage("/login")
                    .defaultSuccessUrl("/articles")
                .and()
                .logout() // 로그아웃 설정
                    .logoutSuccessUrl("/login")
                    .invalidateHttpSession(true)
                .and()
                .csrf().disable() csrf 비활성화
                .build();
    }

    @Bean // 인증 관리자 관련 설정
    public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userService) // 사용자 정보 서비스 설정
                .passwordEncoder(bCryptPasswordEncoder)
                .and()
                .build();
    }

    @Bean // 패스워드 인코더로 사용할 빈 등록
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

첫번째로 스프링 시큐리티에 기능을 사용하지 않도록 제한하는  WebSecurityCustomizer configure()입니다. 이 메서드를 이용하여 인증 및 인가를 적용하지 않을 곳을 설정해 줍니다. 일반적으로 정적 리소스(이미지,HTML 파일)에 설정합니다. 만약 정적리소스를 등록하지 않는다면 비로그인 시 페이지의 리소스가 제대로 불러오지지 않는 문제점이 발생할 수 있습니다. static 하위 경로에 있는 리소스와 h2의 데이터를 확인하는데 사용하는 h2-console하위 url을 대상으로 ignoring()메서드를 사용합니다.

 

SecurityFilterChain filterChain(HttpSecurity http) 을 통해 특정 HTTP 요청에 대해 웹 기반 보안을 구성합니다. 이 메서드에서 인증/인가 및 로그인, 로그아웃 관련 설정 할 수 있습니다. 

 

requestMatcher()을 통해 특정 요청과 일치하는 url에 대한 엑세스를 설정합니다.  

PermitAll()을 통해 누구나 접근이 가능하게 설정합니다. 즉 /login, /signup, /user로 요청이 오면 인증/인가 없이도 접근 할 수 있습니다.

anyRequest()는 위에서 설정한 url 이외의 요청에 대해서 설정합니다.

authenticated() 별도의 인가는 필요하지 않지만 인증이 성공된 상태여야 접근 할 수 있습니다.

 

.formlogin은 폼 기반 로그인을 설정합니다. loginPage()를 통해 로그인 페이지의 경로를 설정합니다. defaultSuccessUrl()을 통해 로그인이 완료되었을 때 이동할 경로를 설정합니다.

 

csrf().disable()을 통해서 CSRF설정을 비활성화 합니다. CSRF의 공격을 방지하기 위해 활성화 시킬 수도 있습니다.

 

authenticationManager를 통해 인증 관리자를 설정해 줍니다. 사용자 정보를 가져올 서비스를 재정의하거나 인증방법등을 설정할 때 사용합니다.

 

UserDetailsService()를 통해 사용자 정보를 가져올 서비스를 설정합니다. 이때 설정하는 서비스 클래스는 반드시 UserDetailsService를 상속받은 클래스여야 합니다.

PasswordEncoder()를 통해 비밀번호를 암호화 하기 위한 인코더를 설정합니다.

 

bCryptPasswordEncoder() 를 이용하여 패스워드 인코더를 빈으로 등록합니다.

 

이제 시큐리티 설정을 완료하였습니다.