[Spring Boot] 스프링 시큐리티 세션로그인 과정 간단 정리

Juice-Han·2024년 10월 31일
0

Spring Boot

목록 보기
2/3
post-thumbnail

스프링 시큐리티를 배우면서 회원가입 과정이 복잡하여 정리해보고자 글을 작성한다.

Spring Boot : 3.3.5 버전
Java : 17 버전

스프링 시큐리티란?

스프링에서 인증&인가를 편하게 구현할 수 있도록 도와주는 보안 관련 프레임워크다.

인증(Authentication) : 사용자가 누구인지 검증하는 과정
인가(Authorization) : 사용자의 역할에 따라 권한을 부여하는 과정

서버를 회사라고 비유한다면 회사 출입 카드를 사용하여 회사에 들어가는 과정이
인증을 하는 과정이고, 소속한 부서에 따라 출입가능한 사무실을 다르게 하는 것을 인가라고 한다.

스프링 시큐리티 인증 내부 구조

스프링 시큐리티의 내부 구조는 이렇다고 하는데
너무 복잡해서 일단 간단하게 정리하고 넘어가고자 한다.

스프링 시큐리티 인증 간략 내부 구조

이렇게 말이다.

출처 : 개발자 유미 유튜브 - 스프링 시큐리티 강의

전체적인 과정

전체적인 진행과정을 순서대로 보면서 내부 구조를 이해해보자

사용자의 로그인 요청
-> 스프링 시큐리티 필터 적용
-> UserDetailsService에서 loadUserByUsername함수 실행
-> UserDetails 객체 만들어서 시큐리티에 넘겨주기
-> 해당 정보를 바탕으로 인증 완료

사용자의 로그인 요청

로그인 폼

로그인 창에서 사용자가 계정 정보를 입력하고 로그인 요청을 보낸다.

스프링 시큐리티 필터 적용

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/", "/login","/loginProc","/join","joinProc").permitAll()
                        .requestMatchers("/admin").hasRole("ADMIN")
                        .requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
                        .anyRequest().authenticated()
                );

        http
                .formLogin((auth) -> auth
                        .loginPage("/login")
                        .loginProcessingUrl("/loginProc")
                        .permitAll()
                );

        http
                .csrf((auth) -> auth.disable());

        return http.build();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Spring Security의 전체적인 환경설정을 담당하는 클래스다.

filterChain이라는 함수를 보자.

먼저, authorizeHttpRequests함수가 있다.
이는 인가(authorization)를 해준다.
자세한 내용은 다음과 같다.

  • permitAll()은 모든 사람이 접근 가능하다는 뜻
  • hasRole("ADMIN")은 ADMIN 역할만 가진 유저만 접근 가능하다는 뜻
  • hasAnyRole("ADMIN", "USER")은 여러 역할 중 하나라도 적용되면 접근 가능하다는 뜻
  • anyRequest().authenticated()는 그 외 나머지 요청들은 로그인을 한 유저면 가능하다는 뜻

로그인 요청을 보내는 /joinProcpermitAll이므로
로그인이 안 된 유저도 접근 가능하다.

formLogin함수를 보면
로그인을 하는 페이지와 로그인을 진행하는 Url을 등록할 수 있다.
이를 통해 로그인을 어떻게 진행할 지 내가 지정할 수 있다.

그 외에도 csrf공격 방지 등 여러 가지 보안 관련 설정을 직접 커스텀 할 수 있다.

비밀번호 암호화에 필요한 BCryptPasswordEncoder도 정의해놓는다.
이는 회원가입 로직에 사용된다.

UserDetailsService 에서 loadUserByUsername함수 실행

로그인 요청이 들어오면 해당 데이터와 DB에 저장된 데이터를 비교해서
올바른 사용자 정보인지 확인해아 한다.

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<User> op_user = userRepository.findByUsername(username);
        if(op_user.isPresent()) {
            return new CustomUserDetails(op_user.get());
        }
        return null;
    }
}

UserDetailsService 인터페이스를 구현하는 CustomUserDetailsService 클래스를 작성한다.

필수로 구현해야하는 loadUserByUsername함수를 구현해준다.

이 함수는 UserRepository에서 User정보를 가져오고
해당 정보를 가지고 UserDetails 객체를 생성한다.

유저 엔티티는 다음과 같다.

@Setter
@Getter
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(unique = true)
    private String username;

    private String password;

    private String role; //ADMIN이나 USER 역할 부여용 컬럼
}

UserDetails 객체 만들어서 시큐리티에 넘겨주기

UserDetails객체는 스프링 시큐리티에서 활용하기 편하게
유저 정보를 정리해놓은 데이터(DTO)라고 보면 된다.

@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {

    private final User user;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() { // 유저 권한 리턴해주는 함수

        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole(); // 사용자의 권한을 리턴
            }
        });

        return collection;
    }

    @Override
    public String getPassword() { // 유저 비밀번호 리턴해주는 함수
        return user.getPassword();
    }

    @Override
    public String getUsername() { // 유저 아이디를 리턴해주는 함수
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() { // 계정이 만료되었는지 확인해주는 함수
        return true;
    }

    @Override
    public boolean isAccountNonLocked() { // 계정이 잠금되었는지 확인해주는 함수
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() { // 비밀번호가 만료되었는지 확인해주는 함수
        return true;
    }

    @Override
    public boolean isEnabled() { // 계정이 사용가능한지 확인해주는 함수
        return true;
    }
}

유저 DB에 아래 4가지 함수에 대한 정보를 저장하지 않아서 모두 true를 반환하도록 했다.

해당 정보를 바탕으로 인증 완료

스프링 시큐리티는 전달받은 UserDetails를 확인하여
로그인을 요청한 사용자가 올바른 사용자인지 확인하고 세션을 발급해준다.

느낀점

일단 처음이라 이렇게 간단하게 공부해도 양이 정말 많았다.

시큐리티 내부구조를 잘 이해할수록 서비스에 중요한 보안을 유지할 수 있기 때문에
내부구조를 제대로 이해할 때까지 열심히 공부해야겠다.

JWT에 OAuth까지.. 배워야할 게 산더미다.

공부 내용 출처

개발자 유미 유튜브

스프링을 쉽게 잘 가르쳐 주신다. 초보자라면 강추!

profile
배우고 기록하고
post-custom-banner

0개의 댓글