You Have To(운동 보조 앱) [1]

10000DOO·2023년 1월 14일
1

YouHaveTo

목록 보기
2/12

📌회원가입

먼저 DB테이블 구조는 사진과 같습니다.

스프링 시큐리티를 사용하여 회원가입을 구현했습니다.

⚙️동작 원리

  1. 클라이언트에서 회원가입 요청.
  2. 회원 중복검사 후 문제 없으면 회원가입.
  3. 가입된 유저이름 반환.

SecurityConfig.java

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtTokenProvider jwtTokenProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable()
                .csrf().disable()
                .formLogin().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .requestMatchers("/signup").permitAll()
                .requestMatchers("/signin").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

.httpBasic().disable().csrf().disable() : rest api를 사용하기 때문에 비활성화.

rest api를 이용한 서버라면, session 기반 인증과는 다르게 stateless하기 때문에 서버에 인증정보를 보관하지 않는다. rest api에서 client는 권한이 필요한 요청을 하기 위해서는 요청에 필요한 인증 정보를(OAuth2, jwt토큰 등)을 포함시켜야 한다. 따라서 서버에 인증정보를 저장하지 않기 때문에 굳이 불필요한 csrf 코드들을 작성할 필요가 없다.

.formLogin().disable() : 스프링 시큐리티에서 제공하는 기본 폼로그인을 사용하지 않는다. (프론트 부분을 앱으로 만들기 때문)

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) : JWT를 사용하여 로그인을 구현할 것이기 때문에 세션을 사용하지 않는다.

.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class); : JWT인증을 위해 만든 필터를 UsernamePasswordAuthenticationFilter필터 이전에 사용.

PasswordEncoder : 비밀번호를 암호화 해서 저장하기 위해 사용.


Member.java

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

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

    private String username;

    private String email;

    private String loginId;

    private String password;

    private String field; //유저 활동 지역

    @Enumerated(EnumType.STRING)
    private MemberType role;

    @OneToMany(mappedBy = "postWriter")
    private List<Post> posts = new ArrayList<>();
    @Embedded //생성일 수정일 삭제일
    private DateTime dateTime;

    @OneToMany(mappedBy = "member")
    private List<Diary> diaries = new ArrayList<>();

    @OneToMany(mappedBy = "commentWriter")
    private List<Comment> comments = new ArrayList<>();

    @OneToMany(mappedBy = "member")
    private List<Challenge> challenges = new ArrayList<>();

    @Builder
    public Member(String username, String email, String loginId, String password, String field, MemberType role, DateTime dateTime) {
        this.username = username;
        this.email = email;
        this.loginId = loginId;
        this.password = password;
        this.field = field;
        this.role = role;
        this.dateTime = dateTime;
    }
}

SignUpRequestDto.java

@Data
public class SignUpRequestDto {

    @NotBlank(message = "아이디는 필수 입니다.")
    @Length(min = 5,max = 20,message = "아이디는 5~20자 입니다.")
    private String loginId;

    @NotBlank(message = "비밀번호는 필수 입니다.")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$@$!%*?&])[A-Za-z\\d$@$!%*?&]{8,20}",
            message = "비밀번호는 영문 대 소문자, 숫자, 특수문자($@$!%*?&)를 사용하세요. 비밀번호는 8~20자 입니다.")
    private String password;

    @NotBlank(message = "사용자명은 필수입니다.")
    private String username;

    @NotBlank(message = "이메일은 필수 입니다.")
    @Email(message = "잘못된 형식의 이메일입니다.")
    private String email;

    private String field;//활동지역
}

MemberController.java

@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    @PostMapping("/signup")
    public ResponseResult signUp(@RequestBody @Valid SignUpRequestDto signUpRequestDto){
        return memberService.join(signUpRequestDto);
    }
}

@Valid를 통해 유효성 검사를 진행합니다.


MemberService.java

@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class MemberService {

    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;

    public ResponseResult join(SignUpRequestDto signUpRequestDto){
        String result = validateDuplicateMember(signUpRequestDto);

        if (StringUtils.hasText(result)){
            throw new IllegalArgumentException(result);
        }else {
            Member member = Member.builder()
                    .username(signUpRequestDto.getUsername())
                    .email(signUpRequestDto.getEmail())
                    .loginId(signUpRequestDto.getLoginId())
                    .password(passwordEncoder.encode(signUpRequestDto.getPassword()))
                    .field(signUpRequestDto.getField())
                    .role(MemberType.USER)
                    .dateTime(new DateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
                            LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
                            null))
                    .build();

            memberRepository.save(member);
            log.info(member.getUsername()+" 회원가입 완료");

            return new ResponseResult(HttpStatus.CREATED.value(), member.getUsername());
        }
    }

    private String validateDuplicateMember(SignUpRequestDto signUpRequestDto) {
        Optional<Member> findIdMember = memberRepository.findByLoginId(signUpRequestDto.getLoginId());
        Optional<Member> findEmailMember = memberRepository.findByEmail(signUpRequestDto.getEmail());
        Optional<Member> findUnameMember = memberRepository.findByUsername(signUpRequestDto.getUsername());
        String errorMessage = "";
        if (findIdMember.isPresent()){
            errorMessage += "이미 존재하는 아이디입니다. ";
        }
        if (findEmailMember.isPresent()){
            errorMessage += "이미 존재하는 이메일입니다. ";
        }
        if (findUnameMember.isPresent()){
            errorMessage += "이미 존재하는 이름입니다. ";
        }
        return errorMessage;
    }  
}

validateDuplicateMember를 통해 회원가입 시 아이디, 비밀번호, 유저이름에 대한 중복 검사를 실시합니다.
중복되는 항목이 있으면 IllegalArgumentException을 발생시킵니다.

중복되는 항목이 없다면 Member객체를 만들어 MemberRepository로 보내 DB에 저장합니다.
비밀번호는 Bean으로 등록해두었던 passwordEncoder의 encode()를 사용해서 암호화 후 저장했다.


MemberRepository.java

public interface MemberRepository extends JpaRepository<Member,Long> {

    Optional<Member> findByLoginId(String loginId);

    Optional<Member> findByEmail(String email);

    Optional<Member> findByUsername(String username);
}

ExceptionController.java

@Slf4j
@RestControllerAdvice(assignableTypes = MemberController.class)
public class ExceptionController {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ExceptionResponse validHandle(MethodArgumentNotValidException exception){

        List<ErrorMessageDto> exceptionResponses = new ArrayList<>();
        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();

        for (FieldError fieldError : fieldErrors) {
            ErrorMessageDto errorMessageDto = new ErrorMessageDto();
            errorMessageDto.setError(fieldError.getDefaultMessage());
            log.error(fieldError.getDefaultMessage());
            exceptionResponses.add(errorMessageDto);
        }
        return new ExceptionResponse(HttpStatus.BAD_REQUEST.value(), exceptionResponses);
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ExceptionResponse signUpHandle(IllegalArgumentException exception){

        log.error(exception.getMessage());
        return new ExceptionResponse(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
    }
}

validHandle 메서드는 SignUpRequestDto의 유효성 검사가 실패했을 때 발생하는 예외를 처리한다.
signUpHandle 메서드는 회원가입 시 중복된 항목에 대해 발생시킨 IllegalArgumentException을 처리한다.


📌결과

성공

실패

📚참고자료

https://velog.io/@woohobi/Spring-security-csrf란
https://gksdudrb922.tistory.com/217

profile
iOS 개발자 지망생 https://github.com/10000DOO

0개의 댓글