회원가입 기능 구현 -1-

jihan kong·2022년 12월 14일
0
post-thumbnail

Spring Security 를 활용한 회원가입 기능 구현

스프링 시큐리티를 사용하기 위해 의존성을 추가.

pom.xml

<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

SecurityConfig 클래스 작성

예전에는 스프링 시큐리티 설정을 위해 WebSecurityConfigurerAdapter 를 extends 해서 configure 메소드를 오버라이딩 해서 구현했었다. 그런데 이제는 deprecated 되었기 때문에 다른 방식을 생각해야했다.

열심히 구글링해본 결과, SecurityFilterChain 를 아예 Bean 으로 등록해서 사용하는 방식을 추천했다.

(출처 : 스프링 공식 블로그)

위의 방식을 토대로 SecurityConfig 를 작성해보았다.

@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig {

    @Autowired
    MemberService memberService;

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

        http.exceptionHandling()
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint());         
        // 인증되지 않은 사용자가 리소스에 접근했을 때 수행되는 핸들러 등록
        return http.build();
    }

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

일단은 인증되지 않은 사용자의 리소스에 대한 접근 처리를 우선적으로 하였다.

회원가입 기능 구현

먼저, 각 멤버가 일반 유저인지, 관리자인지 구분할 수 있는 역할을 부여하기 위해 com.shop.constant 패키지에 Role.java 를 생성했다.

com.shop.constant.Role.java

package com.shop.constant;

public enum Role {
	USER, ADMIN
}

DTO 생성

회원 가입 화면으로부터 넘어오는 가입 정보를 담은 dto 생성
com.shop.dto.MemberFormDto.java

package com.shop.dto;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class MemberFormDto {

	private String name;
    
    private String email;
    
    private String password;
    
    private String address;
    
}

Member Entity 생성

com.shop.entity.Member.java

package com.shop.entity;

import com.shop.constant.Role;
import com.shop.dto.MemberFormDto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.persistence.*;

@Entity
@Table(name="member")
@Getter
@Setter
@ToString
public class Member {     // 회원 정보 저장 엔티티

    @Id
    @Column(name="member_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @Column(unique = true)
    private String email;  // 회원은 이메일을 통해 유일하게 구분하므로 
    					   // unique 속성 지정
    private String password;

    private String address;

    @Enumerated(EnumType.STRING)  // enum의 순서가 바뀌지 않도록 하기위해 
    private Role role;            // enumType을 String으로 지정

    public static Member createMember(MemberFormDto memberFormDto,
                                      PasswordEncoder passwordEncoder) {

        Member member = new Member();
        member.setName(memberFormDto.getName());
        member.setEmail(memberFormDto.getEmail());
        member.setAddress(memberFormDto.getAddress());
        String password = passwordEncoder.encode(memberFormDto.getPassword());      
        // 스프링 시큐리티 설정 클래스에 등록한 BCryptPasswordEncoder Bean을 
        // 파라미터로 넘겨서 비밀번호 암호화
        member.setPassword(password);
        member.setRole(Role.ADMIN);
        return member;
    }
}

Member Repository 생성

com.shop.repository.MemberRepository.java

package com.shop.repository;

import com.shop.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {   

    Member findByEmail(String email);                         
}

Member Service 생성

com.shop.service.MemberService.java

package com.shop.service;

import com.shop.entity.Member;
import com.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
@Transactional   // 로직을 처리하다가 에러가 발생하였다면, 변경된 데이터를 로직을 수행하기 이전 상태로 콜백
@RequiredArgsConstructor                                 
public class MemberService implements UserDetailsService {  

    private final MemberRepository memberRepository;

    public Member saveMember(Member member){
        validateDuplicateMember(member);
        return memberRepository.save(member);
    }

    private void validateDuplicateMember(Member member) {
        Member findMember = memberRepository.findByEmail(member.getEmail());
        if(findMember != null) {
            throw new IllegalStateException("이미 가입된 회원입니다.");   
            // 이미 가입된 회원인 경우 예외 처리
        }
    }
}

프로젝트 구조

Constant : ItemSellStatus, Role
Dto : MemberFormDto
Entity : Item, Member
Repository : ItemRepository, MemberRepository
Service : MemberService

배운 것✍️

  1. 회원가입 측면에서 각 계층을 바라보면...🤔
  • MemberFormDto(Dto) 에서는 회원가입 화면으로부터 넘어오는 가입정보 저장
  • Member(Entity) 에서는 회원정보를 저장
  • MemberRepository(Repository) 는 쿼리 메소드를 통해 회원 조회
  • MemberService(Service) 는 비즈니스 로직을 처리
  1. Spring Security 에서 인증/ 인가 실패에 따른 리다이렉트에 대해 학습했다.

    1) 아직 회원가입 하지 않은 회원 : 인증(Authentication) 의 문제
    2) 회원가입은 했으나 권한이 없는 회원 : 인가(Authorization) 의 문제

인증은 AuthenticationEntryPoint 인터페이스, 인가는 AccessDeniedHandler 인터페이스를 통해 세분화하여 처리할 수 있다는 것을 알게 되었다.

profile
학습하며 도전하는 것을 즐기는 개발자

0개의 댓글