Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] security jwt 를 이용한 로그인 및 회원가입 기능 추가 #31

Merged
merged 15 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ database/data/

### REDIS/DATA Directory ###
redis/data/
/src/main/generated/com/haejwo/tripcometrue
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
compileOnly 'org.projectlombok:lombok'

// mysql connector
Expand Down Expand Up @@ -53,6 +55,14 @@ dependencies {

// dotenv-java
implementation 'io.github.cdimascio:java-dotenv:+'

//oauth
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")

// jjwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2'
}

jar {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package com.haejwo.tripcometrue.domain.member.controller;

import com.haejwo.tripcometrue.domain.member.request.SignUpRequest;
import com.haejwo.tripcometrue.domain.member.response.SignUpResponse;
import com.haejwo.tripcometrue.domain.member.dto.request.SignUpRequestDto;
import com.haejwo.tripcometrue.domain.member.dto.response.SignUpResponseDto;
import com.haejwo.tripcometrue.domain.member.dto.response.TestUserResponseDto;
import com.haejwo.tripcometrue.domain.member.entity.Member;
import com.haejwo.tripcometrue.domain.member.service.MemberService;
import com.haejwo.tripcometrue.global.springsecurity.PrincipalDetails;
import com.haejwo.tripcometrue.global.util.ResponseDTO;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -20,10 +26,35 @@ public class MemberController {
private final MemberService memberService;

@PostMapping("/signup")
public ResponseEntity<ResponseDTO<SignUpResponse>> signup(
@Valid @RequestBody SignUpRequest signUpRequest) {
SignUpResponse signupResponse = memberService.signup(signUpRequest);
ResponseDTO<SignUpResponse> response = ResponseDTO.okWithData(signupResponse);
return ResponseEntity.status(response.getCode()).body(response);
public ResponseEntity<ResponseDTO<SignUpResponseDto>> signup(
@Valid @RequestBody SignUpRequestDto signUpRequestDto) {
SignUpResponseDto signupResponseDto = memberService.signup(signUpRequestDto);
ResponseDTO<SignUpResponseDto> response = ResponseDTO.okWithData(signupResponseDto);
return ResponseEntity
.status(response.getCode())
.body(response);
}

// Authenticated user 샘플테스트 코드입니다
@GetMapping("/test/jwt")
public ResponseEntity<ResponseDTO<TestUserResponseDto>> test(
@AuthenticationPrincipal PrincipalDetails principalDetails) {
Member member = principalDetails.getMember();

TestUserResponseDto testUserResponseDto = TestUserResponseDto.fromEntity(member);
ResponseDTO<TestUserResponseDto> response = ResponseDTO.okWithData(testUserResponseDto);
return ResponseEntity
.status(response.getCode())
.body(response);
}

@GetMapping("/check-duplicated-email")
public ResponseEntity<ResponseDTO<Void>> checkDuplicateEmail(
@RequestParam String email) {
memberService.checkDuplicateEmail(email);
ResponseDTO<Void> response = ResponseDTO.ok();
return ResponseEntity
.status(response.getCode())
.body(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.haejwo.tripcometrue.domain.member.dto.request;

import jakarta.validation.constraints.NotNull;

public record LoginRequestDto(
@NotNull(message = "email은 필수값입니다")
String email,
@NotNull(message = "password은 필수값입니다")
String password
) {

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.haejwo.tripcometrue.domain.member.request;
package com.haejwo.tripcometrue.domain.member.dto.request;

import com.haejwo.tripcometrue.domain.member.entity.Member;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
public record SignUpRequest(

public record SignUpRequestDto(

@Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "이메일 형식이 유효하지 않습니다")
@NotNull(message = "email은 필수값입니다")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.haejwo.tripcometrue.domain.member.dto.response;

import com.haejwo.tripcometrue.domain.member.entity.Member;

public record LoginResponseDto(
String email,
String name,
String token
) {

public static LoginResponseDto fromEntity(Member member, String token) {
return new LoginResponseDto(
member.getMemberBase().getEmail(),
member.getMemberBase().getNickname(),
token
);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package com.haejwo.tripcometrue.domain.member.response;
package com.haejwo.tripcometrue.domain.member.dto.response;


import com.haejwo.tripcometrue.domain.member.entity.Member;

public record SignUpResponse(
public record SignUpResponseDto(

Long memberId,
String email,
String name

) {
public static SignUpResponse fromEntity(Member member) {
return new SignUpResponse (
member.getMemberId(),

public static SignUpResponseDto fromEntity(Member member) {
return new SignUpResponseDto(
member.getId(),
member.getMemberBase().getEmail(),
member.getMemberBase().getNickname()
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.haejwo.tripcometrue.domain.member.dto.response;

import com.haejwo.tripcometrue.domain.member.entity.Member;

public record TestUserResponseDto(

String email,
String nickname,
String authority,
String provider

) {

public static TestUserResponseDto fromEntity(Member member) {
return new TestUserResponseDto(
member.getMemberBase().getEmail(),
member.getMemberBase().getNickname(),
member.getMemberBase().getAuthority(),
member.getProvider()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.haejwo.tripcometrue.domain.member.entity;

import com.haejwo.tripcometrue.global.entity.BaseTimeEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
Expand All @@ -18,7 +19,8 @@ public class Member extends BaseTimeEntity {

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

@Embedded
protected MemberBase memberBase;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.haejwo.tripcometrue.domain.member.service;

import com.haejwo.tripcometrue.domain.member.dto.request.SignUpRequestDto;
import com.haejwo.tripcometrue.domain.member.dto.response.SignUpResponseDto;
import com.haejwo.tripcometrue.domain.member.entity.Member;
import com.haejwo.tripcometrue.domain.member.exception.EmailDuplicateException;
import com.haejwo.tripcometrue.domain.member.repository.MemberRepository;
import com.haejwo.tripcometrue.domain.member.request.SignUpRequest;
import com.haejwo.tripcometrue.domain.member.response.SignUpResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
Expand All @@ -18,16 +18,22 @@ public class MemberService {
private final MemberRepository memberRepository;
private final BCryptPasswordEncoder passwordEncoder;

public SignUpResponse signup(SignUpRequest signUpRequest) {
public SignUpResponseDto signup(SignUpRequestDto signUpRequestDto) {

memberRepository.findByMemberBaseEmail(signUpRequest.email()).ifPresent(user -> {
memberRepository.findByMemberBaseEmail(signUpRequestDto.email()).ifPresent(user -> {
throw new EmailDuplicateException();
});

String encodedPassword = passwordEncoder.encode(signUpRequest.password());
String encodedPassword = passwordEncoder.encode(signUpRequestDto.password());

Member newMember = signUpRequest.toEntity(encodedPassword);
Member newMember = signUpRequestDto.toEntity(encodedPassword);
memberRepository.save(newMember);
return SignUpResponse.fromEntity(newMember);
return SignUpResponseDto.fromEntity(newMember);
}

public void checkDuplicateEmail(String email) {
memberRepository.findByMemberBaseEmail(email).ifPresent(user -> {
throw new EmailDuplicateException();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

@Configuration
public class AppConfig {

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.haejwo.tripcometrue.global.exception;

import com.haejwo.tripcometrue.global.util.ResponseDTO;
import java.util.Map;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
Expand Down Expand Up @@ -66,14 +65,16 @@ public ResponseEntity<ResponseDTO<Void>> handleValidationExceptions(

BindingResult bindingResult = e.getBindingResult();

Map<String, String> fieldErrors = bindingResult.getFieldErrors()
List<String> fieldErrors = bindingResult.getFieldErrors()
.stream()
.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());

log.error(e.getMessage(), e);
String errorMessage = String.join(", ", fieldErrors);

return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(ResponseDTO.errorWithMessage(HttpStatus.BAD_REQUEST,
fieldErrors.values().toString().substring(1,fieldErrors.values().toString().length()-1)));
.body(ResponseDTO.errorWithMessage(HttpStatus.BAD_REQUEST, errorMessage));
}
}
Loading