Skip to content

Commit

Permalink
feat: 회원가입, 로그인, 로그아웃, 토큰 재발급 기능 구현(Fastcampus-Final-Team3#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
hybiis committed Oct 18, 2023
1 parent bb77f12 commit 34b9037
Show file tree
Hide file tree
Showing 12 changed files with 412 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.javajober.member.controller;

import javax.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.javajober.core.util.ApiResponse;
import com.javajober.exception.ApiStatus;
import com.javajober.exception.ApplicationException;
import com.javajober.member.dto.MemberLoginRequest;
import com.javajober.member.dto.MemberLoginResponse;
import com.javajober.member.dto.MemberSignupRequest;
import com.javajober.member.dto.MemberSignupResponse;
import com.javajober.member.service.MemberService;
import com.javajober.refreshToken.service.RefreshTokenService;
import com.javajober.refreshToken.dto.RefreshTokenRequest;

@RestController
@Validated
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
private final RefreshTokenService refreshTokenService;

public MemberController(MemberService memberService,
RefreshTokenService refreshTokenService) {
this.memberService = memberService;
this.refreshTokenService = refreshTokenService;
}

@PostMapping("/signup")
public ResponseEntity<ApiResponse.Response<MemberSignupResponse>> signup(@RequestBody @Valid MemberSignupRequest memberSignupRequest,
BindingResult bindingResult) {

MemberSignupResponse data = memberService.signup(memberSignupRequest, bindingResult);

return ApiResponse.response(ApiStatus.OK, "회원가입에 성공했습니다.", data);
}

@PostMapping("/login")
public ResponseEntity<ApiResponse.Response<MemberLoginResponse>> login(@RequestBody @Valid MemberLoginRequest loginDto, BindingResult bindingResult) {
try {

MemberLoginResponse data = memberService.login(loginDto,bindingResult);

return ApiResponse.response(ApiStatus.OK, "로그인에 성공했습니다.", data);
} catch (IllegalArgumentException e) {

throw new ApplicationException(ApiStatus.NO_PERMISSION, "로그인에 실패했습니다.");
}
}

@DeleteMapping("/logout")
public ResponseEntity logout(@RequestBody RefreshTokenRequest refreshTokenRequest) {

refreshTokenService.deleteRefreshToken(refreshTokenRequest.getRefreshToken());

return ApiResponse.response(ApiStatus.OK, "로그인아웃에 성공했습니다.", null);
}

@PostMapping("/refreshToken")
public ResponseEntity<ApiResponse.Response<MemberLoginResponse>> requestRefresh(@RequestBody RefreshTokenRequest refreshTokenRequest) {

MemberLoginResponse data = refreshTokenService.findRefreshToken(refreshTokenRequest);

return ApiResponse.response(ApiStatus.OK, "토큰 재발급에 성공했습니다.", data);
}
}
5 changes: 4 additions & 1 deletion src/main/java/com/javajober/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

@Setter
@Getter
@Table(name = "member")
@EntityListeners(AuditingEntityListener.class)
Expand Down Expand Up @@ -49,7 +52,7 @@ public class Member {
@Column(name = "deleted_at")
private LocalDateTime deletedAt;

protected Member() {
public Member() {

}

Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/javajober/member/dto/MemberLoginRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.javajober.member.dto;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

import lombok.Getter;

@Getter
public class MemberLoginRequest {
public MemberLoginRequest() {

}

@NotEmpty
@Pattern(regexp = "^[a-zA-Z0-9+-\\_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")
private String email;

@NotEmpty
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*\\W).{8,20}$") // 영문, 특수문자 8자 이상 20자 이하
private String password;
}
33 changes: 33 additions & 0 deletions src/main/java/com/javajober/member/dto/MemberLoginResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.javajober.member.dto;

import com.javajober.member.domain.Member;

import lombok.Builder;
import lombok.Getter;

@Getter
public class MemberLoginResponse {
private String accessToken;
private String refreshToken;
private Long memberId;
private String nickname;

private MemberLoginResponse(){

}

@Builder
public MemberLoginResponse(final String accessToken, final String refreshToken, final Long memberId, final String nickname){
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.memberId = memberId;
this.nickname = nickname;
}

public MemberLoginResponse(final Member member, final String accessToken, final String refreshToken){
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.memberId = member.getId();
this.nickname = member.getMemberName();
}
}
45 changes: 45 additions & 0 deletions src/main/java/com/javajober/member/dto/MemberSignupRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.javajober.member.dto;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

import com.javajober.member.domain.Member;
import com.javajober.member.domain.MemberShipType;

import lombok.Getter;

@Getter
public class MemberSignupRequest {

@NotEmpty
@Pattern(regexp = "^[a-zA-Z0-9+-\\_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
message = "이메일 형식을 맞춰야합니다")
private String email;

@NotEmpty
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[~!@#$%^&*()+|=])[A-Za-z\\d~!@#$%^&*()+|=]{7,16}$",
message = "비밀번호는 영문+숫자+특수문자를 포함한 8~20자여야 합니다")
private String password;

@NotEmpty
@Pattern(regexp = "^[a-zA-Z가-힣\\\\s]{2,15}",
message = "이름은 영문자, 한글, 공백포함 2글자부터 15글자까지 가능합니다.")
private String name;

private String phoneNumber;

private MemberShipType memberShip;

private MemberSignupRequest(){

}

public static Member toEntity(final MemberSignupRequest memberSignupRequest){
return Member.builder()
.memberEmail(memberSignupRequest.getEmail())
.memberName(memberSignupRequest.getName())
.phoneNumber(memberSignupRequest.getPhoneNumber())
.memberShip(memberSignupRequest.getMemberShip())
.build();
}
}
41 changes: 41 additions & 0 deletions src/main/java/com/javajober/member/dto/MemberSignupResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.javajober.member.dto;

import java.time.LocalDateTime;

import com.javajober.member.domain.Member;
import com.javajober.member.domain.MemberShipType;

import lombok.Builder;
import lombok.Getter;

@Getter
public class MemberSignupResponse {
private Long memberId;
private String email;
private String name;
private String phoneNumber;
private MemberShipType memberShip;
private LocalDateTime regDate;

private MemberSignupResponse(){

}
@Builder
public MemberSignupResponse(final Long memberId, final String email, final String name, final String phoneNumber, final MemberShipType memberShip, final LocalDateTime regDate){
this.memberId = memberId;
this.email = email;
this.name = name;
this.phoneNumber = phoneNumber;
this.memberShip = memberShip;
this.regDate = regDate;
}

public MemberSignupResponse(Member member) {
this.memberId = member.getId();
this.email = member.getMemberEmail();
this.name = member.getMemberName();
this.phoneNumber = member.getPhoneNumber();
this.memberShip = member.getMemberShip();
this.regDate = member.getCreatedAt();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,29 @@

import java.util.Optional;
import org.springframework.data.repository.Repository;

import com.javajober.exception.ApiStatus;
import com.javajober.exception.ApplicationException;
import com.javajober.member.domain.Member;

public interface MemberRepository extends Repository<Member, Long> {
Optional<Member> findById(final Long id);

Optional<Member> findByMemberEmail(final String email);

Member save(final Member member);

default Member findMember(final Long id) {
String message = "존재하지 않는 회원정보입니다.";

return findById(id)
.orElseThrow(() -> new ApplicationException(ApiStatus.NOT_FOUND, message));
}

default Member findMember (final String email) {
String message = "존재하지 않는 회원정보입니다.";

return findByMemberEmail(email)
.orElseThrow(() -> new ApplicationException(ApiStatus.NOT_FOUND, message));
}
}
69 changes: 69 additions & 0 deletions src/main/java/com/javajober/member/service/MemberService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.javajober.member.service;


import javax.transaction.Transactional;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindingResult;

import com.javajober.exception.ApiStatus;
import com.javajober.exception.ApplicationException;
import com.javajober.member.domain.Member;
import com.javajober.member.dto.MemberLoginRequest;
import com.javajober.member.dto.MemberLoginResponse;
import com.javajober.member.dto.MemberSignupRequest;
import com.javajober.member.dto.MemberSignupResponse;
import com.javajober.member.repository.MemberRepository;
import com.javajober.refreshToken.repository.RefreshTokenRepository;
import com.javajober.security.JwtTokenizer;
import com.javajober.refreshToken.domain.RefreshToken;

@Service
public class MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenizer jwtTokenizer;
private final RefreshTokenRepository refreshTokenRepository;

public MemberService(MemberRepository memberRepository, PasswordEncoder passwordEncoder, JwtTokenizer jwtTokenizer,
RefreshTokenRepository refreshTokenRepository) {
this.memberRepository = memberRepository;
this.passwordEncoder = passwordEncoder;
this.jwtTokenizer = jwtTokenizer;
this.refreshTokenRepository = refreshTokenRepository;
}

@Transactional
public MemberSignupResponse signup(MemberSignupRequest memberSignupRequest, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new ApplicationException(ApiStatus.INVALID_DATA, "잘못된 요청입니다.");
}

Member member = memberSignupRequest.toEntity(memberSignupRequest);
member.setPassword(passwordEncoder.encode(memberSignupRequest.getPassword()));
Member saveMember = memberRepository.save(member);

return new MemberSignupResponse(saveMember);
}

@Transactional
public MemberLoginResponse login(MemberLoginRequest loginDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new ApplicationException(ApiStatus.INVALID_DATA, "잘못된 요청입니다.");
}
Member member = memberRepository.findMember(loginDto.getEmail());

if (!passwordEncoder.matches(loginDto.getPassword(), member.getPassword())) {
throw new ApplicationException(ApiStatus.NOT_FOUND, "비밀번호가 일치하지 않습니다.");
}

String accessToken = jwtTokenizer.createAccessToken(member.getId(), member.getMemberEmail());
String refreshToken = jwtTokenizer.createRefreshToken(member.getId(), member.getMemberEmail());

RefreshToken refreshTokenEntity = new RefreshToken(member.getId(),refreshToken);
refreshTokenRepository.save(refreshTokenEntity);

return new MemberLoginResponse(member,accessToken,refreshToken);
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/javajober/refreshToken/domain/RefreshToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.javajober.refreshToken.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Builder;
import lombok.Getter;

@Getter
@Table(name="refresh_token")
@Entity
public class RefreshToken {

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

private Long memberId;

private String value;

public RefreshToken(){

}

@Builder
public RefreshToken(final Long memberId, final String value){
this.memberId = memberId;
this.value = value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.javajober.refreshToken.dto;

import javax.validation.constraints.NotEmpty;

import lombok.Data;

@Data
public class RefreshTokenRequest {

@NotEmpty
String refreshToken;
}
Loading

0 comments on commit 34b9037

Please sign in to comment.