diff --git a/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java b/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java index 67ae421..b1ff01f 100644 --- a/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java +++ b/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java @@ -9,7 +9,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; @@ -51,9 +50,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .and() .authorizeRequests() - .antMatchers(HttpMethod.POST, "/api/user/login").permitAll() - .antMatchers(HttpMethod.POST, "/api/user/refresh").permitAll() - .antMatchers(HttpMethod.POST, "/api/user").permitAll() + .antMatchers(HttpMethod.POST, "/api/auth/token").permitAll() + .antMatchers(HttpMethod.POST, "/api/auth/refresh").permitAll() + .antMatchers(HttpMethod.POST, "/api/users").permitAll() .antMatchers("/api/movies/**").permitAll() .antMatchers("/l7check").permitAll() .antMatchers("/actuator/**").permitAll() diff --git a/server/src/main/java/com/ticketing/server/global/validator/constraints/FieldsValueNotMatch.java b/server/src/main/java/com/ticketing/server/global/validator/constraints/FieldsValueNotMatch.java new file mode 100644 index 0000000..ecabaf4 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/global/validator/constraints/FieldsValueNotMatch.java @@ -0,0 +1,28 @@ +package com.ticketing.server.global.validator.constraints; + +import com.ticketing.server.global.validator.constraintvalidators.FieldsValueNotMatchValidator; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +@Constraint(validatedBy = FieldsValueNotMatchValidator.class) +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface FieldsValueNotMatch { + + String message(); + + String field(); + + String fieldMatch(); + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/server/src/main/java/com/ticketing/server/global/validator/constraintvalidators/FieldsValueNotMatchValidator.java b/server/src/main/java/com/ticketing/server/global/validator/constraintvalidators/FieldsValueNotMatchValidator.java new file mode 100644 index 0000000..bfea20e --- /dev/null +++ b/server/src/main/java/com/ticketing/server/global/validator/constraintvalidators/FieldsValueNotMatchValidator.java @@ -0,0 +1,29 @@ +package com.ticketing.server.global.validator.constraintvalidators; + +import com.ticketing.server.global.validator.constraints.FieldsValueNotMatch; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import org.springframework.beans.BeanWrapperImpl; + +public class FieldsValueNotMatchValidator implements ConstraintValidator { + + private String field; + private String fieldMatch; + + public void initialize(FieldsValueNotMatch constraintAnnotation) { + this.field = constraintAnnotation.field(); + this.fieldMatch = constraintAnnotation.fieldMatch(); + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + Object fieldValue = new BeanWrapperImpl(value).getPropertyValue(field); + Object fieldMatchValue = new BeanWrapperImpl(value).getPropertyValue(fieldMatch); + + if (fieldValue != null) { + return !fieldValue.equals(fieldMatchValue); + } else { + return fieldMatchValue != null; + } + } +} diff --git a/server/src/main/java/com/ticketing/server/user/application/AuthController.java b/server/src/main/java/com/ticketing/server/user/application/AuthController.java new file mode 100644 index 0000000..11d3243 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/application/AuthController.java @@ -0,0 +1,52 @@ +package com.ticketing.server.user.application; + +import com.ticketing.server.user.application.request.LoginRequest; +import com.ticketing.server.user.application.response.TokenDto; +import com.ticketing.server.user.service.interfaces.AuthenticationService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/auth") +@Slf4j +public class AuthController { + + private final AuthenticationService authenticationService; + + @PostMapping("/token") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + TokenDto tokenDto = authenticationService.generateTokenDto(loginRequest.toAuthentication()); + + return ResponseEntity.status(HttpStatus.OK) + .headers(getHttpHeaders()) + .body(tokenDto); + } + + @PostMapping("/refresh") + public ResponseEntity refreshToken(@RequestParam("refreshToken") String refreshToken) { + TokenDto tokenDto = authenticationService.reissueTokenDto(refreshToken); + + return ResponseEntity.status(HttpStatus.OK) + .headers(getHttpHeaders()) + .body(tokenDto); + } + + private HttpHeaders getHttpHeaders() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate"); + httpHeaders.set(HttpHeaders.PRAGMA, "no-store"); + httpHeaders.set(HttpHeaders.EXPIRES, "0"); + + return httpHeaders; + } + +} diff --git a/server/src/main/java/com/ticketing/server/user/application/UserController.java b/server/src/main/java/com/ticketing/server/user/application/UserController.java index 1bdd5ca..0164c1f 100644 --- a/server/src/main/java/com/ticketing/server/user/application/UserController.java +++ b/server/src/main/java/com/ticketing/server/user/application/UserController.java @@ -1,20 +1,15 @@ package com.ticketing.server.user.application; -import com.ticketing.server.global.security.jwt.JwtProperties; -import com.ticketing.server.user.application.request.LoginRequest; import com.ticketing.server.user.application.request.SignUpRequest; +import com.ticketing.server.user.application.request.UserChangePasswordRequest; import com.ticketing.server.user.application.request.UserDeleteRequest; -import com.ticketing.server.user.application.request.UserModifyPasswordRequest; import com.ticketing.server.user.application.response.SignUpResponse; -import com.ticketing.server.user.application.response.TokenDto; import com.ticketing.server.user.application.response.UserChangePasswordResponse; import com.ticketing.server.user.application.response.UserDeleteResponse; import com.ticketing.server.user.application.response.UserDetailResponse; import com.ticketing.server.user.domain.User; import com.ticketing.server.user.domain.UserGrade; import com.ticketing.server.user.service.UserServiceImpl; -import com.ticketing.server.user.service.interfaces.AuthenticationService; -import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,19 +25,16 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor -@RequestMapping("/api/user") +@RequestMapping("/api/users") @Slf4j public class UserController { private final UserServiceImpl userService; - private final AuthenticationService authenticationService; private final PasswordEncoder passwordEncoder; - private final JwtProperties jwtProperties; @PostMapping public ResponseEntity register(@RequestBody @Valid SignUpRequest request) { @@ -50,9 +42,9 @@ public class UserController { return ResponseEntity.status(HttpStatus.CREATED).body(SignUpResponse.from(user)); } - @GetMapping("/info") + @GetMapping("/details") @Secured("ROLE_GUEST") - public ResponseEntity myInfo(@AuthenticationPrincipal UserDetails userRequest) { + public ResponseEntity details(@AuthenticationPrincipal UserDetails userRequest) { User user = userService.findByEmail(userRequest.getUsername()); return ResponseEntity.status(HttpStatus.OK).body(UserDetailResponse.from(user)); } @@ -66,32 +58,11 @@ public class UserController { @PutMapping("/password") @Secured(UserGrade.ROLES.GUEST) - public ResponseEntity changePassword(@RequestBody @Valid UserModifyPasswordRequest request) { - if (request.oldEqualNew()) { - log.error("기존 패스워드와 동일한 패스워드로 변경할 수 없습니다."); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); - } - - User user = userService.changePassword(request.toChangePasswordDto(passwordEncoder)); + public ResponseEntity changePassword( + @AuthenticationPrincipal UserDetails userRequest, + @RequestBody @Valid UserChangePasswordRequest request) { + User user = userService.changePassword(request.toChangePasswordDto(userRequest.getUsername(), passwordEncoder)); return ResponseEntity.status(HttpStatus.OK).body(UserChangePasswordResponse.from(user)); } - @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletResponse response) { - TokenDto tokenDto = authenticationService.login(loginRequest.toAuthentication()); - - response.setHeader("Cache-Control", "no-store"); - response.setHeader("Pragma", "no-store"); - return ResponseEntity.status(HttpStatus.OK).body(tokenDto); - } - - @PostMapping("/refresh") - public ResponseEntity refreshToken(@RequestParam("refreshToken") String refreshToken, HttpServletResponse response) { - TokenDto tokenDto = authenticationService.reissueAccessToken(refreshToken); - - response.setHeader(jwtProperties.getAccessHeader(), tokenDto.getAccessToken()); - response.setHeader(jwtProperties.getRefreshHeader(), tokenDto.getRefreshToken()); - return ResponseEntity.status(HttpStatus.OK).body(tokenDto); - } - } diff --git a/server/src/main/java/com/ticketing/server/user/application/request/UserModifyPasswordRequest.java b/server/src/main/java/com/ticketing/server/user/application/request/UserChangePasswordRequest.java similarity index 63% rename from server/src/main/java/com/ticketing/server/user/application/request/UserModifyPasswordRequest.java rename to server/src/main/java/com/ticketing/server/user/application/request/UserChangePasswordRequest.java index fc4b8b2..a4f1eab 100644 --- a/server/src/main/java/com/ticketing/server/user/application/request/UserModifyPasswordRequest.java +++ b/server/src/main/java/com/ticketing/server/user/application/request/UserChangePasswordRequest.java @@ -1,7 +1,7 @@ package com.ticketing.server.user.application.request; +import com.ticketing.server.global.validator.constraints.FieldsValueNotMatch; import com.ticketing.server.user.service.dto.ChangePasswordDTO; -import javax.validation.constraints.Email; import javax.validation.constraints.NotEmpty; import lombok.AllArgsConstructor; import lombok.Getter; @@ -11,11 +11,12 @@ import org.springframework.security.crypto.password.PasswordEncoder; @Getter @NoArgsConstructor @AllArgsConstructor -public class UserModifyPasswordRequest { - - @NotEmpty(message = "{validation.not.empty.email}") - @Email(message = "{validation.email}") - private String email; +@FieldsValueNotMatch( + field = "oldPassword", + fieldMatch = "newPassword", + message = "{validation.password.not.change}" +) +public class UserChangePasswordRequest { @NotEmpty(message = "{validation.not.empty.oldpassword}") private String oldPassword; @@ -23,12 +24,8 @@ public class UserModifyPasswordRequest { @NotEmpty(message = "{validation.not.empty.newpassword}") private String newPassword; - public ChangePasswordDTO toChangePasswordDto(PasswordEncoder passwordEncoder) { + public ChangePasswordDTO toChangePasswordDto(String email, PasswordEncoder passwordEncoder) { return new ChangePasswordDTO(email, oldPassword, newPassword, passwordEncoder); } - public boolean oldEqualNew() { - return oldPassword.equals(newPassword); - } - } diff --git a/server/src/main/java/com/ticketing/server/user/service/AuthenticationServiceImpl.java b/server/src/main/java/com/ticketing/server/user/service/AuthenticationServiceImpl.java index 4aee68a..5f0df8a 100644 --- a/server/src/main/java/com/ticketing/server/user/service/AuthenticationServiceImpl.java +++ b/server/src/main/java/com/ticketing/server/user/service/AuthenticationServiceImpl.java @@ -29,7 +29,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { @Override @Transactional - public TokenDto login(UsernamePasswordAuthenticationToken authenticationToken) { + public TokenDto generateTokenDto(UsernamePasswordAuthenticationToken authenticationToken) { // 회원인증 Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); @@ -50,7 +50,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { @Override @Transactional - public TokenDto reissueAccessToken(String bearerRefreshToken) { + public TokenDto reissueTokenDto(String bearerRefreshToken) { String refreshToken = resolveToken(bearerRefreshToken); // 토큰 검증 diff --git a/server/src/main/java/com/ticketing/server/global/security/service/CustomUserDetailsService.java b/server/src/main/java/com/ticketing/server/user/service/CustomUserDetailsService.java similarity index 95% rename from server/src/main/java/com/ticketing/server/global/security/service/CustomUserDetailsService.java rename to server/src/main/java/com/ticketing/server/user/service/CustomUserDetailsService.java index 056a33e..a54b0de 100644 --- a/server/src/main/java/com/ticketing/server/global/security/service/CustomUserDetailsService.java +++ b/server/src/main/java/com/ticketing/server/user/service/CustomUserDetailsService.java @@ -1,4 +1,4 @@ -package com.ticketing.server.global.security.service; +package com.ticketing.server.user.service; import com.ticketing.server.user.domain.User; import com.ticketing.server.user.domain.repository.UserRepository; diff --git a/server/src/main/java/com/ticketing/server/user/service/interfaces/AuthenticationService.java b/server/src/main/java/com/ticketing/server/user/service/interfaces/AuthenticationService.java index 2e95919..7954395 100644 --- a/server/src/main/java/com/ticketing/server/user/service/interfaces/AuthenticationService.java +++ b/server/src/main/java/com/ticketing/server/user/service/interfaces/AuthenticationService.java @@ -5,8 +5,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio public interface AuthenticationService { - TokenDto login(UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken); + TokenDto generateTokenDto(UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken); - TokenDto reissueAccessToken(String bearerRefreshToken); + TokenDto reissueTokenDto(String bearerRefreshToken); } diff --git a/server/src/main/resources/i18n/messages.properties b/server/src/main/resources/i18n/messages.properties index 2d2f70c..d356a7f 100644 --- a/server/src/main/resources/i18n/messages.properties +++ b/server/src/main/resources/i18n/messages.properties @@ -7,3 +7,4 @@ validation.not.empty.grade="\uC0AC\uC6A9\uC790 \uB4F1\uAE09\uC740 \uD544\uC218 \ validation.not.empty.phone="\uD734\uB300\uBC88\uD638\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.email="\uC774\uBA54\uC77C\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." validation.phone="\uD734\uB300\uBC88\uD638\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." +validation.password.not.change="\uB3D9\uC77C\uD55C \uD328\uC2A4\uC6CC\uB4DC\uB85C \uBCC0\uACBD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." diff --git a/server/src/main/resources/i18n/messages_en.properties b/server/src/main/resources/i18n/messages_en.properties index 65240e4..6538fff 100644 --- a/server/src/main/resources/i18n/messages_en.properties +++ b/server/src/main/resources/i18n/messages_en.properties @@ -7,3 +7,4 @@ validation.not.empty.grade="user grade is required." validation.not.empty.phone="phone is required." validation.email="email is not valid." validation.phone="phone is not valid." +validation.password.not.change="password not change." diff --git a/server/src/main/resources/i18n/messages_ko.properties b/server/src/main/resources/i18n/messages_ko.properties index 2d2f70c..d356a7f 100644 --- a/server/src/main/resources/i18n/messages_ko.properties +++ b/server/src/main/resources/i18n/messages_ko.properties @@ -7,3 +7,4 @@ validation.not.empty.grade="\uC0AC\uC6A9\uC790 \uB4F1\uAE09\uC740 \uD544\uC218 \ validation.not.empty.phone="\uD734\uB300\uBC88\uD638\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.email="\uC774\uBA54\uC77C\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." validation.phone="\uD734\uB300\uBC88\uD638\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." +validation.password.not.change="\uB3D9\uC77C\uD55C \uD328\uC2A4\uC6CC\uB4DC\uB85C \uBCC0\uACBD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." diff --git a/server/src/test/java/com/ticketing/server/user/application/UserControllerTest.java b/server/src/test/java/com/ticketing/server/user/application/AuthControllerTest.java similarity index 91% rename from server/src/test/java/com/ticketing/server/user/application/UserControllerTest.java rename to server/src/test/java/com/ticketing/server/user/application/AuthControllerTest.java index 34f462b..5b9d66f 100644 --- a/server/src/test/java/com/ticketing/server/user/application/UserControllerTest.java +++ b/server/src/test/java/com/ticketing/server/user/application/AuthControllerTest.java @@ -27,7 +27,10 @@ import org.springframework.web.context.WebApplicationContext; @SpringBootTest @Transactional -class UserControllerTest { +class AuthControllerTest { + + private static final String LOGIN_URL = "/api/auth/token"; + private static final String REGISTER_URL = "/api/users"; @Autowired WebApplicationContext context; @@ -53,7 +56,7 @@ class UserControllerTest { LoginRequest request = new LoginRequest("ticketing@gmail.com", "qwe123"); // when - ResultActions actions = mvc.perform(post("/api/user/login") + ResultActions actions = mvc.perform(post(LOGIN_URL) .content(asJsonString(request)) .contentType(MediaType.APPLICATION_JSON)); @@ -69,7 +72,7 @@ class UserControllerTest { LoginRequest request = new LoginRequest("ticketing@gmail.com", "qwe1234"); // when - ResultActions actions = mvc.perform(post("/api/user/login") + ResultActions actions = mvc.perform(post(LOGIN_URL) .content(asJsonString(request)) .contentType(MediaType.APPLICATION_JSON)); @@ -91,7 +94,7 @@ class UserControllerTest { SignUpRequest signUpRequest = new SignUpRequest("ticketing", "ticketing@gmail.com", "qwe123", "010-2240-7920"); - mvc.perform(post("/api/user") + mvc.perform(post(REGISTER_URL) .content(asJsonString(signUpRequest)) .contentType(MediaType.APPLICATION_JSON)); } diff --git a/server/src/test/java/com/ticketing/server/user/service/AuthenticationServiceImplTest.java b/server/src/test/java/com/ticketing/server/user/service/AuthenticationServiceImplTest.java index 90420cb..8623013 100644 --- a/server/src/test/java/com/ticketing/server/user/service/AuthenticationServiceImplTest.java +++ b/server/src/test/java/com/ticketing/server/user/service/AuthenticationServiceImplTest.java @@ -70,7 +70,7 @@ class AuthenticationServiceImplTest { when(jwtProperties.hasTokenStartsWith(refreshToken)).thenReturn(true); // when - TokenDto tokenDto = authenticationService.reissueAccessToken(refreshToken); + TokenDto tokenDto = authenticationService.reissueTokenDto(refreshToken); // then assertAll(