Skip to content

Commit

Permalink
feat: 인증 객체로부터 사용자 정보를 얻을 수 있도록 한다.
Browse files Browse the repository at this point in the history
feat: 인증 객체로부터 사용자 정보를 얻을 수 있도록 한다.
  • Loading branch information
hseong3243 authored Jan 24, 2024
2 parents 982ebcf + f81e70c commit e6caf29
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 33 deletions.
12 changes: 12 additions & 0 deletions src/main/java/com/seong/shoutlink/domain/auth/LoginUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.seong.shoutlink.domain.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import com.seong.shoutlink.domain.member.Member;
import com.seong.shoutlink.domain.member.MemberRole;
import com.seong.shoutlink.domain.member.service.MemberRepository;
import com.seong.shoutlink.global.exception.ErrorCode;
import com.seong.shoutlink.global.exception.ShoutLinkException;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/com/seong/shoutlink/domain/exception/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.seong.shoutlink.domain.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ErrorCode {
ILLEGAL_ARGUMENT("SL001", Constants.BAD_REQUEST),
UNAUTHENTICATED("SL101", Constants.UNAUTHORIZED),
INVALID_ACCESS_TOKEN("SL102", Constants.UNAUTHORIZED),
EXPIRED_ACCESS_TOKEN("SL103", Constants.UNAUTHORIZED),
DUPLICATE_EMAIL("SL901", Constants.CONFLICT),
DUPLICATE_NICKNAME("SL902", Constants.BAD_REQUEST);

private final String errorCode;
private final int status;

private static class Constants {

private static final int BAD_REQUEST = 400;
private static final int UNAUTHORIZED = 401;
private static final int CONFLICT = 409;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.seong.shoutlink.global.exception;
package com.seong.shoutlink.domain.exception;

import lombok.Getter;

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/seong/shoutlink/domain/member/Member.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.seong.shoutlink.domain.member;

import com.seong.shoutlink.global.exception.ErrorCode;
import com.seong.shoutlink.global.exception.ShoutLinkException;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import java.util.Objects;
import java.util.regex.Pattern;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.seong.shoutlink.global.auth.authentication;

import com.seong.shoutlink.global.exception.ErrorCode;
import com.seong.shoutlink.global.exception.ShoutLinkException;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Objects;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import com.seong.shoutlink.domain.auth.service.response.ClaimsResponse;
import com.seong.shoutlink.domain.auth.service.response.TokenResponse;
import com.seong.shoutlink.domain.member.MemberRole;
import com.seong.shoutlink.global.exception.ErrorCode;
import com.seong.shoutlink.global.exception.ShoutLinkException;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.seong.shoutlink.global.auth.resolver;

import com.seong.shoutlink.domain.auth.LoginUser;
import com.seong.shoutlink.global.auth.authentication.Authentication;
import com.seong.shoutlink.global.auth.authentication.AuthenticationContext;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@RequiredArgsConstructor
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

private final AuthenticationContext authenticationContext;

@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean hasParameterAnnotation = parameter.hasParameterAnnotation(LoginUser.class);
boolean hasLongParameterType = parameter.getParameterType().isAssignableFrom(Long.class);
return hasParameterAnnotation && hasLongParameterType;
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Authentication authentication = authenticationContext.getAuthentication();
checkAuthenticated(authentication);
return authentication.getPrincipal();
}

private void checkAuthenticated(Authentication authentication) {
if(Objects.isNull(authentication)) {
throw new ShoutLinkException("인증되지 않은 사용자 요청입니다.", ErrorCode.UNAUTHENTICATED);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.seong.shoutlink.global.config;

import com.seong.shoutlink.global.auth.resolver.LoginUserArgumentResolver;
import com.seong.shoutlink.global.auth.authentication.AuthenticationContext;
import com.seong.shoutlink.global.auth.authentication.JwtAuthenticationInterceptor;
import com.seong.shoutlink.global.auth.authentication.JwtAuthenticationProvider;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

Expand All @@ -22,4 +25,9 @@ public void addInterceptors(InterceptorRegistry registry) {
.order(1)
.addPathPatterns("/api/**");
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginUserArgumentResolver(authenticationContext));
}
}
15 changes: 0 additions & 15 deletions src/main/java/com/seong/shoutlink/global/exception/ErrorCode.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.seong.shoutlink.global.exception;

public record ErrorResponse(String message, String errorCode) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.seong.shoutlink.global.exception;

import com.seong.shoutlink.domain.exception.ShoutLinkException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class globalExceptionHandler {

@ExceptionHandler(ShoutLinkException.class)
public ResponseEntity<ErrorResponse> shoutLinkExHandle(ShoutLinkException e) {
ErrorResponse errorResponse
= new ErrorResponse(e.getMessage(), e.getErrorCode().getErrorCode());
return ResponseEntity.status(e.getErrorCode().getStatus()).body(errorResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.seong.shoutlink.fixture.AuthFixture;
import com.seong.shoutlink.global.auth.authentication.AuthenticationContext;
import com.seong.shoutlink.global.auth.authentication.JwtAuthenticationProvider;
import com.seong.shoutlink.global.config.WebConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -27,7 +28,7 @@
import org.springframework.web.filter.CharacterEncodingFilter;

@WebMvcTest
@Import({RestDocsConfig.class, BaseControllerConfig.class})
@Import({RestDocsConfig.class, BaseControllerConfig.class, WebConfig.class})
@ExtendWith(RestDocumentationExtension.class)
public class BaseControllerTest {

Expand All @@ -50,6 +51,8 @@ public AuthenticationContext authenticationContext() {
}
}

protected static final String AUTHORIZATION = "Authorization";

protected MockMvc mockMvc;

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import com.seong.shoutlink.domain.member.MemberRole;
import com.seong.shoutlink.domain.member.repository.StubMemberRepository;
import com.seong.shoutlink.global.auth.jwt.JJwtProvider;
import com.seong.shoutlink.global.exception.ErrorCode;
import com.seong.shoutlink.global.exception.ShoutLinkException;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchException;

import com.seong.shoutlink.global.exception.ErrorCode;
import com.seong.shoutlink.global.exception.ShoutLinkException;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.seong.shoutlink.global.auth;

import com.seong.shoutlink.domain.auth.LoginUser;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/test")
public class AuthTestController {

@GetMapping("/login-user")
public ResponseEntity<Long> loginUser(@LoginUser Long memberId) {
return ResponseEntity.ok(memberId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import com.seong.shoutlink.domain.auth.service.response.TokenResponse;
import com.seong.shoutlink.domain.member.MemberRole;
import com.seong.shoutlink.fixture.AuthFixture;
import com.seong.shoutlink.global.exception.ErrorCode;
import com.seong.shoutlink.global.exception.ShoutLinkException;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import com.seong.shoutlink.domain.auth.service.response.ClaimsResponse;
import com.seong.shoutlink.domain.auth.service.response.TokenResponse;
import com.seong.shoutlink.domain.member.MemberRole;
import com.seong.shoutlink.global.exception.ErrorCode;
import com.seong.shoutlink.global.exception.ShoutLinkException;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.seong.shoutlink.global.auth.resolver;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.seong.shoutlink.base.BaseControllerTest;
import com.seong.shoutlink.domain.auth.service.response.TokenResponse;
import com.seong.shoutlink.domain.member.MemberRole;
import com.seong.shoutlink.fixture.AuthFixture;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.ResultActions;

class LoginUserArgumentResolverTest extends BaseControllerTest {

@Nested
@DisplayName("@LoginUser를 핸들러 메서드의 파라미터에 선언 시")
class LoginUserTest {

@Test
@DisplayName("성공: 인증된 사용자의 principal을 반환")
void returnAuthenticatedUserPrincipal() throws Exception {
//given
TokenResponse tokenResponse = AuthFixture.jwtProvider()
.createToken(1L, MemberRole.ROLE_USER);
String bearerAccessToken = "Bearer " + tokenResponse.accessToken();

//when
ResultActions resultActions = mockMvc.perform(get("/api/test/login-user")
.header(AUTHORIZATION, bearerAccessToken));

//then
resultActions.andExpect(status().isOk())
.andExpect(jsonPath("$").value(1));
}

@Test
@DisplayName("예외(unauthenticated): 인증되지 않은 사용자 요청")
void unauthenticated_WhenUnauthenticatedUserRequest() throws Exception {
//given
//when
//then
mockMvc.perform(get("/api/test/login-user"))
.andExpect(
result -> {
Exception exception = result.getResolvedException();
assertThat(exception)
.isInstanceOf(ShoutLinkException.class)
.extracting(e -> ((ShoutLinkException) e).getErrorCode())
.isEqualTo(ErrorCode.UNAUTHENTICATED);
}
);
}
}
}

0 comments on commit e6caf29

Please sign in to comment.