Skip to content

Commit 2e57c6a

Browse files
authored
merge: pull request #14 from feat/login/1
[feat] login/logout
2 parents fddea9a + 187efef commit 2e57c6a

File tree

11 files changed

+374
-18
lines changed

11 files changed

+374
-18
lines changed

build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ dependencies {
4545
runtimeOnly ("org.mariadb.jdbc:mariadb-java-client")
4646

4747
// JWT
48-
// implementation ("io.jsonwebtoken:jjwt-api:0.11.5")
49-
// implementation ("io.jsonwebtoken:jjwt-impl:0.11.5")
50-
// implementation ("io.jsonwebtoken:jjwt-jackson:0.11.5")
48+
implementation ("io.jsonwebtoken:jjwt-api:0.11.5")
49+
implementation ("io.jsonwebtoken:jjwt-impl:0.11.5")
50+
implementation ("io.jsonwebtoken:jjwt-jackson:0.11.5")
5151

5252
// Redis
5353
implementation("org.springframework.boot:spring-boot-starter-data-redis")

src/main/java/org/dfbf/soundlink/domain/user/controller/UserController.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
import io.swagger.v3.oas.annotations.Operation;
44
import io.swagger.v3.oas.annotations.tags.Tag;
55

6+
import jakarta.servlet.http.HttpServletResponse;
67
import lombok.AllArgsConstructor;
8+
import org.dfbf.soundlink.domain.user.dto.request.LoginReqDto;
79
import org.dfbf.soundlink.global.exception.ErrorCode;
810
import org.dfbf.soundlink.domain.user.dto.request.UserSignUpDto;
911
import org.dfbf.soundlink.domain.user.dto.request.UserUpdateDto;
1012
import org.dfbf.soundlink.domain.user.service.UserService;
1113
import org.dfbf.soundlink.global.exception.ResponseResult;
14+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1215
import org.springframework.web.bind.annotation.*;
1316

1417
@RestController
@@ -26,27 +29,36 @@ public class UserController {
2629
@GetMapping("/checkNickName")
2730
@Operation(summary = "닉네임 중복 확인", description = "닉네임이 이미 사용중인지 확인.")
2831
public ResponseResult checkNickName(@RequestParam String nickName){
29-
boolean exists = userService.checkNickName(nickName);
30-
return exists
31-
? new ResponseResult(ErrorCode.DUPLICATE_NICKNAME) //중복 닉네임
32-
: new ResponseResult(ErrorCode.NOT_DUPLICATE_NICKNAME);
32+
return userService.checkNickName(nickName);
3333
}
3434

3535
@GetMapping
3636
@Operation(summary = "유저 조회", description = "유저 조회 API")
37-
public ResponseResult getUser(/*@AuthenticationPrincipal id: Long*/) { return userService.getUser(1L); }
37+
public ResponseResult getUser(@AuthenticationPrincipal Long id) { return userService.getUser(id); }
3838

3939
@PutMapping
4040
@Operation(summary = "유저 수정", description = "유저 수정 API")
41-
public ResponseResult updateUser(/*@AuthenticationPrincipal id: Long, */@RequestBody UserUpdateDto userUpdateDto) {
42-
return userService.updateUser(1L, userUpdateDto);
41+
public ResponseResult updateUser(@AuthenticationPrincipal Long id,@RequestBody UserUpdateDto userUpdateDto) {
42+
return userService.updateUser(id, userUpdateDto);
4343
}
4444

4545
@DeleteMapping
4646
@Operation(summary = "유저 삭제", description = "회원 탈퇴하는 API (탈퇴시 프로필 정보도 삭제됩니다.)")
47-
public ResponseResult deleteUser(/*@AuthenticationPrincipal id: Long*/) { return userService.deleteUser(1L); }
47+
public ResponseResult deleteUser(@AuthenticationPrincipal Long id) { return userService.deleteUser(id); }
4848

4949
@GetMapping("/mypage")
5050
@Operation(summary = "마이 페이지", description = "마이 페이지 조회 API")
51-
public ResponseResult getMyPage(/*@AuthenticationPrincipal id: Long*/) { return userService.getMyPage(1L); }
51+
public ResponseResult getMyPage(@AuthenticationPrincipal Long id) { return userService.getMyPage(id); }
52+
53+
@PostMapping("/login")
54+
@Operation(summary = "로그인", description = "로그인 API")
55+
public ResponseResult login(@RequestBody LoginReqDto loginReqDto, HttpServletResponse response) {
56+
return userService.login(loginReqDto, response);
57+
}
58+
59+
@PostMapping("/logout")
60+
@Operation(summary = "로그아웃", description = "로그아웃 API")
61+
public ResponseResult logout(HttpServletResponse response) {
62+
return userService.logout(response);
63+
}
5264
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package org.dfbf.soundlink.domain.user.dto.request;
2+
3+
public record LoginReqDto(String loginId, String password) { }

src/main/java/org/dfbf/soundlink/domain/user/entity/User.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ public class User {
3030
@Column(nullable = true)
3131
private Long socialId;
3232

33+
@Column(name="login_id")
3334
private String loginId;
35+
3436
private String password;
3537
private String email;
3638

src/main/java/org/dfbf/soundlink/domain/user/repository/UserRepository.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,16 @@ public interface UserRepository extends JpaRepository<User, Long> {
2626
"WHERE u = :user"
2727
)
2828
UserMyPageDto findMyPageDtoByUserId(@Param("user") User user);
29+
30+
//로그인관련
31+
boolean existsByLoginId(String loginId);
32+
33+
Optional<User> findByLoginId(String loginId);
34+
35+
@Query("Select u.password from User u where u.loginId =:loginId ")
36+
String findByPassword(@Param("loginId")String loginId);
37+
38+
39+
40+
2941
}

src/main/java/org/dfbf/soundlink/domain/user/service/UserService.java

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.dfbf.soundlink.domain.user.service;
22

33
import jakarta.mail.MessagingException;
4+
import jakarta.servlet.http.HttpServletResponse;
45
import jakarta.transaction.Transactional;
56
import lombok.AllArgsConstructor;
67
import lombok.extern.slf4j.Slf4j;
78
import org.dfbf.soundlink.domain.emotionRecord.entity.SpotifyMusic;
89
import org.dfbf.soundlink.domain.emotionRecord.repository.EmotionRecordRepository;
910
import org.dfbf.soundlink.domain.emotionRecord.repository.SpotifyMusicRepository;
11+
import org.dfbf.soundlink.domain.user.dto.request.LoginReqDto;
1012
import org.dfbf.soundlink.domain.user.dto.request.UserSignUpDto;
1113
import org.dfbf.soundlink.domain.user.dto.request.UserUpdateDto;
1214
import org.dfbf.soundlink.domain.user.dto.response.UserGetDto;
@@ -16,12 +18,18 @@
1618
import org.dfbf.soundlink.domain.user.exception.NoUserDataException;
1719
import org.dfbf.soundlink.domain.user.repository.ProfileMusicRepository;
1820
import org.dfbf.soundlink.domain.user.repository.UserRepository;
21+
import org.dfbf.soundlink.global.auth.JwtProvider;
22+
import org.dfbf.soundlink.global.auth.TokenProperties;
1923
import org.dfbf.soundlink.global.exception.ErrorCode;
2024
import org.dfbf.soundlink.global.exception.ResponseResult;
25+
import org.springframework.data.redis.core.RedisTemplate;
26+
import org.springframework.http.ResponseCookie;
2127
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
2228
import org.springframework.stereotype.Service;
2329

2430
import javax.naming.AuthenticationException;
31+
import java.util.HashMap;
32+
import java.util.Map;
2533

2634
@Slf4j
2735
@Service
@@ -35,6 +43,10 @@ public class UserService {
3543
private final BCryptPasswordEncoder passwordEncoder;
3644
private final MailService mailService;
3745
private final RedisService redisService;
46+
private final JwtProvider jwtProvider;
47+
private final TokenProperties tokenProperties;
48+
private RedisTemplate<String, String> redisTemplate;
49+
3850

3951
// 회원가입
4052
public ResponseResult signUp(UserSignUpDto userSignUpDto) {
@@ -92,9 +104,6 @@ public ResponseResult updateUser(Long userId, UserUpdateDto userUpdateDto) {
92104
public ResponseResult deleteUser(Long userId) {
93105
try {
94106
User user = userRepository.findById(userId).orElseThrow(() -> new NoUserDataException());
95-
96-
// profileMusicRepository.deleteByUser(user); // 유저 프로필 음악 삭제
97-
// emotionRecordRepository.deleteByUser(user); // 유저 감정 기록 삭제
98107
userRepository.deleteById(userId); // 유저 삭제
99108

100109
return new ResponseResult(ErrorCode.SUCCESS);
@@ -159,7 +168,77 @@ public ResponseResult checkEmail(String email){
159168
}
160169

161170
//닉네임 중복 확인
162-
public boolean checkNickName(String nickName){
163-
return userRepository.existsByNickName(nickName);
171+
public ResponseResult checkNickName(String nickName){
172+
boolean exists =userRepository.existsByNickName(nickName);
173+
if(exists){
174+
return new ResponseResult(ErrorCode.DUPLICATE_NICKNAME);
175+
}
176+
return new ResponseResult(ErrorCode.NOT_DUPLICATE_NICKNAME);
164177
}
178+
179+
//refreshToken을 쿠키로 설정
180+
private ResponseCookie getRefreshToken(String refreshToken) {
181+
return ResponseCookie
182+
.from("REFRESHTOKEN", refreshToken)
183+
.domain("localhost")
184+
.path("/")
185+
.httpOnly(true)
186+
.maxAge(tokenProperties.getRefreshTokenExpirationTime()) //만료시간 설정
187+
.build();
188+
}
189+
190+
//로그인
191+
public ResponseResult login(LoginReqDto loginReqDto, HttpServletResponse response) {
192+
try {
193+
if(!userRepository.existsByLoginId(loginReqDto.loginId())) {
194+
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER, "계정을 찾을 수 없습니다.");
195+
}
196+
// 비밀번호 검증(암호화 된 비밀번호 비교)
197+
if(!passwordEncoder.matches(loginReqDto.password(), userRepository.findByPassword(loginReqDto.loginId()))){
198+
return new ResponseResult( ErrorCode.NOT_EQUALS_PASSWORD,"잘못된 비밀번호 입니다.");
199+
}
200+
201+
User user = userRepository.findByLoginId(loginReqDto.loginId())
202+
.orElseThrow(NoUserDataException::new);
203+
204+
String accessToken = jwtProvider.createAccessToken(user.getUserId());
205+
String refreshToken = jwtProvider.createRefreshToken(user.getUserId());
206+
207+
//refreshToken - 쿠키
208+
ResponseCookie refreshCookie = getRefreshToken(refreshToken);
209+
response.setHeader("Set-Cookie", refreshCookie.toString());
210+
211+
//accessToken - 바디
212+
Map<String, String> responseBody = new HashMap<>();
213+
responseBody.put("accessToken", accessToken);
214+
215+
return new ResponseResult(responseBody);
216+
} catch (Exception e) {
217+
System.out.println("[ERROR] " + e.getMessage());
218+
return new ResponseResult(ErrorCode. INTERNAL_SERVER_ERROR);
219+
}
220+
}
221+
222+
//로그아웃
223+
public ResponseResult logout(HttpServletResponse response) {
224+
try {
225+
//클라이언트 - 토큰 삭제
226+
ResponseCookie refreshCookie = ResponseCookie
227+
.from("REFRESHTOKEN", "") // 추후 토큰값 추가
228+
.domain("localhost")
229+
.path("/")
230+
.httpOnly(true)
231+
.maxAge(0)
232+
.build();
233+
response.setHeader("Set-Cookie", refreshCookie.toString());//쿠키 삭제 요청
234+
235+
return new ResponseResult(ErrorCode.SUCCESS);
236+
237+
} catch (Exception e) {
238+
return new ResponseResult(ErrorCode. INTERNAL_SERVER_ERROR,"로그아웃 중 오류가 발생했습니다.");
239+
}
240+
}
241+
242+
243+
165244
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.dfbf.soundlink.global.auth;
2+
3+
import org.springframework.security.core.GrantedAuthority;
4+
import org.springframework.security.core.userdetails.UserDetails;
5+
6+
import java.util.Collection;
7+
import java.util.List;
8+
9+
public class CustomUserDetails implements UserDetails{
10+
private final Long userId;
11+
12+
public CustomUserDetails(Long userId) {
13+
if (userId == null) {
14+
throw new IllegalArgumentException("userId cannot be null");
15+
}
16+
this.userId = userId;
17+
}
18+
19+
public Long getUserId() {
20+
return userId;
21+
}
22+
23+
24+
@Override
25+
public Collection<? extends GrantedAuthority> getAuthorities() {
26+
return List.of(); //사용자의 권한 정보 반환
27+
}
28+
29+
@Override
30+
public String getPassword() {
31+
return null;
32+
}
33+
34+
@Override
35+
public String getUsername() {
36+
return String.valueOf(userId);//로그인할때 ID
37+
}
38+
39+
@Override
40+
public boolean isAccountNonExpired() {
41+
return true; //계정 만료 여부(만료 x)
42+
}
43+
44+
@Override
45+
public boolean isAccountNonLocked() {
46+
return true; //계정이 잠겨있는지 확인.
47+
}
48+
49+
@Override
50+
public boolean isCredentialsNonExpired() {
51+
return true; //비밀번호 유효기간 확인
52+
}
53+
54+
@Override
55+
public boolean isEnabled() {
56+
return true; //계정활성화 여부 확인
57+
}
58+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.dfbf.soundlink.global.auth;
2+
3+
import jakarta.servlet.FilterChain;
4+
import jakarta.servlet.ServletException;
5+
import jakarta.servlet.http.HttpServletRequest;
6+
import jakarta.servlet.http.HttpServletResponse;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
9+
import org.springframework.security.core.context.SecurityContextHolder;
10+
import org.springframework.web.filter.OncePerRequestFilter;
11+
12+
import java.io.IOException;
13+
14+
@RequiredArgsConstructor
15+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
16+
private final JwtProvider jwtProvider;
17+
18+
@Override
19+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
20+
String accessToken = jwtProvider.resolveAccessToken(request); //1.Access Token 추출
21+
22+
if(accessToken !=null && jwtProvider.validateToken(accessToken)) { //2.유효성 검사
23+
this.setAuthentication(accessToken); //3.유저정보 저장
24+
}
25+
filterChain.doFilter(request, response); //필터 체인 진행(전달)
26+
}
27+
28+
//유저정보 저장
29+
public void setAuthentication(String token) {
30+
Long userId = jwtProvider.getUserId(token); //userId 추출
31+
32+
if (userId == null) {
33+
throw new IllegalArgumentException("userId cannot be null");
34+
}
35+
36+
CustomUserDetails userDetails = new CustomUserDetails(userId);
37+
38+
// 인증토큰 생성
39+
UsernamePasswordAuthenticationToken authentication =
40+
new UsernamePasswordAuthenticationToken(userId, null, userDetails.getAuthorities());
41+
42+
// 인증정보 설정
43+
SecurityContextHolder.getContext().setAuthentication(authentication);
44+
}
45+
46+
}

0 commit comments

Comments
 (0)