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/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/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/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..7f3e4cfb6 --- /dev/null +++ b/src/main/java/nextstep/auth/authentication/Authenticator.java @@ -0,0 +1,39 @@ +package nextstep.auth.authentication; + +import nextstep.member.application.UserDetailsService; +import nextstep.member.domain.User; +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 UserDetailsService userDetailsService; + + public Authenticator(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + AuthenticationToken token = convert(request); + User user = userDetailsService.loadUserByUsername(token.getPrincipal()); + + checkAuthentication(user, token.getCredentials()); + authenticate(user, response); + + return false; + } + + abstract public AuthenticationToken convert(HttpServletRequest request) throws IOException; + + abstract public void authenticate(User user, HttpServletResponse response) throws IOException; + + private void checkAuthentication(User user, String password) { + if (!user.checkPassword(password)) { + throw new AuthenticationException(); + } + } +} 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..6eaaf084b 100644 --- a/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BasicAuthenticationFilter.java @@ -1,50 +1,45 @@ package nextstep.auth.authentication; import nextstep.auth.context.Authentication; -import nextstep.auth.context.SecurityContextHolder; -import nextstep.member.application.LoginMemberService; -import nextstep.member.domain.LoginMember; +import nextstep.member.application.UserDetailsService; +import nextstep.member.domain.User; 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 implements HandlerInterceptor { - private LoginMemberService loginMemberService; +public class BasicAuthenticationFilter extends Authorizator { - public BasicAuthenticationFilter(LoginMemberService loginMemberService) { - this.loginMemberService = loginMemberService; + private final UserDetailsService userDetailsService; + + public BasicAuthenticationFilter(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; } @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); + User loginMember = userDetailsService.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, 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 65c6bcb82..4c585c547 100644 --- a/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/BearerTokenAuthenticationFilter.java @@ -1,15 +1,11 @@ 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 { +public class BearerTokenAuthenticationFilter extends Authorizator { private final JwtTokenProvider jwtTokenProvider; public BearerTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { @@ -17,21 +13,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/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 61f46b08b..3119f0aad 100644 --- a/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java +++ b/src/main/java/nextstep/auth/authentication/UsernamePasswordAuthenticationFilter.java @@ -1,45 +1,28 @@ 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 nextstep.member.application.UserDetailsService; +import nextstep.member.domain.User; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.List; +import java.io.IOException; -public class UsernamePasswordAuthenticationFilter implements HandlerInterceptor { - private final LoginMemberService loginMemberService; +public class UsernamePasswordAuthenticationFilter extends Authenticator { - public UsernamePasswordAuthenticationFilter(LoginMemberService loginMemberService) { - this.loginMemberService = loginMemberService; + public UsernamePasswordAuthenticationFilter(UserDetailsService userDetailsService) { + super(userDetailsService); } @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(User 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/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/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/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/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java b/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java index 268443936..e872d96e7 100644 --- a/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java +++ b/src/main/java/nextstep/auth/token/TokenAuthenticationInterceptor.java @@ -1,51 +1,45 @@ package nextstep.auth.token; import com.fasterxml.jackson.databind.ObjectMapper; -import nextstep.auth.authentication.AuthenticationException; -import nextstep.member.application.LoginMemberService; -import nextstep.member.domain.LoginMember; +import nextstep.auth.authentication.AuthenticationToken; +import nextstep.auth.authentication.Authenticator; +import nextstep.member.application.UserDetailsService; +import nextstep.member.domain.User; import org.springframework.http.MediaType; -import org.springframework.web.servlet.HandlerInterceptor; 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; + private final ObjectMapper objectMapper = new ObjectMapper(); - public TokenAuthenticationInterceptor(LoginMemberService loginMemberService, JwtTokenProvider jwtTokenProvider) { - this.loginMemberService = loginMemberService; + public TokenAuthenticationInterceptor(UserDetailsService userDetailsService, JwtTokenProvider jwtTokenProvider) { + super(userDetailsService); 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(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); - - 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/main/java/nextstep/member/application/LoginMemberService.java b/src/main/java/nextstep/member/application/LoginMemberService.java index 8a6ecd6d9..6d9f23115 100644 --- a/src/main/java/nextstep/member/application/LoginMemberService.java +++ b/src/main/java/nextstep/member/application/LoginMemberService.java @@ -1,12 +1,12 @@ 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; @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/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/application/UserDetailsService.java b/src/main/java/nextstep/member/application/UserDetailsService.java new file mode 100644 index 000000000..e653605be --- /dev/null +++ b/src/main/java/nextstep/member/application/UserDetailsService.java @@ -0,0 +1,8 @@ +package nextstep.member.application; + +import nextstep.member.domain.User; + +public interface UserDetailsService { + + 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 742acdb17..c975e03ee 100644 --- a/src/main/java/nextstep/member/ui/MemberController.java +++ b/src/main/java/nextstep/member/ui/MemberController.java @@ -1,10 +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.*; @@ -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/unit/BasicAuthenticationFilterMockTest.java b/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java new file mode 100644 index 000000000..d49a86a43 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/BasicAuthenticationFilterMockTest.java @@ -0,0 +1,55 @@ +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 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 + UserDetailsService userDetailsService; + + @InjectMocks + BasicAuthenticationFilter filter; + + @Test + void preHandle() throws IOException { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", BASIC_TOKEN); + MockHttpServletResponse response = new MockHttpServletResponse(); + + given(userDetailsService.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(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 new file mode 100644 index 000000000..843e0d692 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/BearerTokenAuthenticationFilterMockTest.java @@ -0,0 +1,46 @@ +package nextstep.subway.unit; + +import nextstep.auth.authentication.BearerTokenAuthenticationFilter; +import nextstep.auth.context.Authentication; +import nextstep.auth.context.SecurityContextHolder; +import nextstep.auth.token.JwtTokenProvider; +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.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/TokenAuthenticationInterceptorMockTest.java b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java new file mode 100644 index 000000000..420dbf3eb --- /dev/null +++ b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorMockTest.java @@ -0,0 +1,83 @@ +package nextstep.subway.unit; + +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 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; + +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 TokenAuthenticationInterceptorMockTest { + 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 + UserDetailsService userDetailsService; + + 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() 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(userDetailsService.loadUserByUsername(any())).willReturn(new LoginMember(EMAIL, PASSWORD, List.of())); + 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 { + MockHttpServletRequest request = new MockHttpServletRequest(); + TokenRequest tokenRequest = new TokenRequest(EMAIL, PASSWORD); + request.setContent(mapper.writeValueAsString(tokenRequest).getBytes()); + + return request; + } +} diff --git a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java b/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java deleted file mode 100644 index 6ef358e0a..000000000 --- a/src/test/java/nextstep/subway/unit/TokenAuthenticationInterceptorTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package nextstep.subway.unit; - -import com.fasterxml.jackson.databind.ObjectMapper; -import nextstep.auth.token.TokenRequest; -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import java.io.IOException; - -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"; - - @Test - void convert() throws IOException { - } - - @Test - void authenticate() { - } - - @Test - void preHandle() throws IOException { - } - - private MockHttpServletRequest createMockRequest() throws IOException { - MockHttpServletRequest request = new MockHttpServletRequest(); - TokenRequest tokenRequest = new TokenRequest(EMAIL, PASSWORD); - request.setContent(new ObjectMapper().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..c6713036d 100644 --- a/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java +++ b/src/test/java/nextstep/subway/unit/UsernamePasswordAuthenticationFilterMockTest.java @@ -1,8 +1,11 @@ package nextstep.subway.unit; +import nextstep.auth.authentication.AuthenticationToken; +import nextstep.auth.authentication.LoginMember; import nextstep.auth.authentication.UsernamePasswordAuthenticationFilter; -import nextstep.member.application.LoginMemberService; -import nextstep.member.domain.LoginMember; +import nextstep.auth.context.Authentication; +import nextstep.auth.context.SecurityContextHolder; +import nextstep.member.application.UserDetailsService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -11,6 +14,7 @@ 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; @@ -21,13 +25,13 @@ public class UsernamePasswordAuthenticationFilterMockTest { @Mock - LoginMemberService loginMemberService; + UserDetailsService userDetailsService; @InjectMocks UsernamePasswordAuthenticationFilter filter; @Test - void preHandle() { + void preHandle() throws IOException { String email = "admin@email.com"; String password = "password"; MockHttpServletRequest request = new MockHttpServletRequest(); @@ -35,8 +39,30 @@ void preHandle() { 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)).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())); } } 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); }