From c42932daebb3d7588d23fee041c44d26af65da7d Mon Sep 17 00:00:00 2001 From: ashley Date: Mon, 15 Aug 2022 14:37:41 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[add]=20form=20login=20unit=20test,=20accep?= =?UTF-8?q?tance=20test,=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++ src/main/java/nextstep/DataLoader.java | 16 +++++++ .../java/nextstep/DataLoaderBootstrap.java | 2 +- .../BearerTokenAuthenticationFilter.java | 3 +- .../UsernamePasswordAuthenticationFilter.java | 28 ++++++++++++- .../application/LoginMemberService.java | 2 +- .../member/application/MemberService.java | 3 +- .../nextstep/subway/ui/StationController.java | 2 + .../subway/acceptance/AcceptanceTest.java | 5 +++ .../subway/acceptance/AuthAcceptanceTest.java | 18 +++++++- .../subway/acceptance/MemberSteps.java | 2 +- ...ePasswordAuthenticationFilterMockTest.java | 42 +++++++++++++++++++ 12 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java diff --git a/build.gradle b/build.gradle index d12875bc9..1e7f69d61 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,9 @@ dependencies { // jwt implementation 'io.jsonwebtoken:jjwt:0.9.1' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:4.2.0' diff --git a/src/main/java/nextstep/DataLoader.java b/src/main/java/nextstep/DataLoader.java index d3cd1cfc7..321d0033d 100644 --- a/src/main/java/nextstep/DataLoader.java +++ b/src/main/java/nextstep/DataLoader.java @@ -1,9 +1,25 @@ package nextstep; +import lombok.AllArgsConstructor; +import nextstep.member.application.LoginMemberService; +import nextstep.member.application.MemberService; +import nextstep.member.application.dto.MemberRequest; +import nextstep.member.domain.Member; +import nextstep.member.domain.MemberRepository; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @Component +@AllArgsConstructor public class DataLoader { + + private final MemberRepository memberRepository; + + private final String ADMIN_EMAIL = "admin@email.com"; + private final String ADMIN_PASSWORD = "password"; + private final int ADMIN_AGE = 20; + public void loadData() { + memberRepository.save(new Member(ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_AGE)); } } diff --git a/src/main/java/nextstep/DataLoaderBootstrap.java b/src/main/java/nextstep/DataLoaderBootstrap.java index 701384f51..772be213f 100644 --- a/src/main/java/nextstep/DataLoaderBootstrap.java +++ b/src/main/java/nextstep/DataLoaderBootstrap.java @@ -6,7 +6,7 @@ @Component public class DataLoaderBootstrap implements ApplicationListener { - private DataLoader dataLoader; + private final DataLoader dataLoader; public DataLoaderBootstrap(DataLoader dataLoader) { this.dataLoader = dataLoader; diff --git a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java index c4f72f149..61c0d9747 100644 --- a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java @@ -15,7 +15,8 @@ public BearerTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - // TODO: 구현하세요. + + return true; } } diff --git a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java index f0f531cf7..61f46b08b 100644 --- a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java @@ -1,13 +1,20 @@ package nextstep.auth.authentication; +import com.fasterxml.jackson.databind.ObjectMapper; +import nextstep.auth.context.Authentication; +import nextstep.auth.context.SecurityContext; +import nextstep.auth.context.SecurityContextHolder; import nextstep.member.application.LoginMemberService; +import nextstep.member.domain.LoginMember; +import nextstep.member.domain.Member; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.List; public class UsernamePasswordAuthenticationFilter implements HandlerInterceptor { - private LoginMemberService loginMemberService; + private final LoginMemberService loginMemberService; public UsernamePasswordAuthenticationFilter(LoginMemberService loginMemberService) { this.loginMemberService = loginMemberService; @@ -15,7 +22,24 @@ public UsernamePasswordAuthenticationFilter(LoginMemberService loginMemberServic @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - // TODO: 구현하세요. + String email = request.getParameter("email"); + String password = request.getParameter("password"); + + LoginMember member; + + try { + member = loginMemberService.loadUserByUsername(email); + } catch (RuntimeException e) { + throw new AuthenticationException(); + } + + if (!member.checkPassword(password)) { + throw new AuthenticationException(); + } + + Authentication authentication = new Authentication(member.getEmail(), member.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + return true; } } diff --git a/src/main/java/nextstep/member/application/LoginMemberService.java b/src/main/java/nextstep/member/application/LoginMemberService.java index a38234f73..8a6ecd6d9 100644 --- a/src/main/java/nextstep/member/application/LoginMemberService.java +++ b/src/main/java/nextstep/member/application/LoginMemberService.java @@ -7,7 +7,7 @@ @Service public class LoginMemberService { - private MemberRepository memberRepository; + private final MemberRepository memberRepository; public LoginMemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; diff --git a/src/main/java/nextstep/member/application/MemberService.java b/src/main/java/nextstep/member/application/MemberService.java index b5985dd44..477e4bf08 100644 --- a/src/main/java/nextstep/member/application/MemberService.java +++ b/src/main/java/nextstep/member/application/MemberService.java @@ -5,10 +5,11 @@ import nextstep.member.domain.Member; import nextstep.member.domain.MemberRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class MemberService { - private MemberRepository memberRepository; + private final MemberRepository memberRepository; public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; diff --git a/src/main/java/nextstep/subway/ui/StationController.java b/src/main/java/nextstep/subway/ui/StationController.java index 956375927..0d790bbc4 100644 --- a/src/main/java/nextstep/subway/ui/StationController.java +++ b/src/main/java/nextstep/subway/ui/StationController.java @@ -1,5 +1,6 @@ package nextstep.subway.ui; +import nextstep.auth.secured.Secured; import nextstep.subway.applicaion.StationService; import nextstep.subway.applicaion.dto.StationRequest; import nextstep.subway.applicaion.dto.StationResponse; @@ -19,6 +20,7 @@ public StationController(StationService stationService) { } @PostMapping("/stations") +// @Secured("ROLE_ADMIN") public ResponseEntity createStation(@RequestBody StationRequest stationRequest) { StationResponse station = stationService.saveStation(stationRequest); return ResponseEntity.created(URI.create("/stations/" + station.getId())).body(station); diff --git a/src/test/java/nextstep/subway/acceptance/AcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/AcceptanceTest.java index 2187652c9..b95a58e46 100644 --- a/src/test/java/nextstep/subway/acceptance/AcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/AcceptanceTest.java @@ -1,6 +1,7 @@ package nextstep.subway.acceptance; import io.restassured.RestAssured; +import nextstep.DataLoader; import nextstep.subway.utils.DatabaseCleanup; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; @@ -17,9 +18,13 @@ public class AcceptanceTest { @Autowired private DatabaseCleanup databaseCleanup; + @Autowired + private DataLoader dataLoader; + @BeforeEach public void setUp() { RestAssured.port = port; databaseCleanup.execute(); + dataLoader.loadData(); } } diff --git a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java index 07366c9eb..87c4637e5 100644 --- a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java @@ -1,9 +1,16 @@ package nextstep.subway.acceptance; +import io.restassured.RestAssured; +import io.restassured.authentication.FormAuthConfig; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import java.util.HashMap; +import java.util.Map; import static nextstep.subway.acceptance.MemberSteps.*; @@ -40,7 +47,16 @@ void myInfoWithBearerAuth() { } private ExtractableResponse 폼_로그인_후_내_회원_정보_조회_요청(String email, String password) { - return null; +// Map params = new HashMap<>(); +// params.put("email", email); +// params.put("password", password); + + return RestAssured.given().log().all() + .auth().form(email, password, new FormAuthConfig("/login/form", "email", "password")) +// .auth().form(email, password, FormAuthConfig.springSecurity()) + .when().get("/members/me") + .then().log().all() + .statusCode(HttpStatus.OK.value()).extract(); } private ExtractableResponse 베어러_인증으로_내_회원_정보_조회_요청(String accessToken) { diff --git a/src/test/java/nextstep/subway/acceptance/MemberSteps.java b/src/test/java/nextstep/subway/acceptance/MemberSteps.java index ff370ad00..44c6e0edc 100644 --- a/src/test/java/nextstep/subway/acceptance/MemberSteps.java +++ b/src/test/java/nextstep/subway/acceptance/MemberSteps.java @@ -83,7 +83,7 @@ public class MemberSteps { public static ExtractableResponse 베이직_인증으로_내_회원_정보_조회_요청(String username, String password) { return RestAssured.given().log().all() - .auth().preemptive().basic(username, password) + .auth().basic(username, password) .accept(MediaType.APPLICATION_JSON_VALUE) .when().get("/members/me") .then().log().all() diff --git a/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java new file mode 100644 index 000000000..1f638a587 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java @@ -0,0 +1,42 @@ +package nextstep.subway.unit; + +import nextstep.auth.authentication.UsernamePasswordAuthenticationFilter; +import nextstep.member.application.LoginMemberService; +import nextstep.member.domain.LoginMember; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +public class UsernamePasswordAuthenticationFilterMockTest { + + @Mock + LoginMemberService loginMemberService; + + @InjectMocks + UsernamePasswordAuthenticationFilter filter; + + @Test + void preHandle() { + String email = "admin@email.com"; + String password = "password"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("email", email); + request.setParameter("password", password); + MockHttpServletResponse response = new MockHttpServletResponse(); + + given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(email, password, List.of())); + + assertThat(filter.preHandle(request, response, null)).isTrue(); + } +} From bbd2e9632e3a3464cf03344e21bb8cd1fa7d7ef4 Mon Sep 17 00:00:00 2001 From: ashley Date: Mon, 15 Aug 2022 15:43:59 +0900 Subject: [PATCH 2/9] =?UTF-8?q?[add]=20bearer=20login=20acceptance=20test,?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BearerTokenAuthenticationFilter.java | 13 +++++++- .../subway/acceptance/AuthAcceptanceTest.java | 14 ++++---- .../subway/acceptance/MemberSteps.java | 2 +- .../BearerTokenAuthenticationFilterTest.java | 28 ++++++++++++++++ .../subway/unit/JwtTokenProviderTest.java | 32 +++++++++++++++++++ 5 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterTest.java create mode 100644 src/test/java/nextstep/subway/unit/JwtTokenProviderTest.java diff --git a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java index 61c0d9747..f1fe2e542 100644 --- a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java @@ -1,13 +1,16 @@ package nextstep.auth.authentication; +import nextstep.auth.context.Authentication; +import nextstep.auth.context.SecurityContextHolder; import nextstep.auth.token.JwtTokenProvider; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.List; public class BearerTokenAuthenticationFilter implements HandlerInterceptor { - private JwtTokenProvider jwtTokenProvider; + private final JwtTokenProvider jwtTokenProvider; public BearerTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; @@ -15,7 +18,15 @@ public BearerTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String authCredentials = AuthorizationExtractor.extract(request, AuthorizationType.BEARER); + jwtTokenProvider.validateToken(authCredentials); + String principal = jwtTokenProvider.getPrincipal(authCredentials); + List roles = jwtTokenProvider.getRoles(authCredentials); + + Authentication authentication = new Authentication(principal, roles); + + SecurityContextHolder.getContext().setAuthentication(authentication); return true; } diff --git a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java index 87c4637e5..2b8e8cd16 100644 --- a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java @@ -47,19 +47,21 @@ void myInfoWithBearerAuth() { } private ExtractableResponse 폼_로그인_후_내_회원_정보_조회_요청(String email, String password) { -// Map params = new HashMap<>(); -// params.put("email", email); -// params.put("password", password); - return RestAssured.given().log().all() .auth().form(email, password, new FormAuthConfig("/login/form", "email", "password")) -// .auth().form(email, password, FormAuthConfig.springSecurity()) .when().get("/members/me") .then().log().all() .statusCode(HttpStatus.OK.value()).extract(); } private ExtractableResponse 베어러_인증으로_내_회원_정보_조회_요청(String accessToken) { - return null; + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer " + accessToken); + + return RestAssured.given().log().all() + .headers(headers) + .when().get("/members/me") + .then().log().all() + .statusCode(HttpStatus.OK.value()).extract(); } } diff --git a/src/test/java/nextstep/subway/acceptance/MemberSteps.java b/src/test/java/nextstep/subway/acceptance/MemberSteps.java index 44c6e0edc..ff370ad00 100644 --- a/src/test/java/nextstep/subway/acceptance/MemberSteps.java +++ b/src/test/java/nextstep/subway/acceptance/MemberSteps.java @@ -83,7 +83,7 @@ public class MemberSteps { public static ExtractableResponse 베이직_인증으로_내_회원_정보_조회_요청(String username, String password) { return RestAssured.given().log().all() - .auth().basic(username, password) + .auth().preemptive().basic(username, password) .accept(MediaType.APPLICATION_JSON_VALUE) .when().get("/members/me") .then().log().all() diff --git a/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterTest.java b/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterTest.java new file mode 100644 index 000000000..409bf6533 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterTest.java @@ -0,0 +1,28 @@ +package nextstep.subway.unit; + +import nextstep.auth.authentication.BearerTokenAuthenticationFilter; +import nextstep.auth.token.JwtTokenProvider; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; + +public class BearerTokenAuthenticationFilterTest { + + JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(); + + BearerTokenAuthenticationFilter filter; + + @Test + void preHandle() { + String token = ""; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("Authorization", token); + MockHttpServletResponse response = new MockHttpServletResponse(); + + assertThat(filter.preHandle(request, response, null)).isTrue(); + } +} diff --git a/src/test/java/nextstep/subway/unit/JwtTokenProviderTest.java b/src/test/java/nextstep/subway/unit/JwtTokenProviderTest.java new file mode 100644 index 000000000..910051c0f --- /dev/null +++ b/src/test/java/nextstep/subway/unit/JwtTokenProviderTest.java @@ -0,0 +1,32 @@ +package nextstep.subway.unit; + +import nextstep.auth.token.JwtTokenProvider; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JwtTokenProviderTest { + + @Test + void token() { + JwtTokenProvider jwtTokenProvider = getTarget(); + String email = "email@email.com"; + + String token = jwtTokenProvider.createToken(email, List.of()); + List roles = jwtTokenProvider.getRoles(token); + + assertThat(jwtTokenProvider.getPrincipal(token)).isEqualTo(email); +// TODO: role assert + } + + private JwtTokenProvider getTarget() { + JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(); + ReflectionTestUtils.setField(jwtTokenProvider, "secretKey", "atdd-secret-key"); + ReflectionTestUtils.setField(jwtTokenProvider, "validityInMilliseconds", 3600000); + + return jwtTokenProvider; + } +} From bc8efda3239735375eefdf8b36f3b98c5ac90a19 Mon Sep 17 00:00:00 2001 From: ashley Date: Mon, 15 Aug 2022 16:33:47 +0900 Subject: [PATCH 3/9] =?UTF-8?q?[add]=20=EA=B6=8C=ED=95=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/DataLoader.java | 10 +++---- src/main/java/nextstep/MemberData.java | 22 ++++++++++++++ .../BearerTokenAuthenticationFilter.java | 18 +++++++----- .../nextstep/subway/ui/LineController.java | 6 ++++ .../nextstep/subway/ui/StationController.java | 3 +- .../subway/acceptance/LineAcceptanceTest.java | 7 ++--- .../nextstep/subway/acceptance/LineSteps.java | 11 ++++--- .../acceptance/StationAcceptanceTest.java | 3 +- .../subway/acceptance/StationSteps.java | 13 ++++++--- .../BearerTokenAuthenticationFilterTest.java | 28 ------------------ .../subway/unit/JwtTokenProviderTest.java | 17 ++++------- .../nextstep/subway/utils/SecurityUtil.java | 29 +++++++++++++++++++ 12 files changed, 99 insertions(+), 68 deletions(-) create mode 100644 src/main/java/nextstep/MemberData.java delete mode 100644 src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterTest.java create mode 100644 src/test/java/nextstep/subway/utils/SecurityUtil.java diff --git a/src/main/java/nextstep/DataLoader.java b/src/main/java/nextstep/DataLoader.java index 321d0033d..9a374fea8 100644 --- a/src/main/java/nextstep/DataLoader.java +++ b/src/main/java/nextstep/DataLoader.java @@ -6,20 +6,20 @@ import nextstep.member.application.dto.MemberRequest; import nextstep.member.domain.Member; import nextstep.member.domain.MemberRepository; +import nextstep.member.domain.RoleType; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Component @AllArgsConstructor public class DataLoader { private final MemberRepository memberRepository; - private final String ADMIN_EMAIL = "admin@email.com"; - private final String ADMIN_PASSWORD = "password"; - private final int ADMIN_AGE = 20; - public void loadData() { - memberRepository.save(new Member(ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_AGE)); + memberRepository.save(MemberData.admin); + memberRepository.save(MemberData.member); } } diff --git a/src/main/java/nextstep/MemberData.java b/src/main/java/nextstep/MemberData.java new file mode 100644 index 000000000..e9d678877 --- /dev/null +++ b/src/main/java/nextstep/MemberData.java @@ -0,0 +1,22 @@ +package nextstep; + +import lombok.AllArgsConstructor; +import nextstep.member.domain.Member; +import nextstep.member.domain.MemberRepository; +import nextstep.member.domain.RoleType; +import org.springframework.stereotype.Component; + +import java.util.List; + +public class MemberData { + private static final String ADMIN_EMAIL = "admin@email.com"; + private static final String ADMIN_PASSWORD = "password"; + private static final int ADMIN_AGE = 20; + + private static final String MEMBER_EMAIL = "email@email.com"; + private static final String MEMBER_PASSWORD = "password"; + private static final int MEMBER_AGE = 20; + + public static Member admin = new Member(ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_AGE, List.of(RoleType.ROLE_ADMIN.toString())); + public static Member member = new Member(MEMBER_EMAIL, MEMBER_PASSWORD, MEMBER_AGE, List.of(RoleType.ROLE_MEMBER.toString())); +} diff --git a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java index f1fe2e542..65c6bcb82 100644 --- a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java @@ -18,16 +18,20 @@ public BearerTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - String authCredentials = AuthorizationExtractor.extract(request, AuthorizationType.BEARER); + try { + String authCredentials = AuthorizationExtractor.extract(request, AuthorizationType.BEARER); - jwtTokenProvider.validateToken(authCredentials); - String principal = jwtTokenProvider.getPrincipal(authCredentials); - List roles = jwtTokenProvider.getRoles(authCredentials); + jwtTokenProvider.validateToken(authCredentials); + String principal = jwtTokenProvider.getPrincipal(authCredentials); + List roles = jwtTokenProvider.getRoles(authCredentials); - Authentication authentication = new Authentication(principal, roles); + Authentication authentication = new Authentication(principal, roles); - SecurityContextHolder.getContext().setAuthentication(authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); - return true; + return true; + } catch (RuntimeException exception) { + return true; + } } } diff --git a/src/main/java/nextstep/subway/ui/LineController.java b/src/main/java/nextstep/subway/ui/LineController.java index 34066048b..76cb8755e 100644 --- a/src/main/java/nextstep/subway/ui/LineController.java +++ b/src/main/java/nextstep/subway/ui/LineController.java @@ -1,5 +1,6 @@ package nextstep.subway.ui; +import nextstep.auth.secured.Secured; import nextstep.subway.applicaion.LineService; import nextstep.subway.applicaion.dto.LineRequest; import nextstep.subway.applicaion.dto.LineResponse; @@ -20,6 +21,7 @@ public LineController(LineService lineService) { } @PostMapping + @Secured("ROLE_ADMIN") public ResponseEntity createLine(@RequestBody LineRequest lineRequest) { LineResponse line = lineService.saveLine(lineRequest); return ResponseEntity.created(URI.create("/lines/" + line.getId())).body(line); @@ -38,24 +40,28 @@ public ResponseEntity getLine(@PathVariable Long id) { } @PutMapping("/{id}") + @Secured("ROLE_ADMIN") public ResponseEntity updateLine(@PathVariable Long id, @RequestBody LineRequest lineRequest) { lineService.updateLine(id, lineRequest); return ResponseEntity.ok().build(); } @DeleteMapping("/{id}") + @Secured("ROLE_ADMIN") public ResponseEntity updateLine(@PathVariable Long id) { lineService.deleteLine(id); return ResponseEntity.noContent().build(); } @PostMapping("/{lineId}/sections") + @Secured("ROLE_ADMIN") public ResponseEntity addSection(@PathVariable Long lineId, @RequestBody SectionRequest sectionRequest) { lineService.addSection(lineId, sectionRequest); return ResponseEntity.ok().build(); } @DeleteMapping("/{lineId}/sections") + @Secured("ROLE_ADMIN") public ResponseEntity deleteSection(@PathVariable Long lineId, @RequestParam Long stationId) { lineService.deleteSection(lineId, stationId); return ResponseEntity.ok().build(); diff --git a/src/main/java/nextstep/subway/ui/StationController.java b/src/main/java/nextstep/subway/ui/StationController.java index 0d790bbc4..6653657b4 100644 --- a/src/main/java/nextstep/subway/ui/StationController.java +++ b/src/main/java/nextstep/subway/ui/StationController.java @@ -20,7 +20,7 @@ public StationController(StationService stationService) { } @PostMapping("/stations") -// @Secured("ROLE_ADMIN") + @Secured("ROLE_ADMIN") public ResponseEntity createStation(@RequestBody StationRequest stationRequest) { StationResponse station = stationService.saveStation(stationRequest); return ResponseEntity.created(URI.create("/stations/" + station.getId())).body(station); @@ -32,6 +32,7 @@ public ResponseEntity> showStations() { } @DeleteMapping("/stations/{id}") + @Secured("ROLE_ADMIN") public ResponseEntity deleteStation(@PathVariable Long id) { stationService.deleteStationById(id); return ResponseEntity.noContent().build(); diff --git a/src/test/java/nextstep/subway/acceptance/LineAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/LineAcceptanceTest.java index d93594071..40bef887b 100644 --- a/src/test/java/nextstep/subway/acceptance/LineAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/LineAcceptanceTest.java @@ -3,6 +3,7 @@ import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import nextstep.subway.utils.SecurityUtil; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; @@ -86,8 +87,7 @@ void updateLine() { // when Map params = new HashMap<>(); params.put("color", "red"); - RestAssured - .given().log().all() + SecurityUtil.given() .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) .when().put(createResponse.header("location")) @@ -111,8 +111,7 @@ void deleteLine() { ExtractableResponse createResponse = 지하철_노선_생성_요청("2호선", "green"); // when - ExtractableResponse response = RestAssured - .given().log().all() + ExtractableResponse response = SecurityUtil.given() .when().delete(createResponse.header("location")) .then().log().all().extract(); diff --git a/src/test/java/nextstep/subway/acceptance/LineSteps.java b/src/test/java/nextstep/subway/acceptance/LineSteps.java index 1c93c1265..057afde2f 100644 --- a/src/test/java/nextstep/subway/acceptance/LineSteps.java +++ b/src/test/java/nextstep/subway/acceptance/LineSteps.java @@ -3,6 +3,7 @@ import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import nextstep.subway.utils.SecurityUtil; import org.springframework.http.MediaType; import java.util.HashMap; @@ -13,8 +14,7 @@ public class LineSteps { Map params = new HashMap<>(); params.put("name", name); params.put("color", color); - return RestAssured - .given().log().all() + return SecurityUtil.given() .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) .when().post("/lines") @@ -43,8 +43,7 @@ public class LineSteps { } public static ExtractableResponse 지하철_노선_생성_요청(Map params) { - return RestAssured - .given().log().all() + return SecurityUtil.given() .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) .when().post("/lines") @@ -52,7 +51,7 @@ public class LineSteps { } public static ExtractableResponse 지하철_노선에_지하철_구간_생성_요청(Long lineId, Map params) { - return RestAssured.given().log().all() + return SecurityUtil.given() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(params) .when().post("/lines/{lineId}/sections", lineId) @@ -60,7 +59,7 @@ public class LineSteps { } public static ExtractableResponse 지하철_노선에_지하철_구간_제거_요청(Long lineId, Long stationId) { - return RestAssured.given().log().all() + return SecurityUtil.given() .when().delete("/lines/{lineId}/sections?stationId={stationId}", lineId, stationId) .then().log().all().extract(); } diff --git a/src/test/java/nextstep/subway/acceptance/StationAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/StationAcceptanceTest.java index 52fd9cb7d..36131ed72 100644 --- a/src/test/java/nextstep/subway/acceptance/StationAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/StationAcceptanceTest.java @@ -4,6 +4,7 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import nextstep.subway.applicaion.dto.StationResponse; +import nextstep.subway.utils.SecurityUtil; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; @@ -75,7 +76,7 @@ void deleteStation() { // when String location = createResponse.header("location"); - RestAssured.given().log().all() + SecurityUtil.given() .when() .delete(location) .then().log().all() diff --git a/src/test/java/nextstep/subway/acceptance/StationSteps.java b/src/test/java/nextstep/subway/acceptance/StationSteps.java index 92078c1ac..4ba626af9 100644 --- a/src/test/java/nextstep/subway/acceptance/StationSteps.java +++ b/src/test/java/nextstep/subway/acceptance/StationSteps.java @@ -3,19 +3,24 @@ import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import nextstep.MemberData; +import nextstep.auth.token.JwtTokenProvider; +import nextstep.subway.utils.SecurityUtil; import org.springframework.http.MediaType; import java.util.HashMap; import java.util.Map; public class StationSteps { + public static ExtractableResponse 지하철역_생성_요청(String name) { Map params = new HashMap<>(); params.put("name", name); - return RestAssured.given().log().all() - .body(params) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .when() + + return SecurityUtil.given() +// return RestAssured.given().log().all() +// .auth().preemptive().basic(MemberData.admin.getEmail(), MemberData.admin.getPassword()) + .when().contentType(MediaType.APPLICATION_JSON_VALUE).body(params) .post("/stations") .then().log().all() .extract(); diff --git a/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterTest.java b/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterTest.java deleted file mode 100644 index 409bf6533..000000000 --- a/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package nextstep.subway.unit; - -import nextstep.auth.authentication.BearerTokenAuthenticationFilter; -import nextstep.auth.token.JwtTokenProvider; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; - -public class BearerTokenAuthenticationFilterTest { - - JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(); - - BearerTokenAuthenticationFilter filter; - - @Test - void preHandle() { - String token = ""; - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setParameter("Authorization", token); - MockHttpServletResponse response = new MockHttpServletResponse(); - - assertThat(filter.preHandle(request, response, null)).isTrue(); - } -} diff --git a/src/test/java/nextstep/subway/unit/JwtTokenProviderTest.java b/src/test/java/nextstep/subway/unit/JwtTokenProviderTest.java index 910051c0f..079651e7b 100644 --- a/src/test/java/nextstep/subway/unit/JwtTokenProviderTest.java +++ b/src/test/java/nextstep/subway/unit/JwtTokenProviderTest.java @@ -1,6 +1,8 @@ package nextstep.subway.unit; import nextstep.auth.token.JwtTokenProvider; +import nextstep.member.domain.RoleType; +import nextstep.subway.utils.SecurityUtil; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; @@ -12,21 +14,12 @@ public class JwtTokenProviderTest { @Test void token() { - JwtTokenProvider jwtTokenProvider = getTarget(); + JwtTokenProvider jwtTokenProvider = SecurityUtil.getUnlimitedJwtTokenProvider(); String email = "email@email.com"; - String token = jwtTokenProvider.createToken(email, List.of()); - List roles = jwtTokenProvider.getRoles(token); + String token = jwtTokenProvider.createToken(email, List.of(RoleType.ROLE_MEMBER.toString())); assertThat(jwtTokenProvider.getPrincipal(token)).isEqualTo(email); -// TODO: role assert - } - - private JwtTokenProvider getTarget() { - JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(); - ReflectionTestUtils.setField(jwtTokenProvider, "secretKey", "atdd-secret-key"); - ReflectionTestUtils.setField(jwtTokenProvider, "validityInMilliseconds", 3600000); - - return jwtTokenProvider; + assertThat(jwtTokenProvider.getRoles(token)).contains(RoleType.ROLE_MEMBER.toString()); } } diff --git a/src/test/java/nextstep/subway/utils/SecurityUtil.java b/src/test/java/nextstep/subway/utils/SecurityUtil.java new file mode 100644 index 000000000..c7677da42 --- /dev/null +++ b/src/test/java/nextstep/subway/utils/SecurityUtil.java @@ -0,0 +1,29 @@ +package nextstep.subway.utils; + +import io.restassured.RestAssured; +import io.restassured.specification.RequestSpecification; +import nextstep.MemberData; +import nextstep.auth.token.JwtTokenProvider; +import nextstep.member.domain.RoleType; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +public class SecurityUtil { + + static JwtTokenProvider jwtTokenProvider = getUnlimitedJwtTokenProvider(); + static String token = jwtTokenProvider.createToken(MemberData.admin.getEmail(), List.of(RoleType.ROLE_ADMIN.toString())); + + public static RequestSpecification given() { + return RestAssured.given().log().all() + .auth().oauth2(token); + } + + public static JwtTokenProvider getUnlimitedJwtTokenProvider() { + JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(); + ReflectionTestUtils.setField(jwtTokenProvider, "secretKey", "atdd-secret-key"); + ReflectionTestUtils.setField(jwtTokenProvider, "validityInMilliseconds", Integer.MAX_VALUE); + + return jwtTokenProvider; + } +} \ No newline at end of file From fde6d3acb7a66be2dc24bf685463d6568ec8b316 Mon Sep 17 00:00:00 2001 From: ashley Date: Mon, 15 Aug 2022 16:49:55 +0900 Subject: [PATCH 4/9] =?UTF-8?q?[add]=20=EA=B6=8C=ED=95=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B2=B4=ED=81=AC=20=EC=9D=B8=EC=88=98=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../secured/SecuredAnnotationChecker.java | 5 +++ .../subway/ui/ControllerExceptionHandler.java | 6 +++ .../subway/acceptance/AuthAcceptanceTest.java | 39 +++++++++++++++++-- .../subway/acceptance/StationSteps.java | 14 ++++++- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/main/java/nextstep/auth/secured/SecuredAnnotationChecker.java b/src/main/java/nextstep/auth/secured/SecuredAnnotationChecker.java index ee6f2e3f5..aae21f87e 100644 --- a/src/main/java/nextstep/auth/secured/SecuredAnnotationChecker.java +++ b/src/main/java/nextstep/auth/secured/SecuredAnnotationChecker.java @@ -24,6 +24,11 @@ public void checkAuthorities(JoinPoint joinPoint) { List values = Arrays.stream(secured.value()).collect(Collectors.toList()); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if(authentication == null) { + throw new RoleAuthenticationException("권한이 없습니다."); + } + authentication.getAuthorities().stream() .filter(values::contains) .findFirst() diff --git a/src/main/java/nextstep/subway/ui/ControllerExceptionHandler.java b/src/main/java/nextstep/subway/ui/ControllerExceptionHandler.java index 3ad5d7b65..5055c254d 100644 --- a/src/main/java/nextstep/subway/ui/ControllerExceptionHandler.java +++ b/src/main/java/nextstep/subway/ui/ControllerExceptionHandler.java @@ -1,5 +1,6 @@ package nextstep.subway.ui; +import nextstep.auth.secured.RoleAuthenticationException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -16,4 +17,9 @@ public ResponseEntity handleIllegalArgsException(DataIntegrityViolationExc public ResponseEntity handleIllegalArgsException(IllegalArgumentException e) { return ResponseEntity.badRequest().build(); } + + @ExceptionHandler(RoleAuthenticationException.class) + public ResponseEntity handleNoAuthenticationException(RoleAuthenticationException e) { + return ResponseEntity.badRequest().build(); + } } diff --git a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java index 2b8e8cd16..c7653756e 100644 --- a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java @@ -4,6 +4,7 @@ import io.restassured.authentication.FormAuthConfig; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import nextstep.MemberData; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; @@ -13,12 +14,14 @@ import java.util.Map; import static nextstep.subway.acceptance.MemberSteps.*; +import static nextstep.subway.acceptance.StationSteps.지하철역_생성_요청_토큰따로; +import static org.assertj.core.api.Assertions.assertThat; class AuthAcceptanceTest extends AcceptanceTest { - private static final String EMAIL = "admin@email.com"; - private static final String PASSWORD = "password"; - private static final Integer AGE = 20; + private static final String EMAIL = MemberData.admin.getEmail(); + private static final String PASSWORD = MemberData.admin.getPassword(); + private static final Integer AGE = MemberData.admin.getAge(); @DisplayName("Basic Auth") @Test @@ -46,6 +49,36 @@ void myInfoWithBearerAuth() { 회원_정보_조회됨(response, EMAIL, AGE); } + @DisplayName("권한 통과") + @Test + void onlyAdminAuth() { + String accessToken = 로그인_되어_있음(EMAIL, PASSWORD); + + ExtractableResponse response = 지하철역_생성_요청_토큰따로("서울역", accessToken); + + assertThat(response.response().statusCode()).isEqualTo(HttpStatus.CREATED.value()); + } + + @DisplayName("권한 존재하지 않는 토큰 실패") + @Test + void fail_onlyAdminAuth() { + String accessToken = "Bearer invalid"; + + ExtractableResponse response = 지하철역_생성_요청_토큰따로("서울역", accessToken); + + assertThat(response.response().statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + @DisplayName("권한 부족한 토큰 실패") + @Test + void fail_notAdminAuth() { + String accessToken = 로그인_되어_있음(MemberData.member.getEmail(), MemberData.member.getPassword()); + + ExtractableResponse response = 지하철역_생성_요청_토큰따로("서울역", accessToken); + + assertThat(response.response().statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + private ExtractableResponse 폼_로그인_후_내_회원_정보_조회_요청(String email, String password) { return RestAssured.given().log().all() .auth().form(email, password, new FormAuthConfig("/login/form", "email", "password")) diff --git a/src/test/java/nextstep/subway/acceptance/StationSteps.java b/src/test/java/nextstep/subway/acceptance/StationSteps.java index 4ba626af9..ff446ae8e 100644 --- a/src/test/java/nextstep/subway/acceptance/StationSteps.java +++ b/src/test/java/nextstep/subway/acceptance/StationSteps.java @@ -18,8 +18,18 @@ public class StationSteps { params.put("name", name); return SecurityUtil.given() -// return RestAssured.given().log().all() -// .auth().preemptive().basic(MemberData.admin.getEmail(), MemberData.admin.getPassword()) + .when().contentType(MediaType.APPLICATION_JSON_VALUE).body(params) + .post("/stations") + .then().log().all() + .extract(); + } + + public static ExtractableResponse 지하철역_생성_요청_토큰따로(String name, String token) { + Map params = new HashMap<>(); + params.put("name", name); + + return RestAssured.given().log().all() + .auth().oauth2(token) .when().contentType(MediaType.APPLICATION_JSON_VALUE).body(params) .post("/stations") .then().log().all() From a3ef7154204b643a0a6dcb774b7c4b59f1072b19 Mon Sep 17 00:00:00 2001 From: ashley Date: Mon, 15 Aug 2022 22:03:03 +0900 Subject: [PATCH 5/9] =?UTF-8?q?[modify]=20TokenAuthenticationInterceptor,?= =?UTF-8?q?=20UsernamePasswordAuthenticationFilter=20=EC=B6=94=EC=83=81?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authentication/AuthenticationToken.java | 3 ++ .../auth/authentication/Authenticator.java | 45 ++++++++++++++++ .../UsernamePasswordAuthenticationFilter.java | 28 +++------- .../nextstep/auth/context/Authentication.java | 3 ++ .../token/TokenAuthenticationInterceptor.java | 30 +++++------ .../nextstep/auth/token/TokenResponse.java | 3 ++ .../TokenAuthenticationInterceptorTest.java | 52 +++++++++++++++++-- ...ePasswordAuthenticationFilterMockTest.java | 31 ++++++++++- 8 files changed, 153 insertions(+), 42 deletions(-) create mode 100644 src/main/java/nextstep/auth/authentication/Authenticator.java diff --git a/src/main/java/nextstep/auth/authentication/AuthenticationToken.java b/src/main/java/nextstep/auth/authentication/AuthenticationToken.java index ffc009d57..d45d96ac3 100644 --- a/src/main/java/nextstep/auth/authentication/AuthenticationToken.java +++ b/src/main/java/nextstep/auth/authentication/AuthenticationToken.java @@ -1,5 +1,8 @@ package nextstep.auth.authentication; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode public class AuthenticationToken { private String principal; private String credentials; diff --git a/src/main/java/nextstep/auth/authentication/Authenticator.java b/src/main/java/nextstep/auth/authentication/Authenticator.java new file mode 100644 index 000000000..681314d40 --- /dev/null +++ b/src/main/java/nextstep/auth/authentication/Authenticator.java @@ -0,0 +1,45 @@ +package nextstep.auth.authentication; + +import nextstep.member.application.LoginMemberService; +import nextstep.member.domain.LoginMember; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public abstract class Authenticator implements HandlerInterceptor { + + private final LoginMemberService loginMemberService; + + public Authenticator(LoginMemberService loginMemberService) { + this.loginMemberService = loginMemberService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + AuthenticationToken token = convert(request); + LoginMember member; + + try { + member = loginMemberService.loadUserByUsername(token.getPrincipal()); + } catch (RuntimeException e) { + throw new AuthenticationException(); + } + + checkAuthentication(member, token.getCredentials()); + authenticate(member, response); + + return false; + } + + abstract public AuthenticationToken convert(HttpServletRequest request) throws IOException; + + abstract public void authenticate(LoginMember member, HttpServletResponse response) throws IOException; + + private void checkAuthentication(LoginMember member, String password) { + if (!member.checkPassword(password)) { + throw new AuthenticationException(); + } + } +} diff --git a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java index 61f46b08b..5b20d208e 100644 --- a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java @@ -11,35 +11,23 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.List; -public class UsernamePasswordAuthenticationFilter implements HandlerInterceptor { - private final LoginMemberService loginMemberService; +public class UsernamePasswordAuthenticationFilter extends Authenticator { public UsernamePasswordAuthenticationFilter(LoginMemberService loginMemberService) { - this.loginMemberService = loginMemberService; + super(loginMemberService); } @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - String email = request.getParameter("email"); - String password = request.getParameter("password"); - - LoginMember member; - - try { - member = loginMemberService.loadUserByUsername(email); - } catch (RuntimeException e) { - throw new AuthenticationException(); - } - - if (!member.checkPassword(password)) { - throw new AuthenticationException(); - } + public AuthenticationToken convert(HttpServletRequest request) { + return new AuthenticationToken(request.getParameter("email"), request.getParameter("password")); + } + @Override + public void authenticate(LoginMember member, HttpServletResponse response) throws IOException { Authentication authentication = new Authentication(member.getEmail(), member.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); - - return true; } } diff --git a/src/main/java/nextstep/auth/context/Authentication.java b/src/main/java/nextstep/auth/context/Authentication.java index 35395d387..98049ff2a 100644 --- a/src/main/java/nextstep/auth/context/Authentication.java +++ b/src/main/java/nextstep/auth/context/Authentication.java @@ -1,8 +1,11 @@ package nextstep.auth.context; +import lombok.EqualsAndHashCode; + import java.io.Serializable; import java.util.List; +@EqualsAndHashCode public class Authentication implements Serializable { private Object principal; private List authorities; diff --git a/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java b/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java index 268443936..d9a44406f 100644 --- a/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java +++ b/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java @@ -1,7 +1,10 @@ package nextstep.auth.token; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import nextstep.auth.authentication.AuthenticationException; +import nextstep.auth.authentication.AuthenticationToken; +import nextstep.auth.authentication.Authenticator; import nextstep.member.application.LoginMemberService; import nextstep.member.domain.LoginMember; import org.springframework.http.MediaType; @@ -9,43 +12,36 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.stream.Collectors; -public class TokenAuthenticationInterceptor implements HandlerInterceptor { - private LoginMemberService loginMemberService; - private JwtTokenProvider jwtTokenProvider; +public class TokenAuthenticationInterceptor extends Authenticator { + private final JwtTokenProvider jwtTokenProvider; public TokenAuthenticationInterceptor(LoginMemberService loginMemberService, JwtTokenProvider jwtTokenProvider) { - this.loginMemberService = loginMemberService; + super(loginMemberService); this.jwtTokenProvider = jwtTokenProvider; } @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + public AuthenticationToken convert(HttpServletRequest request) throws IOException { String content = request.getReader().lines().collect(Collectors.joining(System.lineSeparator())); TokenRequest tokenRequest = new ObjectMapper().readValue(content, TokenRequest.class); String principal = tokenRequest.getEmail(); String credentials = tokenRequest.getPassword(); - LoginMember loginMember = loginMemberService.loadUserByUsername(principal); - - if (loginMember == null) { - throw new AuthenticationException(); - } - - if (!loginMember.checkPassword(credentials)) { - throw new AuthenticationException(); - } + return new AuthenticationToken(principal, credentials); + } - String token = jwtTokenProvider.createToken(loginMember.getEmail(), loginMember.getAuthorities()); + @Override + public void authenticate(LoginMember member, HttpServletResponse response) throws IOException { + String token = jwtTokenProvider.createToken(member.getEmail(), member.getAuthorities()); TokenResponse tokenResponse = new TokenResponse(token); String responseToClient = new ObjectMapper().writeValueAsString(tokenResponse); response.setStatus(HttpServletResponse.SC_OK); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getOutputStream().print(responseToClient); - - return false; } } diff --git a/src/main/java/nextstep/auth/token/TokenResponse.java b/src/main/java/nextstep/auth/token/TokenResponse.java index 69a72a148..b7d4285b1 100644 --- a/src/main/java/nextstep/auth/token/TokenResponse.java +++ b/src/main/java/nextstep/auth/token/TokenResponse.java @@ -1,5 +1,8 @@ package nextstep.auth.token; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode public class TokenResponse { private String accessToken; diff --git a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java index 6ef358e0a..b4398bb0b 100644 --- a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java +++ b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java @@ -1,34 +1,80 @@ package nextstep.subway.unit; import com.fasterxml.jackson.databind.ObjectMapper; +import nextstep.auth.authentication.AuthenticationToken; +import nextstep.auth.token.JwtTokenProvider; +import nextstep.auth.token.TokenAuthenticationInterceptor; import nextstep.auth.token.TokenRequest; +import nextstep.auth.token.TokenResponse; +import nextstep.member.application.LoginMemberService; +import nextstep.member.domain.LoginMember; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; + +@ExtendWith({MockitoExtension.class}) class TokenAuthenticationInterceptorTest { private static final String EMAIL = "email@email.com"; private static final String PASSWORD = "password"; public static final String JWT_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.ih1aovtQShabQ7l0cINw4k1fagApg3qLWiB8Kt59Lno"; + @Mock + JwtTokenProvider jwtTokenProvider; + + @Mock + LoginMemberService loginMemberService; + + ObjectMapper mapper = new ObjectMapper(); + + @InjectMocks + TokenAuthenticationInterceptor interceptor; + @Test void convert() throws IOException { + AuthenticationToken token = interceptor.convert(createMockRequest()); + + assertThat(token).isEqualTo(new AuthenticationToken(EMAIL, PASSWORD)); } @Test - void authenticate() { + void authenticate() throws IOException { + MockHttpServletResponse response = new MockHttpServletResponse(); + when(jwtTokenProvider.createToken(any(), any())).thenReturn(JWT_TOKEN); + + interceptor.authenticate(new LoginMember(EMAIL, PASSWORD, List.of()), response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(mapper.readValue(response.getContentAsString(), TokenResponse.class)).isEqualTo(new TokenResponse(JWT_TOKEN)); } @Test void preHandle() throws IOException { + MockHttpServletResponse response = new MockHttpServletResponse(); + given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); + + boolean result = interceptor.preHandle(createMockRequest(), response, null); + + assertThat(result).isFalse(); } private MockHttpServletRequest createMockRequest() throws IOException { MockHttpServletRequest request = new MockHttpServletRequest(); TokenRequest tokenRequest = new TokenRequest(EMAIL, PASSWORD); - request.setContent(new ObjectMapper().writeValueAsString(tokenRequest).getBytes()); + request.setContent(mapper.writeValueAsString(tokenRequest).getBytes()); + return request; } - } diff --git a/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java index 1f638a587..5f7b585e0 100644 --- a/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java +++ b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java @@ -1,6 +1,9 @@ package nextstep.subway.unit; +import nextstep.auth.authentication.AuthenticationToken; import nextstep.auth.authentication.UsernamePasswordAuthenticationFilter; +import nextstep.auth.context.Authentication; +import nextstep.auth.context.SecurityContextHolder; import nextstep.member.application.LoginMemberService; import nextstep.member.domain.LoginMember; import org.junit.jupiter.api.Test; @@ -11,8 +14,10 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import java.io.IOException; import java.util.List; +import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -27,7 +32,7 @@ public class UsernamePasswordAuthenticationFilterMockTest { UsernamePasswordAuthenticationFilter filter; @Test - void preHandle() { + void preHandle() throws IOException { String email = "admin@email.com"; String password = "password"; MockHttpServletRequest request = new MockHttpServletRequest(); @@ -37,6 +42,28 @@ void preHandle() { given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(email, password, List.of())); - assertThat(filter.preHandle(request, response, null)).isTrue(); + assertThat(filter.preHandle(request, response, null)).isFalse(); + } + + @Test + void convert() { + String email = "admin@email.com"; + String password = "password"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("email", email); + request.setParameter("password", password); + + assertThat(filter.convert(request)).isEqualTo(new AuthenticationToken(email, password)); + } + + @Test + void authenticate() throws IOException { + String email = "admin@email.com"; + String password = "password"; + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.authenticate(new LoginMember(email, password, List.of()), response); + + assertThat(SecurityContextHolder.getContext().getAuthentication()).isEqualTo(new Authentication(email, List.of())); } } From c0c4ab36cbb46dde86f73a40237b74dca174af14 Mon Sep 17 00:00:00 2001 From: ashley Date: Mon, 15 Aug 2022 23:05:05 +0900 Subject: [PATCH 6/9] =?UTF-8?q?[modify]=20BasicAuthenticationFilter,=20Bea?= =?UTF-8?q?rerTokenAuthenticationFilter=20=EC=B6=94=EC=83=81=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/authentication/Authorizator.java | 26 +++++++++ .../BasicAuthenticationFilter.java | 42 +++++++------- .../BearerTokenAuthenticationFilter.java | 21 ++----- .../nextstep/auth/token/JwtTokenProvider.java | 9 +++ .../BasicAuthenticationFilterMockTest.java | 58 +++++++++++++++++++ ...arerTokenAuthenticationFilterMockTest.java | 50 ++++++++++++++++ ...kenAuthenticationInterceptorMockTest.java} | 2 +- 7 files changed, 169 insertions(+), 39 deletions(-) create mode 100644 src/main/java/nextstep/auth/authentication/Authorizator.java create mode 100644 src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java create mode 100644 src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java rename src/test/java/nextstep/subway/unit/{TokenAuthenticationInterceptorTest.java => TokenAuthenticationInterceptorMockTest.java} (98%) diff --git a/src/main/java/nextstep/auth/authentication/Authorizator.java b/src/main/java/nextstep/auth/authentication/Authorizator.java new file mode 100644 index 000000000..9f218ba42 --- /dev/null +++ b/src/main/java/nextstep/auth/authentication/Authorizator.java @@ -0,0 +1,26 @@ +package nextstep.auth.authentication; + +import nextstep.auth.context.Authentication; +import nextstep.auth.context.SecurityContextHolder; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public abstract class Authorizator implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + + try { + Authentication authentication = convert(request); + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (RuntimeException e){ + return true; + } + + return true; + } + + abstract public Authentication convert(HttpServletRequest request); +} diff --git a/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java index 21b304050..879633212 100644 --- a/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java @@ -10,41 +10,39 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -public class BasicAuthenticationFilter implements HandlerInterceptor { - private LoginMemberService loginMemberService; +public class BasicAuthenticationFilter extends Authorizator { + + private final LoginMemberService loginMemberService; public BasicAuthenticationFilter(LoginMemberService loginMemberService) { this.loginMemberService = loginMemberService; } @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - try { - String authCredentials = AuthorizationExtractor.extract(request, AuthorizationType.BASIC); - String authHeader = new String(Base64.decodeBase64(authCredentials)); + public Authentication convert(HttpServletRequest request) { + AuthenticationToken token = getToken(request); + LoginMember loginMember = loginMemberService.loadUserByUsername(token.getPrincipal()); - String[] splits = authHeader.split(":"); - String principal = splits[0]; - String credentials = splits[1]; + checkAuthentication(token, loginMember); - AuthenticationToken token = new AuthenticationToken(principal, credentials); + return new Authentication(loginMember.getEmail(), loginMember.getAuthorities()); + } - LoginMember loginMember = loginMemberService.loadUserByUsername(token.getPrincipal()); - if (loginMember == null) { - throw new AuthenticationException(); - } - if (!loginMember.checkPassword(token.getCredentials())) { - throw new AuthenticationException(); - } + private AuthenticationToken getToken(HttpServletRequest request) { + String authCredentials = AuthorizationExtractor.extract(request, AuthorizationType.BASIC); + String authHeader = new String(Base64.decodeBase64(authCredentials)); - Authentication authentication = new Authentication(loginMember.getEmail(), loginMember.getAuthorities()); + String[] splits = authHeader.split(":"); + String principal = splits[0]; + String credentials = splits[1]; - SecurityContextHolder.getContext().setAuthentication(authentication); + return new AuthenticationToken(principal, credentials); + } - return true; - } catch (Exception e) { - return true; + private void checkAuthentication(AuthenticationToken token, LoginMember loginMember) { + if (!loginMember.checkPassword(token.getCredentials())) { + throw new AuthenticationException(); } } } diff --git a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java index 65c6bcb82..76b414940 100644 --- a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java @@ -3,13 +3,14 @@ import nextstep.auth.context.Authentication; import nextstep.auth.context.SecurityContextHolder; import nextstep.auth.token.JwtTokenProvider; +import nextstep.member.domain.LoginMember; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; -public class BearerTokenAuthenticationFilter implements HandlerInterceptor { +public class BearerTokenAuthenticationFilter extends Authorizator { private final JwtTokenProvider jwtTokenProvider; public BearerTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { @@ -17,21 +18,9 @@ public BearerTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { } @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - try { - String authCredentials = AuthorizationExtractor.extract(request, AuthorizationType.BEARER); + public Authentication convert(HttpServletRequest request) { + String authCredentials = AuthorizationExtractor.extract(request, AuthorizationType.BEARER); - jwtTokenProvider.validateToken(authCredentials); - String principal = jwtTokenProvider.getPrincipal(authCredentials); - List roles = jwtTokenProvider.getRoles(authCredentials); - - Authentication authentication = new Authentication(principal, roles); - - SecurityContextHolder.getContext().setAuthentication(authentication); - - return true; - } catch (RuntimeException exception) { - return true; - } + return jwtTokenProvider.toAuthentication(authCredentials); } } diff --git a/src/main/java/nextstep/auth/token/JwtTokenProvider.java b/src/main/java/nextstep/auth/token/JwtTokenProvider.java index 813d91b87..d2ef453de 100644 --- a/src/main/java/nextstep/auth/token/JwtTokenProvider.java +++ b/src/main/java/nextstep/auth/token/JwtTokenProvider.java @@ -1,6 +1,7 @@ package nextstep.auth.token; import io.jsonwebtoken.*; +import nextstep.auth.context.Authentication; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -14,6 +15,14 @@ public class JwtTokenProvider { @Value("${security.jwt.token.expire-length}") private long validityInMilliseconds; + public Authentication toAuthentication(String token) { + validateToken(token); + String principal = getPrincipal(token); + List roles = getRoles(token); + + return new Authentication(principal, roles); + } + public String createToken(String principal, List roles) { Claims claims = Jwts.claims().setSubject(principal); Date now = new Date(); diff --git a/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java new file mode 100644 index 000000000..cb50f44d4 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java @@ -0,0 +1,58 @@ +package nextstep.subway.unit; + +import nextstep.auth.authentication.AuthenticationToken; +import nextstep.auth.authentication.BasicAuthenticationFilter; +import nextstep.auth.authentication.UsernamePasswordAuthenticationFilter; +import nextstep.auth.context.Authentication; +import nextstep.auth.context.SecurityContextHolder; +import nextstep.member.application.LoginMemberService; +import nextstep.member.domain.LoginMember; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +public class BasicAuthenticationFilterMockTest { + + static String EMAIL = "admin@email.com"; + static String PASSWORD = "password"; + static String BASIC_TOKEN = "Basic YWRtaW5AZW1haWwuY29tOnBhc3N3b3Jk"; + + @Mock + LoginMemberService loginMemberService; + + @InjectMocks + BasicAuthenticationFilter filter; + + @Test + void preHandle() throws IOException { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", BASIC_TOKEN); + MockHttpServletResponse response = new MockHttpServletResponse(); + + given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); + + assertThat(filter.preHandle(request, response, null)).isTrue(); + } + + @Test + void convert() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", BASIC_TOKEN); + + given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); + + assertThat(filter.convert(request)).isEqualTo(new Authentication(EMAIL, List.of())); + } +} diff --git a/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java new file mode 100644 index 000000000..e6d263101 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java @@ -0,0 +1,50 @@ +package nextstep.subway.unit; + +import nextstep.auth.authentication.BasicAuthenticationFilter; +import nextstep.auth.authentication.BearerTokenAuthenticationFilter; +import nextstep.auth.context.Authentication; +import nextstep.auth.context.SecurityContextHolder; +import nextstep.auth.token.JwtTokenProvider; +import nextstep.member.application.LoginMemberService; +import nextstep.member.domain.LoginMember; +import nextstep.subway.utils.SecurityUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class BearerTokenAuthenticationFilterMockTest { + + static String EMAIL = "admin@email.com"; + static String BEARER_TOKEN = SecurityUtil.getUnlimitedJwtTokenProvider().createToken(EMAIL, List.of()); + + @Mock + JwtTokenProvider jwtTokenProvider; + @InjectMocks + BearerTokenAuthenticationFilter filter; + + @Test + void preHandle() throws IOException { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + BEARER_TOKEN); + MockHttpServletResponse response = new MockHttpServletResponse(); + Authentication authentication = new Authentication(EMAIL, List.of()); + + when(jwtTokenProvider.toAuthentication(BEARER_TOKEN)).thenReturn(authentication); + + assertThat(filter.preHandle(request, response, null)).isTrue(); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isEqualTo(authentication); + } +} diff --git a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java similarity index 98% rename from src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java rename to src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java index b4398bb0b..6ceaa0b9e 100644 --- a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java +++ b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java @@ -26,7 +26,7 @@ import static org.mockito.Mockito.when; @ExtendWith({MockitoExtension.class}) -class TokenAuthenticationInterceptorTest { +class TokenAuthenticationInterceptorMockTest { private static final String EMAIL = "email@email.com"; private static final String PASSWORD = "password"; public static final String JWT_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.ih1aovtQShabQ7l0cINw4k1fagApg3qLWiB8Kt59Lno"; From d7c23e25518667ae44d18efa648a60f4dbf087ce Mon Sep 17 00:00:00 2001 From: ashley Date: Mon, 15 Aug 2022 23:14:00 +0900 Subject: [PATCH 7/9] =?UTF-8?q?[modify]=20auth=EC=97=90=EC=84=9C=20LoginMe?= =?UTF-8?q?mberService=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/DataLoader.java | 8 -------- src/main/java/nextstep/auth/AuthConfig.java | 14 +++++++------- .../auth/authentication/Authenticator.java | 10 +++++----- .../authentication/BasicAuthenticationFilter.java | 13 +++++-------- .../UsernamePasswordAuthenticationFilter.java | 11 +++-------- .../auth/token/TokenAuthenticationInterceptor.java | 9 +++------ .../member/application/LoginMemberService.java | 2 +- .../member/application/UserDetailsService.java | 8 ++++++++ .../unit/BasicAuthenticationFilterMockTest.java | 11 ++++------- .../BearerTokenAuthenticationFilterMockTest.java | 4 ---- .../TokenAuthenticationInterceptorMockTest.java | 6 +++--- ...ernamePasswordAuthenticationFilterMockTest.java | 7 +++---- 12 files changed, 42 insertions(+), 61 deletions(-) create mode 100644 src/main/java/nextstep/member/application/UserDetailsService.java diff --git a/src/main/java/nextstep/DataLoader.java b/src/main/java/nextstep/DataLoader.java index 9a374fea8..af7df4105 100644 --- a/src/main/java/nextstep/DataLoader.java +++ b/src/main/java/nextstep/DataLoader.java @@ -1,16 +1,8 @@ package nextstep; import lombok.AllArgsConstructor; -import nextstep.member.application.LoginMemberService; -import nextstep.member.application.MemberService; -import nextstep.member.application.dto.MemberRequest; -import nextstep.member.domain.Member; import nextstep.member.domain.MemberRepository; -import nextstep.member.domain.RoleType; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; @Component @AllArgsConstructor diff --git a/src/main/java/nextstep/auth/AuthConfig.java b/src/main/java/nextstep/auth/AuthConfig.java index 53e4e9835..2c10f85fd 100644 --- a/src/main/java/nextstep/auth/AuthConfig.java +++ b/src/main/java/nextstep/auth/AuthConfig.java @@ -7,7 +7,7 @@ import nextstep.auth.context.SecurityContextPersistenceFilter; import nextstep.auth.token.JwtTokenProvider; import nextstep.auth.token.TokenAuthenticationInterceptor; -import nextstep.member.application.LoginMemberService; +import nextstep.member.application.UserDetailsService; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -16,20 +16,20 @@ @Configuration public class AuthConfig implements WebMvcConfigurer { - private LoginMemberService loginMemberService; + private UserDetailsService userDetailsService; private JwtTokenProvider jwtTokenProvider; - public AuthConfig(LoginMemberService loginMemberService, JwtTokenProvider jwtTokenProvider) { - this.loginMemberService = loginMemberService; + public AuthConfig(UserDetailsService userDetailsService, JwtTokenProvider jwtTokenProvider) { + this.userDetailsService = userDetailsService; this.jwtTokenProvider = jwtTokenProvider; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SecurityContextPersistenceFilter()); - registry.addInterceptor(new UsernamePasswordAuthenticationFilter(loginMemberService)).addPathPatterns("/login/form"); - registry.addInterceptor(new TokenAuthenticationInterceptor(loginMemberService, jwtTokenProvider)).addPathPatterns("/login/token"); - registry.addInterceptor(new BasicAuthenticationFilter(loginMemberService)); + registry.addInterceptor(new UsernamePasswordAuthenticationFilter(userDetailsService)).addPathPatterns("/login/form"); + registry.addInterceptor(new TokenAuthenticationInterceptor(userDetailsService, jwtTokenProvider)).addPathPatterns("/login/token"); + registry.addInterceptor(new BasicAuthenticationFilter(userDetailsService)); registry.addInterceptor(new BearerTokenAuthenticationFilter(jwtTokenProvider)); } diff --git a/src/main/java/nextstep/auth/authentication/Authenticator.java b/src/main/java/nextstep/auth/authentication/Authenticator.java index 681314d40..81f5f4f5f 100644 --- a/src/main/java/nextstep/auth/authentication/Authenticator.java +++ b/src/main/java/nextstep/auth/authentication/Authenticator.java @@ -1,6 +1,6 @@ package nextstep.auth.authentication; -import nextstep.member.application.LoginMemberService; +import nextstep.member.application.UserDetailsService; import nextstep.member.domain.LoginMember; import org.springframework.web.servlet.HandlerInterceptor; @@ -10,10 +10,10 @@ public abstract class Authenticator implements HandlerInterceptor { - private final LoginMemberService loginMemberService; + private final UserDetailsService userDetailsService; - public Authenticator(LoginMemberService loginMemberService) { - this.loginMemberService = loginMemberService; + public Authenticator(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; } @Override @@ -22,7 +22,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons LoginMember member; try { - member = loginMemberService.loadUserByUsername(token.getPrincipal()); + member = userDetailsService.loadUserByUsername(token.getPrincipal()); } catch (RuntimeException e) { throw new AuthenticationException(); } diff --git a/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java index 879633212..e74b5a72b 100644 --- a/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java @@ -1,27 +1,24 @@ package nextstep.auth.authentication; import nextstep.auth.context.Authentication; -import nextstep.auth.context.SecurityContextHolder; -import nextstep.member.application.LoginMemberService; +import nextstep.member.application.UserDetailsService; import nextstep.member.domain.LoginMember; import org.apache.tomcat.util.codec.binary.Base64; -import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; public class BasicAuthenticationFilter extends Authorizator { - private final LoginMemberService loginMemberService; + private final UserDetailsService userDetailsService; - public BasicAuthenticationFilter(LoginMemberService loginMemberService) { - this.loginMemberService = loginMemberService; + public BasicAuthenticationFilter(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; } @Override public Authentication convert(HttpServletRequest request) { AuthenticationToken token = getToken(request); - LoginMember loginMember = loginMemberService.loadUserByUsername(token.getPrincipal()); + LoginMember loginMember = userDetailsService.loadUserByUsername(token.getPrincipal()); checkAuthentication(token, loginMember); diff --git a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java index 5b20d208e..a32687637 100644 --- a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java @@ -1,23 +1,18 @@ package nextstep.auth.authentication; -import com.fasterxml.jackson.databind.ObjectMapper; import nextstep.auth.context.Authentication; -import nextstep.auth.context.SecurityContext; import nextstep.auth.context.SecurityContextHolder; -import nextstep.member.application.LoginMemberService; +import nextstep.member.application.UserDetailsService; import nextstep.member.domain.LoginMember; -import nextstep.member.domain.Member; -import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.List; public class UsernamePasswordAuthenticationFilter extends Authenticator { - public UsernamePasswordAuthenticationFilter(LoginMemberService loginMemberService) { - super(loginMemberService); + public UsernamePasswordAuthenticationFilter(UserDetailsService userDetailsService) { + super(userDetailsService); } @Override diff --git a/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java b/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java index d9a44406f..d636ae9b5 100644 --- a/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java +++ b/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java @@ -1,14 +1,11 @@ package nextstep.auth.token; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import nextstep.auth.authentication.AuthenticationException; import nextstep.auth.authentication.AuthenticationToken; import nextstep.auth.authentication.Authenticator; -import nextstep.member.application.LoginMemberService; +import nextstep.member.application.UserDetailsService; import nextstep.member.domain.LoginMember; import org.springframework.http.MediaType; -import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -18,8 +15,8 @@ public class TokenAuthenticationInterceptor extends Authenticator { private final JwtTokenProvider jwtTokenProvider; - public TokenAuthenticationInterceptor(LoginMemberService loginMemberService, JwtTokenProvider jwtTokenProvider) { - super(loginMemberService); + public TokenAuthenticationInterceptor(UserDetailsService userDetailsService, JwtTokenProvider jwtTokenProvider) { + super(userDetailsService); this.jwtTokenProvider = jwtTokenProvider; } diff --git a/src/main/java/nextstep/member/application/LoginMemberService.java b/src/main/java/nextstep/member/application/LoginMemberService.java index 8a6ecd6d9..9fbe54f5f 100644 --- a/src/main/java/nextstep/member/application/LoginMemberService.java +++ b/src/main/java/nextstep/member/application/LoginMemberService.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Service; @Service -public class LoginMemberService { +public class LoginMemberService implements UserDetailsService { private final MemberRepository memberRepository; public LoginMemberService(MemberRepository memberRepository) { diff --git a/src/main/java/nextstep/member/application/UserDetailsService.java b/src/main/java/nextstep/member/application/UserDetailsService.java new file mode 100644 index 000000000..d8a7de586 --- /dev/null +++ b/src/main/java/nextstep/member/application/UserDetailsService.java @@ -0,0 +1,8 @@ +package nextstep.member.application; + +import nextstep.member.domain.LoginMember; + +public interface UserDetailsService { + + LoginMember loadUserByUsername(String email); +} diff --git a/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java index cb50f44d4..c71689588 100644 --- a/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java +++ b/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java @@ -1,11 +1,8 @@ package nextstep.subway.unit; -import nextstep.auth.authentication.AuthenticationToken; import nextstep.auth.authentication.BasicAuthenticationFilter; -import nextstep.auth.authentication.UsernamePasswordAuthenticationFilter; import nextstep.auth.context.Authentication; -import nextstep.auth.context.SecurityContextHolder; -import nextstep.member.application.LoginMemberService; +import nextstep.member.application.UserDetailsService; import nextstep.member.domain.LoginMember; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -30,7 +27,7 @@ public class BasicAuthenticationFilterMockTest { static String BASIC_TOKEN = "Basic YWRtaW5AZW1haWwuY29tOnBhc3N3b3Jk"; @Mock - LoginMemberService loginMemberService; + UserDetailsService userDetailsService; @InjectMocks BasicAuthenticationFilter filter; @@ -41,7 +38,7 @@ void preHandle() throws IOException { request.addHeader("Authorization", BASIC_TOKEN); MockHttpServletResponse response = new MockHttpServletResponse(); - given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); + given(userDetailsService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); assertThat(filter.preHandle(request, response, null)).isTrue(); } @@ -51,7 +48,7 @@ void convert() { MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("Authorization", BASIC_TOKEN); - given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); + given(userDetailsService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); assertThat(filter.convert(request)).isEqualTo(new Authentication(EMAIL, List.of())); } diff --git a/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java index e6d263101..843e0d692 100644 --- a/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java +++ b/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java @@ -1,12 +1,9 @@ package nextstep.subway.unit; -import nextstep.auth.authentication.BasicAuthenticationFilter; import nextstep.auth.authentication.BearerTokenAuthenticationFilter; import nextstep.auth.context.Authentication; import nextstep.auth.context.SecurityContextHolder; import nextstep.auth.token.JwtTokenProvider; -import nextstep.member.application.LoginMemberService; -import nextstep.member.domain.LoginMember; import nextstep.subway.utils.SecurityUtil; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -21,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) diff --git a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java index 6ceaa0b9e..c5cf2515a 100644 --- a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java +++ b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java @@ -6,7 +6,7 @@ import nextstep.auth.token.TokenAuthenticationInterceptor; import nextstep.auth.token.TokenRequest; import nextstep.auth.token.TokenResponse; -import nextstep.member.application.LoginMemberService; +import nextstep.member.application.UserDetailsService; import nextstep.member.domain.LoginMember; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,7 +35,7 @@ class TokenAuthenticationInterceptorMockTest { JwtTokenProvider jwtTokenProvider; @Mock - LoginMemberService loginMemberService; + UserDetailsService userDetailsService; ObjectMapper mapper = new ObjectMapper(); @@ -63,7 +63,7 @@ void authenticate() throws IOException { @Test void preHandle() throws IOException { MockHttpServletResponse response = new MockHttpServletResponse(); - given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); + given(userDetailsService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); boolean result = interceptor.preHandle(createMockRequest(), response, null); diff --git a/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java index 5f7b585e0..6e92a8234 100644 --- a/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java +++ b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java @@ -4,7 +4,7 @@ import nextstep.auth.authentication.UsernamePasswordAuthenticationFilter; import nextstep.auth.context.Authentication; import nextstep.auth.context.SecurityContextHolder; -import nextstep.member.application.LoginMemberService; +import nextstep.member.application.UserDetailsService; import nextstep.member.domain.LoginMember; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,7 +17,6 @@ import java.io.IOException; import java.util.List; -import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -26,7 +25,7 @@ public class UsernamePasswordAuthenticationFilterMockTest { @Mock - LoginMemberService loginMemberService; + UserDetailsService userDetailsService; @InjectMocks UsernamePasswordAuthenticationFilter filter; @@ -40,7 +39,7 @@ void preHandle() throws IOException { request.setParameter("password", password); MockHttpServletResponse response = new MockHttpServletResponse(); - given(loginMemberService.loadUserByUsername(any())).willReturn(new LoginMember(email, password, List.of())); + given(userDetailsService.loadUserByUsername(any())).willReturn(new LoginMember(email, password, List.of())); assertThat(filter.preHandle(request, response, null)).isFalse(); } From 54cdba1d06042d2efcad4dca0038b74b4273162d Mon Sep 17 00:00:00 2001 From: ashley Date: Tue, 16 Aug 2022 00:37:37 +0900 Subject: [PATCH 8/9] =?UTF-8?q?[modify]=20step1=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/MemberData.java | 7 ++---- .../auth/authentication/Authenticator.java | 8 +------ .../member/application/MemberService.java | 2 ++ .../nextstep/member/ui/MemberController.java | 2 ++ .../subway/ui/ControllerExceptionHandler.java | 3 ++- .../subway/acceptance/AuthAcceptanceTest.java | 5 ++-- .../acceptance/MemberAcceptanceTest.java | 24 ++++++++++++++++++- .../subway/acceptance/MemberSteps.java | 21 ++++++++++++++-- .../nextstep/subway/utils/SecurityUtil.java | 8 +++---- 9 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/main/java/nextstep/MemberData.java b/src/main/java/nextstep/MemberData.java index e9d678877..85157ba37 100644 --- a/src/main/java/nextstep/MemberData.java +++ b/src/main/java/nextstep/MemberData.java @@ -1,10 +1,7 @@ package nextstep; -import lombok.AllArgsConstructor; import nextstep.member.domain.Member; -import nextstep.member.domain.MemberRepository; import nextstep.member.domain.RoleType; -import org.springframework.stereotype.Component; import java.util.List; @@ -17,6 +14,6 @@ public class MemberData { private static final String MEMBER_PASSWORD = "password"; private static final int MEMBER_AGE = 20; - public static Member admin = new Member(ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_AGE, List.of(RoleType.ROLE_ADMIN.toString())); - public static Member member = new Member(MEMBER_EMAIL, MEMBER_PASSWORD, MEMBER_AGE, List.of(RoleType.ROLE_MEMBER.toString())); + public static Member admin = new Member(ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_AGE, List.of(RoleType.ROLE_ADMIN.name())); + public static Member member = new Member(MEMBER_EMAIL, MEMBER_PASSWORD, MEMBER_AGE, List.of(RoleType.ROLE_MEMBER.name())); } diff --git a/src/main/java/nextstep/auth/authentication/Authenticator.java b/src/main/java/nextstep/auth/authentication/Authenticator.java index 81f5f4f5f..c47017556 100644 --- a/src/main/java/nextstep/auth/authentication/Authenticator.java +++ b/src/main/java/nextstep/auth/authentication/Authenticator.java @@ -19,13 +19,7 @@ public Authenticator(UserDetailsService userDetailsService) { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { AuthenticationToken token = convert(request); - LoginMember member; - - try { - member = userDetailsService.loadUserByUsername(token.getPrincipal()); - } catch (RuntimeException e) { - throw new AuthenticationException(); - } + LoginMember member = userDetailsService.loadUserByUsername(token.getPrincipal()); checkAuthentication(member, token.getCredentials()); authenticate(member, response); diff --git a/src/main/java/nextstep/member/application/MemberService.java b/src/main/java/nextstep/member/application/MemberService.java index 477e4bf08..2be438e09 100644 --- a/src/main/java/nextstep/member/application/MemberService.java +++ b/src/main/java/nextstep/member/application/MemberService.java @@ -30,11 +30,13 @@ public MemberResponse findMember(String email) { return MemberResponse.of(member); } + @Transactional public void updateMember(Long id, MemberRequest param) { Member member = memberRepository.findById(id).orElseThrow(RuntimeException::new); member.update(param.toMember()); } + @Transactional public void updateMember(String email, MemberRequest param) { Member member = memberRepository.findByEmail(email).orElseThrow(RuntimeException::new); member.update(param.toMember()); diff --git a/src/main/java/nextstep/member/ui/MemberController.java b/src/main/java/nextstep/member/ui/MemberController.java index 742acdb17..512da1be1 100644 --- a/src/main/java/nextstep/member/ui/MemberController.java +++ b/src/main/java/nextstep/member/ui/MemberController.java @@ -1,6 +1,7 @@ package nextstep.member.ui; import nextstep.auth.authorization.AuthenticationPrincipal; +import nextstep.auth.secured.Secured; import nextstep.member.application.MemberService; import nextstep.member.application.dto.MemberRequest; import nextstep.member.application.dto.MemberResponse; @@ -31,6 +32,7 @@ public ResponseEntity findMember(@PathVariable Long id) { } @PutMapping("/members/{id}") + @Secured("ROLE_ADMIN") public ResponseEntity updateMember(@PathVariable Long id, @RequestBody MemberRequest param) { memberService.updateMember(id, param); return ResponseEntity.ok().build(); diff --git a/src/main/java/nextstep/subway/ui/ControllerExceptionHandler.java b/src/main/java/nextstep/subway/ui/ControllerExceptionHandler.java index 5055c254d..dc1bda1d3 100644 --- a/src/main/java/nextstep/subway/ui/ControllerExceptionHandler.java +++ b/src/main/java/nextstep/subway/ui/ControllerExceptionHandler.java @@ -2,6 +2,7 @@ import nextstep.auth.secured.RoleAuthenticationException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -20,6 +21,6 @@ public ResponseEntity handleIllegalArgsException(IllegalArgumentException @ExceptionHandler(RoleAuthenticationException.class) public ResponseEntity handleNoAuthenticationException(RoleAuthenticationException e) { - return ResponseEntity.badRequest().build(); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } diff --git a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java index c7653756e..8d3e51cb1 100644 --- a/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/AuthAcceptanceTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import java.util.HashMap; import java.util.Map; @@ -66,7 +65,7 @@ void fail_onlyAdminAuth() { ExtractableResponse response = 지하철역_생성_요청_토큰따로("서울역", accessToken); - assertThat(response.response().statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + assertThat(response.response().statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); } @DisplayName("권한 부족한 토큰 실패") @@ -76,7 +75,7 @@ void fail_notAdminAuth() { ExtractableResponse response = 지하철역_생성_요청_토큰따로("서울역", accessToken); - assertThat(response.response().statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + assertThat(response.response().statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); } private ExtractableResponse 폼_로그인_후_내_회원_정보_조회_요청(String email, String password) { diff --git a/src/test/java/nextstep/subway/acceptance/MemberAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/MemberAcceptanceTest.java index aedeedb1c..343713308 100644 --- a/src/test/java/nextstep/subway/acceptance/MemberAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/MemberAcceptanceTest.java @@ -10,7 +10,7 @@ import static org.assertj.core.api.Assertions.assertThat; class MemberAcceptanceTest extends AcceptanceTest { - public static final String EMAIL = "email@email.com"; + public static final String EMAIL = "email2@email.com"; public static final String PASSWORD = "password"; public static final int AGE = 20; @@ -67,10 +67,32 @@ void deleteMember() { @DisplayName("회원 정보를 관리한다.") @Test void manageMember() { + // given + ExtractableResponse createResponse = 회원_생성_요청(EMAIL, PASSWORD, AGE); + String newEmail = "new@email.com"; + + // when + ExtractableResponse response = 회원_정보_수정_요청(createResponse, newEmail, PASSWORD, AGE); + ExtractableResponse member = 회원_정보_조회_요청(createResponse); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(member.jsonPath().getString("email")).isEqualTo(newEmail); } @DisplayName("나의 정보를 관리한다.") @Test void manageMyInfo() { + // given + ExtractableResponse createResponse = 회원_생성_요청(EMAIL, PASSWORD, AGE); + String newEmail = "new@email.com"; + + // when + ExtractableResponse response = 베이직_인증으로_내_회원_정보_수정_요청(EMAIL, PASSWORD, newEmail, PASSWORD, AGE); + ExtractableResponse member = 회원_정보_조회_요청(createResponse); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(member.jsonPath().getString("email")).isEqualTo(newEmail); } } \ No newline at end of file diff --git a/src/test/java/nextstep/subway/acceptance/MemberSteps.java b/src/test/java/nextstep/subway/acceptance/MemberSteps.java index ff370ad00..27c980acd 100644 --- a/src/test/java/nextstep/subway/acceptance/MemberSteps.java +++ b/src/test/java/nextstep/subway/acceptance/MemberSteps.java @@ -3,6 +3,7 @@ import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import nextstep.subway.utils.SecurityUtil; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -65,8 +66,7 @@ public class MemberSteps { params.put("password", password); params.put("age", age + ""); - return RestAssured - .given().log().all() + return SecurityUtil.given() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(params) .when().put(uri) @@ -91,6 +91,23 @@ public class MemberSteps { .extract(); } + public static ExtractableResponse 베이직_인증으로_내_회원_정보_수정_요청(String username, String password, String newEmail, String newPassword, Integer newAge) { + Map params = new HashMap<>(); + params.put("email", newEmail); + params.put("password", newPassword); + params.put("age", newAge + ""); + + return RestAssured.given().log().all() + .auth().preemptive().basic(username, password) + .when() + .body(params) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .put("/members/me") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } + public static void 회원_정보_조회됨(ExtractableResponse response, String email, int age) { assertThat(response.jsonPath().getString("id")).isNotNull(); assertThat(response.jsonPath().getString("email")).isEqualTo(email); diff --git a/src/test/java/nextstep/subway/utils/SecurityUtil.java b/src/test/java/nextstep/subway/utils/SecurityUtil.java index c7677da42..4e67b6f2c 100644 --- a/src/test/java/nextstep/subway/utils/SecurityUtil.java +++ b/src/test/java/nextstep/subway/utils/SecurityUtil.java @@ -4,17 +4,15 @@ import io.restassured.specification.RequestSpecification; import nextstep.MemberData; import nextstep.auth.token.JwtTokenProvider; -import nextstep.member.domain.RoleType; import org.springframework.test.util.ReflectionTestUtils; -import java.util.List; +import static nextstep.subway.acceptance.MemberSteps.로그인_되어_있음; public class SecurityUtil { - static JwtTokenProvider jwtTokenProvider = getUnlimitedJwtTokenProvider(); - static String token = jwtTokenProvider.createToken(MemberData.admin.getEmail(), List.of(RoleType.ROLE_ADMIN.toString())); - public static RequestSpecification given() { + String token = 로그인_되어_있음(MemberData.admin.getEmail(), MemberData.admin.getPassword()); + return RestAssured.given().log().all() .auth().oauth2(token); } From 533ccdf59ee5178fcf04ff8e4c80c52e84af1d33 Mon Sep 17 00:00:00 2001 From: ashley Date: Fri, 19 Aug 2022 00:05:39 +0900 Subject: [PATCH 9/9] =?UTF-8?q?[modify]=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/authentication/Authenticator.java | 14 +++++++------- .../authentication/BasicAuthenticationFilter.java | 8 ++++---- .../BearerTokenAuthenticationFilter.java | 5 ----- .../authentication}/LoginMember.java | 7 +++++-- .../UsernamePasswordAuthenticationFilter.java | 4 ++-- .../AuthenticationPrincipalArgumentResolver.java | 2 +- .../auth/token/TokenAuthenticationInterceptor.java | 7 ++++--- .../member/application/LoginMemberService.java | 2 +- .../member/application/UserDetailsService.java | 4 ++-- src/main/java/nextstep/member/domain/User.java | 11 +++++++++++ .../java/nextstep/member/ui/MemberController.java | 2 +- .../unit/BasicAuthenticationFilterMockTest.java | 2 +- .../TokenAuthenticationInterceptorMockTest.java | 9 ++++++--- ...ernamePasswordAuthenticationFilterMockTest.java | 2 +- 14 files changed, 46 insertions(+), 33 deletions(-) rename src/main/java/nextstep/{member/domain => auth/authentication}/LoginMember.java (86%) create mode 100644 src/main/java/nextstep/member/domain/User.java diff --git a/src/main/java/nextstep/auth/authentication/Authenticator.java b/src/main/java/nextstep/auth/authentication/Authenticator.java index c47017556..7f3e4cfb6 100644 --- a/src/main/java/nextstep/auth/authentication/Authenticator.java +++ b/src/main/java/nextstep/auth/authentication/Authenticator.java @@ -1,7 +1,7 @@ package nextstep.auth.authentication; import nextstep.member.application.UserDetailsService; -import nextstep.member.domain.LoginMember; +import nextstep.member.domain.User; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; @@ -19,20 +19,20 @@ public Authenticator(UserDetailsService userDetailsService) { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { AuthenticationToken token = convert(request); - LoginMember member = userDetailsService.loadUserByUsername(token.getPrincipal()); + User user = userDetailsService.loadUserByUsername(token.getPrincipal()); - checkAuthentication(member, token.getCredentials()); - authenticate(member, response); + checkAuthentication(user, token.getCredentials()); + authenticate(user, response); return false; } abstract public AuthenticationToken convert(HttpServletRequest request) throws IOException; - abstract public void authenticate(LoginMember member, HttpServletResponse response) throws IOException; + abstract public void authenticate(User user, HttpServletResponse response) throws IOException; - private void checkAuthentication(LoginMember member, String password) { - if (!member.checkPassword(password)) { + private void checkAuthentication(User user, String password) { + if (!user.checkPassword(password)) { throw new AuthenticationException(); } } diff --git a/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java index e74b5a72b..6eaaf084b 100644 --- a/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java @@ -2,7 +2,7 @@ import nextstep.auth.context.Authentication; import nextstep.member.application.UserDetailsService; -import nextstep.member.domain.LoginMember; +import nextstep.member.domain.User; import org.apache.tomcat.util.codec.binary.Base64; import javax.servlet.http.HttpServletRequest; @@ -18,7 +18,7 @@ public BasicAuthenticationFilter(UserDetailsService userDetailsService) { @Override public Authentication convert(HttpServletRequest request) { AuthenticationToken token = getToken(request); - LoginMember loginMember = userDetailsService.loadUserByUsername(token.getPrincipal()); + User loginMember = userDetailsService.loadUserByUsername(token.getPrincipal()); checkAuthentication(token, loginMember); @@ -37,8 +37,8 @@ private AuthenticationToken getToken(HttpServletRequest request) { return new AuthenticationToken(principal, credentials); } - private void checkAuthentication(AuthenticationToken token, LoginMember loginMember) { - if (!loginMember.checkPassword(token.getCredentials())) { + private void checkAuthentication(AuthenticationToken token, User user) { + if (!user.checkPassword(token.getCredentials())) { throw new AuthenticationException(); } } diff --git a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java index 76b414940..4c585c547 100644 --- a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java @@ -1,14 +1,9 @@ package nextstep.auth.authentication; import nextstep.auth.context.Authentication; -import nextstep.auth.context.SecurityContextHolder; import nextstep.auth.token.JwtTokenProvider; -import nextstep.member.domain.LoginMember; -import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.List; public class BearerTokenAuthenticationFilter extends Authorizator { private final JwtTokenProvider jwtTokenProvider; diff --git a/src/main/java/nextstep/member/domain/LoginMember.java b/src/main/java/nextstep/auth/authentication/LoginMember.java similarity index 86% rename from src/main/java/nextstep/member/domain/LoginMember.java rename to src/main/java/nextstep/auth/authentication/LoginMember.java index 1f90fadc1..4b530bae8 100644 --- a/src/main/java/nextstep/member/domain/LoginMember.java +++ b/src/main/java/nextstep/auth/authentication/LoginMember.java @@ -1,9 +1,12 @@ -package nextstep.member.domain; +package nextstep.auth.authentication; +import nextstep.member.domain.Member; +import nextstep.member.domain.User; + import java.util.List; -public class LoginMember { +public class LoginMember implements User { private String email; private String password; private List authorities; diff --git a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java index a32687637..3119f0aad 100644 --- a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java @@ -3,7 +3,7 @@ import nextstep.auth.context.Authentication; import nextstep.auth.context.SecurityContextHolder; import nextstep.member.application.UserDetailsService; -import nextstep.member.domain.LoginMember; +import nextstep.member.domain.User; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -21,7 +21,7 @@ public AuthenticationToken convert(HttpServletRequest request) { } @Override - public void authenticate(LoginMember member, HttpServletResponse response) throws IOException { + public void authenticate(User member, HttpServletResponse response) throws IOException { Authentication authentication = new Authentication(member.getEmail(), member.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } diff --git a/src/main/java/nextstep/auth/authorization/AuthenticationPrincipalArgumentResolver.java b/src/main/java/nextstep/auth/authorization/AuthenticationPrincipalArgumentResolver.java index 10317cab0..ac0204244 100644 --- a/src/main/java/nextstep/auth/authorization/AuthenticationPrincipalArgumentResolver.java +++ b/src/main/java/nextstep/auth/authorization/AuthenticationPrincipalArgumentResolver.java @@ -1,8 +1,8 @@ package nextstep.auth.authorization; +import nextstep.auth.authentication.LoginMember; import nextstep.auth.context.Authentication; import nextstep.auth.context.SecurityContextHolder; -import nextstep.member.domain.LoginMember; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; diff --git a/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java b/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java index d636ae9b5..e872d96e7 100644 --- a/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java +++ b/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java @@ -4,7 +4,7 @@ import nextstep.auth.authentication.AuthenticationToken; import nextstep.auth.authentication.Authenticator; import nextstep.member.application.UserDetailsService; -import nextstep.member.domain.LoginMember; +import nextstep.member.domain.User; import org.springframework.http.MediaType; import javax.servlet.http.HttpServletRequest; @@ -14,6 +14,7 @@ public class TokenAuthenticationInterceptor extends Authenticator { private final JwtTokenProvider jwtTokenProvider; + private final ObjectMapper objectMapper = new ObjectMapper(); public TokenAuthenticationInterceptor(UserDetailsService userDetailsService, JwtTokenProvider jwtTokenProvider) { super(userDetailsService); @@ -32,11 +33,11 @@ public AuthenticationToken convert(HttpServletRequest request) throws IOExceptio } @Override - public void authenticate(LoginMember member, HttpServletResponse response) throws IOException { + public void authenticate(User member, HttpServletResponse response) throws IOException { String token = jwtTokenProvider.createToken(member.getEmail(), member.getAuthorities()); TokenResponse tokenResponse = new TokenResponse(token); - String responseToClient = new ObjectMapper().writeValueAsString(tokenResponse); + String responseToClient = objectMapper.writeValueAsString(tokenResponse); response.setStatus(HttpServletResponse.SC_OK); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getOutputStream().print(responseToClient); diff --git a/src/main/java/nextstep/member/application/LoginMemberService.java b/src/main/java/nextstep/member/application/LoginMemberService.java index 9fbe54f5f..6d9f23115 100644 --- a/src/main/java/nextstep/member/application/LoginMemberService.java +++ b/src/main/java/nextstep/member/application/LoginMemberService.java @@ -1,6 +1,6 @@ package nextstep.member.application; -import nextstep.member.domain.LoginMember; +import nextstep.auth.authentication.LoginMember; import nextstep.member.domain.Member; import nextstep.member.domain.MemberRepository; import org.springframework.stereotype.Service; diff --git a/src/main/java/nextstep/member/application/UserDetailsService.java b/src/main/java/nextstep/member/application/UserDetailsService.java index d8a7de586..e653605be 100644 --- a/src/main/java/nextstep/member/application/UserDetailsService.java +++ b/src/main/java/nextstep/member/application/UserDetailsService.java @@ -1,8 +1,8 @@ package nextstep.member.application; -import nextstep.member.domain.LoginMember; +import nextstep.member.domain.User; public interface UserDetailsService { - LoginMember loadUserByUsername(String email); + User loadUserByUsername(String email); } diff --git a/src/main/java/nextstep/member/domain/User.java b/src/main/java/nextstep/member/domain/User.java new file mode 100644 index 000000000..1febaebd4 --- /dev/null +++ b/src/main/java/nextstep/member/domain/User.java @@ -0,0 +1,11 @@ +package nextstep.member.domain; + + +import java.util.List; + +public interface User { + String getEmail(); + List getAuthorities(); + + boolean checkPassword(String password); +} diff --git a/src/main/java/nextstep/member/ui/MemberController.java b/src/main/java/nextstep/member/ui/MemberController.java index 512da1be1..c975e03ee 100644 --- a/src/main/java/nextstep/member/ui/MemberController.java +++ b/src/main/java/nextstep/member/ui/MemberController.java @@ -1,11 +1,11 @@ package nextstep.member.ui; +import nextstep.auth.authentication.LoginMember; import nextstep.auth.authorization.AuthenticationPrincipal; import nextstep.auth.secured.Secured; import nextstep.member.application.MemberService; import nextstep.member.application.dto.MemberRequest; import nextstep.member.application.dto.MemberResponse; -import nextstep.member.domain.LoginMember; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java index c71689588..d49a86a43 100644 --- a/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java +++ b/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java @@ -1,9 +1,9 @@ package nextstep.subway.unit; import nextstep.auth.authentication.BasicAuthenticationFilter; +import nextstep.auth.authentication.LoginMember; import nextstep.auth.context.Authentication; import nextstep.member.application.UserDetailsService; -import nextstep.member.domain.LoginMember; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java index c5cf2515a..420dbf3eb 100644 --- a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java +++ b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java @@ -2,17 +2,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import nextstep.auth.authentication.AuthenticationToken; +import nextstep.auth.authentication.LoginMember; import nextstep.auth.token.JwtTokenProvider; import nextstep.auth.token.TokenAuthenticationInterceptor; import nextstep.auth.token.TokenRequest; import nextstep.auth.token.TokenResponse; import nextstep.member.application.UserDetailsService; -import nextstep.member.domain.LoginMember; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -64,10 +65,12 @@ void authenticate() throws IOException { void preHandle() throws IOException { MockHttpServletResponse response = new MockHttpServletResponse(); given(userDetailsService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); - - boolean result = interceptor.preHandle(createMockRequest(), response, null); + given(jwtTokenProvider.createToken(any(), any())).willReturn(JWT_TOKEN); + boolean result = interceptor.preHandle(createMockRequest(), response, new Object()); assertThat(result).isFalse(); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + assertThat(response.getContentAsString()).isEqualTo(mapper.writeValueAsString(new TokenResponse(JWT_TOKEN))); } private MockHttpServletRequest createMockRequest() throws IOException { diff --git a/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java index 6e92a8234..c6713036d 100644 --- a/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java +++ b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java @@ -1,11 +1,11 @@ package nextstep.subway.unit; import nextstep.auth.authentication.AuthenticationToken; +import nextstep.auth.authentication.LoginMember; import nextstep.auth.authentication.UsernamePasswordAuthenticationFilter; import nextstep.auth.context.Authentication; import nextstep.auth.context.SecurityContextHolder; import nextstep.member.application.UserDetailsService; -import nextstep.member.domain.LoginMember; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks;