[Spring]스프링 시큐리티로 로그인, 회원가입 구현_1(스프링 3.xx ver)

cielo ru·2024년 5월 22일
0

Spring

목록 보기
2/9

➰ 서론

지난 블로그에 이어서 스프링 시큐리티를 적용해 로그인과 회원가입을 구현해보려 한다.

➰ 개발환경

Spring Boot 3.1.3 + Spring Security 6.1.3 + mysql:8.0 + JPA

적용한 스프링 부트 버전과 시큐리티 버전에 따라 문법 등 지원하지 않는게 있을 수 있다.
코드 작성 기준 3.2.2 버전이 나왔지만 안정적으로 개발하고자 그 다음으로 최신 버전인 3.1.3 버전을 선택하여 개발하였다.


➰ 의존성 추가

Spring Security를 사용하기 위해 다음 라이브러리를 build.gradle에 추가해준다.

//security
implementation 'org.springframework.boot:spring-boot-starter-security'

➰ Config 설정

Spring Security에 필요한 bean을 추가해주는 config 클래스 파일을 추가해준다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private static final String[] WHITE_LIST = {
            "/user-service/**",
            "/",
            "/error"
    };

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

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .authorizeHttpRequests(request -> request
                        .requestMatchers(WHITE_LIST).permitAll()
                        .anyRequest().authenticated() //어떠한 요청이라도 인증 필요
                );
        return http.build();

    }

config 설정은 스프링 시큐리티 버전마다 다르다. 버전에 맞게 맞춰줘야 한다.

  • BCryptPasswordEncoder: DB에 비밀번호를 저장할 때 그대로 노출되면 안되기 때문에 암호화를 위해 BCryptPasswordEncoder클래스를 생성하여 빈 등록을 해주었다.

  • WHITE_LIST : 로그인과 회원가입 api과 "/error", "/" 는 인증없이 호출이 되어야 한다. 따라서 WHITE_LIST에 따로 관리해주었다. (현재 user-service/에는 인증이 필요 없는 /login , /register 밖에 없어서 다음과 같이 작성하였다.)

  • .anyRequest(). authenticated() : 그 외의 어떠한 요청이라도 인증이 필요하다.

  • .csrf(AbstractHttpConfigurer::disable) : 로컬에서 확인을 위해 csrf를 비활성화 해주었다.


➰ User Entity

먼저 User Entity를 구현해준다.

@Setter
@Getter
@Entity
@Table(name = "users")
@AllArgsConstructor
public class UserEntity {

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

    @Column(name = "product_id") // 외래 키 필드 추가
    private Long product_id;

    @Column(nullable = false, length = 50, unique = true)
    private String email;

    @Column(nullable = false, length = 50)
    private String name;

    @Column(nullable = false, length = 15)
    private String phoneNumber;

    @Column(nullable = false, length = 100)
    private String encryptedPwd;

    @Column(nullable = false)
    private boolean isApproved;

    private UserEntity(Optional<UserEntity> userEntity) {
        this.id = id;
        this.product_id = product_id;
        this.email = email;
        this.name = name;
        this.phoneNumber = phoneNumber;
        this.encryptedPwd = encryptedPwd;
        this.isApproved = isApproved;
    }

    public UserEntity() {
    }

    public static UserEntity of(Optional<UserEntity> userEntity) {
        return new UserEntity(userEntity);
    }

}
  • 이때 @Table(name = "users") 이렇게 users 로 테이블 이름을 지정해준다.
    만약 User 라고 클래스를 만들고 @Table(name = "users)라고 지정해주지 않으면 user라고 지정되게 되는데 user가 예약어이기 때문에 테이블이 만들어지지 않는다.
  • 인증시에 사용되는 아이디는 email 이고, 비밀번호는 password이다.

➰ MyUserDetailsService

MyUserDetailsServices는 Spring Security의 사용자 인증 정보를 관리하는 UserDetailsService 인터페이스를 구현한다.

@Service
@Transactional
@RequiredArgsConstructor
public class MyUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Optional<UserEntity> findOne = userRepository.findByEmail(email);
        UserEntity userEntity = findOne.orElseThrow(() -> new UsernameNotFoundException("없는 회원입니다"));

        return User.builder()
                .username(userEntity.getEmail())
                .password(userEntity.getEncryptedPwd())
                .authorities(new SimpleGrantedAuthority("ADMIN"))
                .build();
    }
}
  • 필자는 재사용성, 테스트 용이성, 그리고 유연성을 고려하여 UserDetails를 UserEntity와 합치지 않고 분리하였다. 따라서 loadUserByUsername 에서 username을 찾은 후 UserDetails로 변환해주는 과정을 추가해 주었다.

  • loadUserByUsername 메서드는 사용자의 이름(여기선 email)으로 사용자의 정보를 불러오는 역할을 한다.

  • 비밀번호와 관련된 것은 PasswordEncoder가 처리하고, 비밀번호를 확인하는 로직은 뒤에서 처리할 수 있기 때문에 일단 username으로 검색하는 것이다.


➰ RequestLogin

@Data
public class RequestLogin {

    @Email
    @NotNull(message = "이메일을 입력해주세요")
    @Size(min = 5, message = "이메일은 5자 이상이어야 합니다")
    private String email;

    @NotNull(message = "비밀번호를 입력해주세요")
    @Size(min = 8, message = "비밀번호는 8자 이상이어야 합니다")
    private String pwd;
}

➰ RequestUser

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class RequestUser {

    @NotNull(message = "이메일을 입력해주세요")
    @Size(min = 5, message = "이메일은 5자 이상이어야 합니다")
    @Email
    private String email;

    @NotNull(message = "비밀번호를 입력해주세요")
    @Size(min = 8, message = "비밀번호는 8자 이상이어야 합니다")
    private String pwd;

    @NotNull(message = "이름을 입력해주세요")
    @Size(min = 2, message = "이름은 2자 이상이어야 합니다")
    private String name;

    @NotNull(message = "전화번호를 입력해주세요")
    @Size(min = 10, max = 15, message = "전화번호는 10자에서 15자 사이여야 합니다")
    private String phoneNumber;

}

로그인과 회원가입을 위한 requestDto를 작성해준다.


다음 포스팅에서는 이어서 JWT 를 이용한 인증 과정에 대해 작성해보겠습니다.

➰ 참조

https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/index.html#publish-authentication-manager-bean

https://curiousjinan.tistory.com/entry/spring-boot-security-userdetailservice-dto-8

profile
Cloud Engineer & BackEnd Developer

0개의 댓글