Skip to content

Commit 1b714ff

Browse files
authored
Merge pull request #18 from team-MoPlus/feature/#17
[feat/#17] 내 정보 조회 api 추가
2 parents 6725504 + a416fbd commit 1b714ff

File tree

11 files changed

+233
-8
lines changed

11 files changed

+233
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.moplus.moplus_server.domain.member.controller;
2+
3+
import com.moplus.moplus_server.domain.member.domain.Member;
4+
import com.moplus.moplus_server.domain.member.dto.response.MemberGetResponse;
5+
import com.moplus.moplus_server.global.annotation.AuthUser;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RestController;
12+
13+
@RestController
14+
@RequestMapping("/api/v1/member")
15+
@RequiredArgsConstructor
16+
public class MemberController {
17+
18+
@GetMapping("me")
19+
@Operation(summary = "내 정보 조회", description = "jwt accessToken을 통해 내 정보를 조회합니다.")
20+
public ResponseEntity<MemberGetResponse> getMyInfo(
21+
@AuthUser Member member
22+
) {
23+
return ResponseEntity.ok(MemberGetResponse.of(member));
24+
}
25+
}
26+

Diff for: src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@ public class Member extends BaseEntity {
2323
@Column(name = "member_id")
2424
private Long id;
2525

26+
private String name;
2627
private String email;
2728
private String password;
2829

2930
@Enumerated(EnumType.STRING)
3031
private MemberRole role;
3132

3233
@Builder
33-
public Member(String nickname, String email, String password, MemberRole role) {
34+
public Member(String name, String email, String password, MemberRole role) {
35+
this.name = name;
3436
this.email = email;
3537
this.password = password;
3638
this.role = role;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.moplus.moplus_server.domain.member.dto.response;
2+
3+
4+
import com.moplus.moplus_server.domain.member.domain.Member;
5+
6+
public record MemberGetResponse(
7+
Long id,
8+
String name,
9+
String email
10+
) {
11+
public static MemberGetResponse of(Member member) {
12+
return new MemberGetResponse(
13+
member.getId(),
14+
member.getName(),
15+
member.getEmail()
16+
);
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.moplus.moplus_server.global.annotation;
2+
3+
import io.swagger.v3.oas.annotations.Parameter;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Target(ElementType.PARAMETER)
10+
@Retention(RetentionPolicy.RUNTIME)
11+
@Parameter(hidden = true)
12+
public @interface AuthUser {
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.moplus.moplus_server.global.annotation;
2+
3+
import com.moplus.moplus_server.domain.member.domain.Member;
4+
import com.moplus.moplus_server.domain.member.repository.MemberRepository;
5+
import com.moplus.moplus_server.global.error.exception.BusinessException;
6+
import com.moplus.moplus_server.global.error.exception.ErrorCode;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.core.MethodParameter;
9+
import org.springframework.security.core.Authentication;
10+
import org.springframework.security.core.context.SecurityContextHolder;
11+
import org.springframework.stereotype.Component;
12+
import org.springframework.web.bind.support.WebDataBinderFactory;
13+
import org.springframework.web.context.request.NativeWebRequest;
14+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
15+
import org.springframework.web.method.support.ModelAndViewContainer;
16+
17+
@Component
18+
@RequiredArgsConstructor
19+
public class AuthenticationArgumentResolver implements HandlerMethodArgumentResolver {
20+
21+
private final MemberRepository memberRepository;
22+
23+
@Override
24+
public boolean supportsParameter(MethodParameter parameter) {
25+
final boolean isUserAuthAnnotation = parameter.getParameterAnnotation(AuthUser.class) != null;
26+
final boolean isMemberClass = parameter.getParameterType().equals(Member.class);
27+
return isUserAuthAnnotation && isMemberClass;
28+
}
29+
30+
@Override
31+
public Member resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
32+
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
33+
return getCurrentMember();
34+
}
35+
36+
private Member getCurrentMember() {
37+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
38+
39+
if (authentication == null || !authentication.isAuthenticated()) {
40+
throw new BusinessException(ErrorCode.AUTH_NOT_FOUND);
41+
}
42+
Object principal = authentication.getPrincipal();
43+
44+
if (!(principal instanceof Member)) {
45+
throw new BusinessException(ErrorCode.INVALID_PRINCIPAL);
46+
}
47+
return (Member) principal;
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.moplus.moplus_server.global.config;
22

3+
import com.moplus.moplus_server.global.annotation.AuthenticationArgumentResolver;
4+
import java.util.List;
35
import lombok.RequiredArgsConstructor;
46
import org.springframework.context.annotation.Configuration;
57
import org.springframework.scheduling.annotation.EnableScheduling;
68
import org.springframework.transaction.annotation.EnableTransactionManagement;
9+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
710
import org.springframework.web.servlet.config.annotation.CorsRegistry;
811
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
912

@@ -13,15 +16,21 @@
1316
@EnableTransactionManagement
1417
public class WebConfig implements WebMvcConfigurer {
1518

19+
private final AuthenticationArgumentResolver authenticationArgumentResolver;
1620

1721
@Override
1822
public void addCorsMappings(CorsRegistry registry) {
1923
registry.addMapping("/**")
20-
.allowedOrigins("https://dev.mopl.kr","http://dev.mopl.kr", "http://localhost:8080", "https://www.mopl.kr", "http"
21-
+ "://localhost:3000", "https://dev-web.mopl.kr", "http://dev-web.mopl.kr")
22-
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
23-
.allowedHeaders("*")
24-
.allowCredentials(true);
24+
.allowedOrigins("https://dev.mopl.kr", "http://dev.mopl.kr", "http://localhost:8080",
25+
"https://www.mopl.kr", "http"
26+
+ "://localhost:3000", "https://dev-web.mopl.kr", "http://dev-web.mopl.kr")
27+
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
28+
.allowedHeaders("*")
29+
.allowCredentials(true);
2530
}
2631

32+
@Override
33+
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
34+
resolvers.add(authenticationArgumentResolver);
35+
}
2736
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.moplus.moplus_server.global.config.swagger;
2+
3+
import io.swagger.v3.oas.models.Components;
4+
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.info.Info;
6+
import io.swagger.v3.oas.models.security.SecurityRequirement;
7+
import io.swagger.v3.oas.models.security.SecurityScheme;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
11+
@Configuration
12+
public class SwaggerConfig {
13+
14+
private SecurityScheme createAPIKeyScheme() {
15+
return new SecurityScheme().type(SecurityScheme.Type.HTTP)
16+
.bearerFormat("JWT")
17+
.scheme("Bearer");
18+
}
19+
20+
@Bean
21+
public OpenAPI openAPI() {
22+
return new OpenAPI().addSecurityItem(new SecurityRequirement().addList("JWT"))
23+
.components(new Components().addSecuritySchemes("JWT", createAPIKeyScheme()))
24+
.info(new Info().title("모플 API 명세서")
25+
.description("모플 API 명세서 입니다")
26+
.version("v0.0.1"));
27+
}
28+
}

Diff for: src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public enum ErrorCode {
2121
TOKEN_SUBJECT_FORMAT_ERROR(HttpStatus.UNAUTHORIZED, "Subject 값에 Long 타입이 아닌 다른 타입이 들어있습니다."),
2222
AT_EXPIRED_AND_RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AT는 만료되었고 RT는 비어있습니다."),
2323
RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "RT가 비어있습니다"),
24+
INVALID_PRINCIPAL(HttpStatus.UNAUTHORIZED, "잘못된 Principal입니다"),
2425

2526
//모의고사
2627
PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.moplus.moplus_server.domain.member.controller;
2+
3+
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
4+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
5+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
6+
7+
import com.moplus.moplus_server.global.properties.jwt.JwtProperties;
8+
import io.jsonwebtoken.Jwts;
9+
import io.jsonwebtoken.security.Keys;
10+
import java.security.Key;
11+
import java.util.Date;
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.Nested;
14+
import org.junit.jupiter.api.Test;
15+
import org.springframework.beans.factory.annotation.Autowired;
16+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
17+
import org.springframework.boot.test.context.SpringBootTest;
18+
import org.springframework.http.HttpHeaders;
19+
import org.springframework.test.context.ActiveProfiles;
20+
import org.springframework.test.context.jdbc.Sql;
21+
import org.springframework.test.web.servlet.MockMvc;
22+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
23+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
24+
import org.springframework.transaction.annotation.Transactional;
25+
import org.springframework.web.context.WebApplicationContext;
26+
27+
@Transactional
28+
@SpringBootTest
29+
@AutoConfigureMockMvc
30+
@ActiveProfiles("h2test")
31+
@Sql({"/auth-test-data.sql"})
32+
class MemberControllerTest {
33+
34+
@Nested
35+
class 어드민_로그인 {
36+
37+
@Autowired
38+
private JwtProperties jwtProperties;
39+
40+
@Autowired
41+
private MockMvc mockMvc;
42+
43+
@Autowired
44+
private WebApplicationContext context;
45+
46+
private String validToken;
47+
private Key key;
48+
49+
@BeforeEach
50+
public void setMockMvc() throws Exception {
51+
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
52+
.apply(springSecurity()).build();
53+
54+
key = Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes());
55+
Date issuedAt = new Date(); // 3 hour ago
56+
Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime());
57+
validToken = Jwts.builder()
58+
.setIssuer(jwtProperties.issuer())
59+
.setSubject("1")
60+
.claim("role", "ROLE_USER")
61+
.setIssuedAt(issuedAt)
62+
.setExpiration(expiredAt)
63+
.signWith(key)
64+
.compact();
65+
}
66+
67+
@Test
68+
void 성공() throws Exception {
69+
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/member/me")
70+
.contentType("application/json")
71+
.header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken))
72+
.andExpect(status().isOk()) // 200 응답 확인
73+
.andExpect(jsonPath("$.id").exists()) // MemberGetResponse의 필드 확인
74+
.andExpect(jsonPath("$.name").exists())
75+
.andExpect(jsonPath("$.email").exists());
76+
}
77+
}
78+
}

Diff for: src/test/resources/auth-test-data.sql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
INSERT INTO member (deleted, created_at, update_at, member_id, email, password, role)
1+
INSERT INTO member (deleted, created_at, update_at, member_id, email, password, name, role)
22
VALUES (false, '2024-07-24 21:27:20.000000', '2024-07-24 21:27:21.000000', 1, '[email protected]',
3-
'password123', 'ADMIN');
3+
'password123', '홍길동', 'ADMIN');
44

0 commit comments

Comments
 (0)