Skip to content

Commit 1cb0fde

Browse files
authored
feat: 로그인 jwt 적용 (#150)
* feat: 로그인 jwt 적용 * feat: 비회원 로그인 jwt 적용 * chore: 사용 안하는 메서드, dto 제거 * chore: ci yml 주입 디코딩 제거
1 parent 8b5c5e8 commit 1cb0fde

18 files changed

+384
-289
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- name: application-ci.yml 주입
2828
run: |
2929
mkdir -p src/main/resources
30-
echo "${{ secrets.APPLICATION_YML_CI }}" | base64 --decode > src/main/resources/application-ci.yml
30+
echo "${{ secrets.APPLICATION_YML_CI }}" > src/main/resources/application-ci.yml
3131
3232
- name: 권한 세팅
3333
run: chmod +x ./gradlew

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ dependencies {
6666
implementation 'com.github.ben-manes.caffeine:caffeine'
6767

6868
implementation 'org.springframework.retry:spring-retry:2.0.12'
69+
70+
//jwt
71+
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
72+
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
73+
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
6974
}
7075

7176
tasks.named('test') {

src/main/java/com/oronaminc/join/Web57OronaminCBeApplication.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.oronaminc.join;
22

3+
import com.oronaminc.join.member.token.JwtConfiguration;
34
import org.springframework.boot.SpringApplication;
45
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
57
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
68

79
@EnableJpaAuditing
810
@SpringBootApplication
11+
@EnableConfigurationProperties(JwtConfiguration.class)
912
public class Web57OronaminCBeApplication {
1013

1114
public static void main(String[] args) {
Lines changed: 66 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,66 @@
1-
package com.oronaminc.join.global.dev;
2-
3-
import java.util.List;
4-
5-
import org.springframework.context.annotation.Profile;
6-
import org.springframework.http.HttpStatus;
7-
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
8-
import org.springframework.security.core.Authentication;
9-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
10-
import org.springframework.security.core.context.SecurityContextHolder;
11-
import org.springframework.web.bind.annotation.PostMapping;
12-
import org.springframework.web.bind.annotation.RequestBody;
13-
import org.springframework.web.bind.annotation.RequestMapping;
14-
import org.springframework.web.bind.annotation.ResponseStatus;
15-
import org.springframework.web.bind.annotation.RestController;
16-
17-
import com.oronaminc.join.member.dao.MemberRepository;
18-
import com.oronaminc.join.member.domain.Member;
19-
import com.oronaminc.join.member.domain.MemberType;
20-
import com.oronaminc.join.member.security.MemberDetails;
21-
22-
import io.swagger.v3.oas.annotations.tags.Tag;
23-
import jakarta.servlet.http.HttpServletRequest;
24-
import jakarta.servlet.http.HttpSession;
25-
import lombok.RequiredArgsConstructor;
26-
27-
@Profile("local")
28-
@RestController
29-
@Tag(name = "개발용 API")
30-
@RequestMapping("/dev")
31-
@RequiredArgsConstructor
32-
public class DevController {
33-
private final MemberRepository memberRepository;
34-
35-
@PostMapping("/join")
36-
@ResponseStatus(HttpStatus.OK)
37-
public Member devJoin(@RequestBody DevJoinRequest devJoinRequest) {
38-
return memberRepository.save(
39-
Member.builder()
40-
.email(devJoinRequest.email())
41-
.nickname(devJoinRequest.nickname())
42-
.memberType(MemberType.MEMBER)
43-
.build()
44-
);
45-
}
46-
47-
@PostMapping("/login")
48-
@ResponseStatus(HttpStatus.OK)
49-
public void devLogin(@RequestBody DevLoginRequest devLoginRequest, HttpServletRequest request) {
50-
Member member = memberRepository.findById(devLoginRequest.memberId())
51-
.orElseThrow(() -> new IllegalArgumentException("해당 ID의 사용자가 존재하지 않습니다."));
52-
53-
MemberDetails memberDetails = MemberDetails.builder()
54-
.id(member.getId())
55-
.name(member.getEmail())
56-
.nickname(member.getNickname())
57-
.role(member.getMemberType())
58-
.build();
59-
60-
Authentication authentication = new UsernamePasswordAuthenticationToken(
61-
memberDetails,
62-
null,
63-
List.of(new SimpleGrantedAuthority(memberDetails.getRole()))
64-
);
65-
66-
SecurityContextHolder.getContext().setAuthentication(authentication);
67-
68-
HttpSession session = request.getSession(true);
69-
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
70-
}
71-
72-
73-
}
1+
//package com.oronaminc.join.global.dev;
2+
//
3+
//import com.oronaminc.join.member.dao.MemberRepository;
4+
//import com.oronaminc.join.member.domain.Member;
5+
//import com.oronaminc.join.member.domain.MemberType;
6+
//import com.oronaminc.join.member.security.MemberDetails;
7+
//import io.swagger.v3.oas.annotations.tags.Tag;
8+
//import jakarta.servlet.http.HttpServletRequest;
9+
//import jakarta.servlet.http.HttpSession;
10+
//import lombok.RequiredArgsConstructor;
11+
//import org.springframework.http.HttpStatus;
12+
//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13+
//import org.springframework.security.core.Authentication;
14+
//import org.springframework.security.core.authority.SimpleGrantedAuthority;
15+
//import org.springframework.security.core.context.SecurityContextHolder;
16+
//import org.springframework.web.bind.annotation.*;
17+
//
18+
//import java.util.List;
19+
//
20+
/// /@Profile("local")
21+
//@RestController
22+
//@Tag(name = "개발용 API")
23+
//@RequestMapping("/dev")
24+
//@RequiredArgsConstructor
25+
//public class DevController {
26+
// private final MemberRepository memberRepository;
27+
//
28+
// @PostMapping("/join")
29+
// @ResponseStatus(HttpStatus.OK)
30+
// public Member devJoin(@RequestBody DevJoinRequest devJoinRequest) {
31+
// return memberRepository.save(
32+
// Member.builder()
33+
// .email(devJoinRequest.email())
34+
// .nickname(devJoinRequest.nickname())
35+
// .memberType(MemberType.MEMBER)
36+
// .build()
37+
// );
38+
// }
39+
//
40+
// @PostMapping("/login")
41+
// @ResponseStatus(HttpStatus.OK)
42+
// public void devLogin(@RequestBody DevLoginRequest devLoginRequest, HttpServletRequest request) {
43+
// Member member = memberRepository.findById(devLoginRequest.memberId())
44+
// .orElseThrow(() -> new IllegalArgumentException("해당 ID의 사용자가 존재하지 않습니다."));
45+
//
46+
// MemberDetails memberDetails = MemberDetails.builder()
47+
// .id(member.getId())
48+
// .name(member.getEmail())
49+
// .nickname(member.getNickname())
50+
// .role(member.getMemberType())
51+
// .build();
52+
//
53+
// Authentication authentication = new UsernamePasswordAuthenticationToken(
54+
// memberDetails,
55+
// null,
56+
// List.of(new SimpleGrantedAuthority(memberDetails.getRole()))
57+
// );
58+
//
59+
// SecurityContextHolder.getContext().setAuthentication(authentication);
60+
//
61+
// HttpSession session = request.getSession(true);
62+
// session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
63+
// }
64+
//
65+
//
66+
//}
Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
package com.oronaminc.join.global.dev;
2-
3-
import org.springframework.http.HttpStatus;
4-
import org.springframework.web.bind.annotation.GetMapping;
5-
import org.springframework.web.bind.annotation.ResponseStatus;
6-
import org.springframework.web.bind.annotation.RestController;
7-
8-
import io.swagger.v3.oas.annotations.Operation;
9-
import io.swagger.v3.oas.annotations.tags.Tag;
10-
11-
@RestController
12-
@Tag(name = "헬스체크 API")
13-
public class HealthController {
14-
15-
@Operation(summary = "애플리케이션 헬스체크")
16-
@ResponseStatus(HttpStatus.OK)
17-
@GetMapping("/health")
18-
public String health() {
19-
return "Server is Healthy!";
20-
}
21-
22-
@Operation(summary = "홈")
23-
@ResponseStatus(HttpStatus.OK)
24-
@GetMapping("/")
25-
public String home() {
26-
return "It's Home!";
27-
}
28-
}
1+
//package com.oronaminc.join.global.dev;
2+
//
3+
//import org.springframework.http.HttpStatus;
4+
//import org.springframework.web.bind.annotation.GetMapping;
5+
//import org.springframework.web.bind.annotation.ResponseStatus;
6+
//import org.springframework.web.bind.annotation.RestController;
7+
//
8+
//import io.swagger.v3.oas.annotations.Operation;
9+
//import io.swagger.v3.oas.annotations.tags.Tag;
10+
//
11+
//@RestController
12+
//@Tag(name = "헬스체크 API")
13+
//public class HealthController {
14+
//
15+
// @Operation(summary = "애플리케이션 헬스체크")
16+
// @ResponseStatus(HttpStatus.OK)
17+
// @GetMapping("/health")
18+
// public String health() {
19+
// return "Server is Healthy!";
20+
// }
21+
//
22+
// @Operation(summary = "홈")
23+
// @ResponseStatus(HttpStatus.OK)
24+
// @GetMapping("/")
25+
// public String home() {
26+
// return "It's Home!";
27+
// }
28+
//}

src/main/java/com/oronaminc/join/member/dto/SessionInfoResponse.java

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/main/java/com/oronaminc/join/member/security/AuthController.java

Lines changed: 29 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,11 @@
11
package com.oronaminc.join.member.security;
22

33

4-
import static com.oronaminc.join.member.util.MemberMapper.toSessionInfoResponse;
5-
6-
import java.util.List;
7-
8-
import org.springframework.http.HttpStatus;
9-
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
10-
import org.springframework.security.core.Authentication;
11-
import org.springframework.security.core.annotation.AuthenticationPrincipal;
12-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
13-
import org.springframework.security.core.context.SecurityContext;
14-
import org.springframework.security.core.context.SecurityContextHolder;
15-
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
16-
import org.springframework.web.bind.annotation.GetMapping;
17-
import org.springframework.web.bind.annotation.PostMapping;
18-
import org.springframework.web.bind.annotation.RequestBody;
19-
import org.springframework.web.bind.annotation.RequestMapping;
20-
import org.springframework.web.bind.annotation.ResponseStatus;
21-
import org.springframework.web.bind.annotation.RestController;
22-
234
import com.oronaminc.join.member.dto.GuestLoginRequest;
24-
import com.oronaminc.join.member.dto.GuestLoginResponse;
255
import com.oronaminc.join.member.dto.KakaoLoginRequest;
26-
import com.oronaminc.join.member.dto.KakaoLoginResponse;
27-
import com.oronaminc.join.member.dto.SessionInfoResponse;
28-
6+
import com.oronaminc.join.member.token.AuthTokenResponse;
7+
import com.oronaminc.join.member.token.JwtUtils;
8+
import com.oronaminc.join.member.token.LoginResponse;
299
import io.swagger.v3.oas.annotations.Operation;
3010
import io.swagger.v3.oas.annotations.responses.ApiResponse;
3111
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -34,82 +14,63 @@
3414
import jakarta.servlet.http.HttpServletResponse;
3515
import jakarta.servlet.http.HttpSession;
3616
import jakarta.validation.Valid;
17+
import java.util.Map;
3718
import lombok.RequiredArgsConstructor;
19+
import org.springframework.http.HttpStatus;
20+
import org.springframework.security.core.context.SecurityContextHolder;
21+
import org.springframework.web.bind.annotation.PostMapping;
22+
import org.springframework.web.bind.annotation.RequestBody;
23+
import org.springframework.web.bind.annotation.RequestMapping;
24+
import org.springframework.web.bind.annotation.ResponseStatus;
25+
import org.springframework.web.bind.annotation.RestController;
3826

3927
@RestController
4028
@RequestMapping("/api/auth")
4129
@Tag(name = "Auth", description = "로그인 관련 API")
4230
@RequiredArgsConstructor
4331
public class AuthController {
32+
4433
private final AuthService authService;
4534

4635
@Operation(
47-
summary = "카카오 로그인",
48-
description = "redirect url 에 포함된 파라미터의 code와 state를 입력해주세요. 이후 모든 요청에 세션 인증이 적용됩니다."
36+
summary = "카카오 로그인"
4937
)
5038
@PostMapping("/kakao")
5139
@ResponseStatus(HttpStatus.OK)
52-
public SessionInfoResponse kakaoLogin(
53-
@RequestBody KakaoLoginRequest kakaoLoginRequest,
54-
HttpServletRequest request
40+
public Map<String, AuthTokenResponse> kakaoLogin(
41+
@RequestBody KakaoLoginRequest kakaoLoginRequest,
42+
HttpServletResponse response
5543
) {
56-
MemberDetails memberDetails = authService.kakaoLogin(kakaoLoginRequest.code());
44+
LoginResponse loginResponse = authService.kakaoLogin(kakaoLoginRequest.code());
5745

58-
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
59-
memberDetails, null, List.of(new SimpleGrantedAuthority(memberDetails.getRole()))
60-
);
46+
String refreshToken = loginResponse.refreshToken();
6147

62-
SecurityContext context = SecurityContextHolder.createEmptyContext();
63-
context.setAuthentication(authentication);
64-
SecurityContextHolder.setContext(context);
48+
JwtUtils.addRefreshTokenCookie(response, refreshToken,
49+
loginResponse.refreshTokenExpiresIn());
6550

66-
SecurityContextHolder.getContext().setAuthentication(authentication);
67-
68-
request.getSession(true).setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
69-
70-
return toSessionInfoResponse(memberDetails);
51+
return Map.of("token", loginResponse.authTokenResponse());
7152
}
7253

7354
@Operation(
7455
summary = "비회원 로그인",
75-
description = "닉네임을 입력하면 비회원 세션이 생성되고 인증이 설정됩니다. 이후 모든 요청에 세션 인증이 적용됩니다.",
7656
responses = {
7757
@ApiResponse(responseCode = "201", description = "비회원 로그인 성공"),
7858
@ApiResponse(responseCode = "400", description = "닉네임 누락 또는 유효성 검증 실패")
7959
}
8060
)
8161
@PostMapping("/guest")
8262
@ResponseStatus(HttpStatus.CREATED)
83-
public SessionInfoResponse guestLogin(@RequestBody @Valid GuestLoginRequest guestLoginRequest, HttpServletRequest request) {
84-
MemberDetails guest = authService.loadGuest(guestLoginRequest);
85-
86-
Authentication authentication = new UsernamePasswordAuthenticationToken(
87-
guest, null, List.of(new SimpleGrantedAuthority(guest.getRole()))
88-
);
89-
90-
SecurityContext context = SecurityContextHolder.createEmptyContext();
91-
context.setAuthentication(authentication);
92-
SecurityContextHolder.setContext(context);
63+
public Map<String, AuthTokenResponse> guestLogin(
64+
@RequestBody @Valid GuestLoginRequest guestLoginRequest,
65+
HttpServletResponse response) {
66+
LoginResponse loginResponse = authService.loadGuest(guestLoginRequest);
9367

94-
request.getSession(true).setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
68+
String refreshToken = loginResponse.refreshToken();
9569

96-
return toSessionInfoResponse(guest);
97-
}
98-
99-
@Operation(
100-
summary = "현재 세션 사용자 정보 조회",
101-
description = "로그인한 사용자의 세션 정보를 반환합니다. 로그인하지 않은 경우 403 또는 401이 발생합니다.",
102-
responses = {
103-
@ApiResponse(responseCode = "200", description = "세션 사용자 정보 조회 성공"),
104-
@ApiResponse(responseCode = "401", description = "로그인되지 않은 사용자"),
105-
@ApiResponse(responseCode = "403", description = "인증된 사용자 아님")
106-
}
107-
)
108-
@GetMapping("/session")
109-
@ResponseStatus(HttpStatus.OK)
110-
public SessionInfoResponse getSessionInfo(@AuthenticationPrincipal MemberDetails memberDetails) {
70+
JwtUtils.addRefreshTokenCookie(response, refreshToken,
71+
loginResponse.refreshTokenExpiresIn());
11172

112-
return toSessionInfoResponse(memberDetails);
73+
return Map.of("token", loginResponse.authTokenResponse());
11374
}
11475

11576
@Operation(

0 commit comments

Comments
 (0)