-
Notifications
You must be signed in to change notification settings - Fork 0
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
Auth 회원가입 및 로그인 기능 구현 #16
Open
yeeunli
wants to merge
15
commits into
develop
Choose a base branch
from
feature/auth
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
71df143
chore: security 관련 의존성 추가
yeeunli 05b38ac
feat: Auth 도메인 설정
yeeunli 853b1a4
feat: Auth 회원가입 로직 구현
yeeunli d04b232
feat: Auth 회원가입 로직 구현
yeeunli 39e0740
feat: Auth 로그인 로직 구현 (access 기준)
yeeunli ff50773
refactor: Auth 회원 가입 엔드포인트 변경
yeeunli ddaf0df
feat: Auth refresh 토큰으로 access 토큰 재발급 구현
yeeunli c9fdda5
feat: Auth refresh 토큰 db에 저장
yeeunli 13f7835
fix: Auth 재로그인 시 기존 refresh 토큰 db에서 삭제
yeeunli 79fd200
feat: Auth 로그아웃 구현
yeeunli cd6a7e3
feat: Auth 쿠키 만료 시간 매개변수로 설정 가능하도록 수정
yeeunli 2294fd8
feat: Auth 토큰에 auth_id 추가
yeeunli e76e23a
feat: Auth 토큰 생성 및 쿠키 설정 방식 변경
yeeunli 585bfaa
fix: Auth 트랜잭션 설정 수정
yeeunli f33433f
feat: Auth 로그아웃 경로 설정
yeeunli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
src/main/java/com/gdgoc/study_group/auth/api/AuthController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.gdgoc.study_group.auth.api; | ||
|
||
import com.gdgoc.study_group.auth.application.JoinService; | ||
import com.gdgoc.study_group.auth.application.ReissueService; | ||
import com.gdgoc.study_group.auth.dto.JoinDto; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
@RestController | ||
@RequestMapping("/auth") | ||
@RequiredArgsConstructor | ||
@Tag(name = "Auth", description = "로그인 API") | ||
public class AuthController { | ||
|
||
private final JoinService joinService; | ||
private final ReissueService reissueService; | ||
|
||
/** | ||
* 학번과 비밀번호 등의 정보로 회원 가입을 합니다. | ||
* | ||
* @param request 회원 가입에 필요한 데이터 dto | ||
* @return 생성 시, 201 회원 가입 성공 메시지 | ||
*/ | ||
@Operation(summary = "회원 가입", description = "학번과 비밀번호 등 정보 입력 시 회원 가입") | ||
@PostMapping("/signup") | ||
public ResponseEntity<Void> joinMember(@RequestBody JoinDto request) { | ||
|
||
joinService.joinMember(request); | ||
|
||
return ResponseEntity.status(HttpStatus.CREATED).build(); | ||
} | ||
|
||
@PostMapping("/reissue") | ||
public ResponseEntity<?> reissue(HttpServletRequest request, HttpServletResponse response) { | ||
|
||
return reissueService.reissueToken(request, response); | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
src/main/java/com/gdgoc/study_group/auth/application/CustomUserDetailsService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.gdgoc.study_group.auth.application; | ||
|
||
import com.gdgoc.study_group.auth.dao.AuthRepository; | ||
import com.gdgoc.study_group.auth.domain.Auth; | ||
import com.gdgoc.study_group.auth.dto.CustomUserDetails; | ||
import lombok.RequiredArgsConstructor; | ||
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; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class CustomUserDetailsService implements UserDetailsService { | ||
|
||
private final AuthRepository authRepository; | ||
|
||
/** | ||
* 학번으로 회원을 조회하여 반환합니다. | ||
* @param studentNumber 조회할 대상 정보 | ||
* @return 조회된 회원 정보를 UserDetails 형식으로 반환 | ||
*/ | ||
@Override | ||
public UserDetails loadUserByUsername(String studentNumber) throws UsernameNotFoundException { | ||
|
||
Auth userData = authRepository.findByMember_StudentNumber(studentNumber); | ||
|
||
if (userData == null) { | ||
throw new UsernameNotFoundException("해당 학번의 사용자를 찾을 수 없습니다"); | ||
} | ||
|
||
return new CustomUserDetails(userData); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
src/main/java/com/gdgoc/study_group/auth/application/JoinService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.gdgoc.study_group.auth.application; | ||
|
||
import com.gdgoc.study_group.auth.dao.AuthRepository; | ||
import com.gdgoc.study_group.auth.domain.Auth; | ||
import com.gdgoc.study_group.auth.dto.JoinDto; | ||
import com.gdgoc.study_group.member.dao.MemberRepository; | ||
import com.gdgoc.study_group.member.domain.Member; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
public class JoinService { | ||
|
||
private final AuthRepository authRepository; | ||
private final MemberRepository memberRepository; | ||
private final BCryptPasswordEncoder bCryptPasswordEncoder; | ||
|
||
/** | ||
* 학번과 비밀번호 등의 정보로 회원 가입을 합니다. | ||
* | ||
* @param joinDto 회원 가입에 필요한 데이터 dto | ||
*/ | ||
@Transactional(readOnly = false) | ||
public void joinMember(JoinDto joinDto) { | ||
|
||
String studentNumber = joinDto.studentNumber(); | ||
|
||
if (authRepository.existsByMember_StudentNumber(studentNumber)) { | ||
throw new IllegalArgumentException("이미 존재하는 학번입니다."); | ||
} | ||
|
||
Member member = Member.builder() | ||
.name(joinDto.name()) | ||
.github(joinDto.github()) | ||
.studentNumber(joinDto.studentNumber()) | ||
.build(); | ||
|
||
member = memberRepository.save(member); | ||
|
||
Auth auth = Auth.builder() | ||
.member(member) | ||
.password(bCryptPasswordEncoder.encode(joinDto.password())) | ||
.role("ROLE_USER") | ||
.build(); | ||
|
||
authRepository.save(auth); | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
src/main/java/com/gdgoc/study_group/auth/application/RefreshTokenService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package com.gdgoc.study_group.auth.application; | ||
|
||
import com.gdgoc.study_group.auth.dao.AuthRepository; | ||
import com.gdgoc.study_group.auth.dao.RefreshRepository; | ||
import com.gdgoc.study_group.auth.domain.Auth; | ||
import com.gdgoc.study_group.auth.domain.Refresh; | ||
import jakarta.transaction.Transactional; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.Date; | ||
import java.util.NoSuchElementException; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class RefreshTokenService { | ||
|
||
private final RefreshRepository refreshRepository; | ||
private final AuthRepository authRepository; | ||
|
||
/** | ||
* 로그인 시 생성되는 리프레쉬 토큰을 데이터베이스에 저장합니다. | ||
* | ||
* @param authId 회원 정보 | ||
* @param refreshToken 저장하려는 리프레쉬 토큰 | ||
* @param expiredMs 저장하려는 리프레쉬 토큰의 만료 시간 | ||
*/ | ||
@Transactional | ||
public void saveRefresh(Long authId, String refreshToken, Long expiredMs) { | ||
|
||
Auth auth = authRepository.findById(authId) | ||
.orElseThrow(() -> new NoSuchElementException("인증 정보를 찾을 수 없습니다.")); | ||
|
||
// 기존 사용자의 refresh 토큰이 있다면 삭제 | ||
refreshRepository.deleteByAuth(auth); | ||
refreshRepository.flush(); | ||
|
||
Refresh refreshAuth = Refresh.builder() | ||
.auth(auth) | ||
.refresh(refreshToken) | ||
.expiration(new Date(System.currentTimeMillis() + expiredMs).toString()) | ||
.build(); | ||
|
||
refreshRepository.save(refreshAuth); | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
src/main/java/com/gdgoc/study_group/auth/application/ReissueService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package com.gdgoc.study_group.auth.application; | ||
|
||
import com.gdgoc.study_group.auth.jwt.JwtUtil; | ||
import io.jsonwebtoken.ExpiredJwtException; | ||
import jakarta.servlet.http.Cookie; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ReissueService { | ||
private final JwtUtil jwtUtil; | ||
|
||
public ResponseEntity<?> reissueToken(HttpServletRequest request, HttpServletResponse response) { | ||
|
||
String refresh = null; | ||
|
||
/** | ||
* 요청에서 refresh 쿠키를 찾아서, | ||
* 해당 값의 null 여부를 확인합니다. | ||
*/ | ||
Cookie[] cookies = request.getCookies(); | ||
|
||
for (Cookie cookie : cookies) { | ||
if (cookie.getName().equals("refresh")) { | ||
refresh = cookie.getValue(); | ||
} | ||
} | ||
|
||
if (refresh == null) { | ||
return new ResponseEntity<>("refresh token null", HttpStatus.BAD_REQUEST); | ||
} | ||
|
||
/** | ||
* refresh 쿠키 존재 시, | ||
* 만료 여부와 category를 확인합니다. | ||
*/ | ||
try { | ||
jwtUtil.isExpired(refresh); | ||
} catch (ExpiredJwtException e) { | ||
return new ResponseEntity<>("refresh token expired", HttpStatus.BAD_REQUEST); | ||
} | ||
|
||
String category = jwtUtil.getCategory(refresh); | ||
if (!category.equals("refresh")) { | ||
return new ResponseEntity<>("invalid refresh token", HttpStatus.BAD_REQUEST); | ||
} | ||
|
||
/** | ||
* refresh token 검증을 마치면, | ||
* 새로운 access token을 발급합니다. | ||
*/ | ||
Long authId = jwtUtil.getAuthId(refresh); | ||
String studentNumber = jwtUtil.getStudentNumber(refresh); | ||
String role = jwtUtil.getRole(refresh); | ||
|
||
String newAccess = jwtUtil.createJWT("access", authId, studentNumber, role, 600000L); | ||
|
||
// 응답 | ||
response.setHeader("Authorization", "Bearer " + newAccess); | ||
|
||
return new ResponseEntity<>(HttpStatus.OK); | ||
} | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 이건 어떤 role 인가요? 일반 회원과 운영진인가여
그리고 enum 으로 따로 관리하면 어떨까요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네, 일반 유저와 관리자입니다.
enum으로 바꿀게요.