From 898ed262e9ad5409fc9a6aa6789b7bbed0bfecf5 Mon Sep 17 00:00:00 2001 From: U-Geon Date: Thu, 25 Jul 2024 22:45:07 +0900 Subject: [PATCH] =?UTF-8?q?fix=20:=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8?= =?UTF-8?q?=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wssrs/domain/File/entity/File.java | 23 +++++++++++++++ .../member/controller/MemberController.java | 20 ++++++------- .../member/service/LoadUserService.java | 4 +-- .../domain/member/service/MemberService.java | 12 ++++---- .../wssrs/domain/recruit/entity/Recruit.java | 28 +++++++++++++++++++ .../wssrs/global/config/SecurityConfig.java | 4 +-- .../global/jwt/JwtAuthenticationFilter.java | 13 +++------ .../wssrs/global/jwt/JwtProvider.java | 4 +-- .../bootcamp/wssrs/global/jwt/TokenDTO.java | 2 -- 9 files changed, 77 insertions(+), 33 deletions(-) create mode 100644 src/main/java/bootcamp/wssrs/domain/File/entity/File.java create mode 100644 src/main/java/bootcamp/wssrs/domain/recruit/entity/Recruit.java diff --git a/src/main/java/bootcamp/wssrs/domain/File/entity/File.java b/src/main/java/bootcamp/wssrs/domain/File/entity/File.java new file mode 100644 index 0000000..424801c --- /dev/null +++ b/src/main/java/bootcamp/wssrs/domain/File/entity/File.java @@ -0,0 +1,23 @@ +package bootcamp.wssrs.domain.File.entity; + +import bootcamp.wssrs.domain.Notice.entity.Notice; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter @Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class File { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String url; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "notice_id") + private Notice notice; +} diff --git a/src/main/java/bootcamp/wssrs/domain/member/controller/MemberController.java b/src/main/java/bootcamp/wssrs/domain/member/controller/MemberController.java index 46b6e3d..b8c4023 100644 --- a/src/main/java/bootcamp/wssrs/domain/member/controller/MemberController.java +++ b/src/main/java/bootcamp/wssrs/domain/member/controller/MemberController.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -24,16 +25,16 @@ public class MemberController { // 로그인 @PostMapping("/login") - @Operation(summary = "로그인", description = "학번, 비밀번호 입력") - public ResponseEntity login(@Parameter(required = true, description = "학번, 비밀번호") + @Operation(summary = "로그인", description = "이메일, 비밀번호 입력") + public ResponseEntity login(@Parameter(required = true, description = "이메일, 비밀번호") @RequestBody @Valid LoginRequestDTO requestDTO) { return ResponseEntity.ok(memberService.login(requestDTO)); } // 회원가입 @PostMapping("/sign-up") - @Operation(summary = "회원 가입", description = "학번, 이름, 비밀번호 입력") - public ResponseEntity signup(@Parameter(required = true, description = "학번, 이름, 비밀번호") + @Operation(summary = "회원 가입", description = "이메일, 학번, 이름, 비밀번호 입력해서 회원가입") + public ResponseEntity signup(@Parameter(required = true, description = "이메일, 학번, 이름, 비밀번호") @RequestBody @Valid SignupRequestDTO requestDTO) { return ResponseEntity.ok(memberService.signUp(requestDTO)); } @@ -48,7 +49,7 @@ public ResponseEntity findMember(@RequestBody @Valid FindMemberRequest // 비밀번호 재설정 @PostMapping("/pw/update") - @Operation(summary = "비밀번호 재설정") + @Operation(summary = "비밀번호 재설정", description = "header : access token 필요") public ResponseEntity updatePassword( @Parameter(required = true, description = "새 비밀번호 입력") @RequestBody @Valid PasswordUpdateRequestDTO requestDTO, @@ -58,7 +59,7 @@ public ResponseEntity updatePassword( // access token 재발급 @PostMapping("/refresh") - @Operation(summary = "Access Token 재발급", description = "Refresh Token을 Body에 담아 전송") + @Operation(summary = "Access Token 재발급", description = "header: access token 필요 + body: refresh token") public ResponseEntity reissueAccessToken( @Parameter(required = true, description = "Refresh Token") @RequestBody @Valid ReissueAccessTokenRequestDTO requestDTO, @@ -68,9 +69,8 @@ public ResponseEntity reissueAccessToken( // logout @GetMapping("/logout") - @Operation(summary = "로그아웃") - public void logout(@AuthenticationPrincipal Member member, - @RequestBody @Valid TokenDTO tokenDTO) { - memberService.logout(member.getStudentId(), tokenDTO); + @Operation(summary = "로그아웃", description = "액세스 토큰 + 리프레시 토큰 제거") + public void logout(@RequestBody @Valid TokenDTO tokenDTO) { + memberService.logout(tokenDTO); } } diff --git a/src/main/java/bootcamp/wssrs/domain/member/service/LoadUserService.java b/src/main/java/bootcamp/wssrs/domain/member/service/LoadUserService.java index 2aedb2d..445afa1 100644 --- a/src/main/java/bootcamp/wssrs/domain/member/service/LoadUserService.java +++ b/src/main/java/bootcamp/wssrs/domain/member/service/LoadUserService.java @@ -15,8 +15,8 @@ public class LoadUserService implements UserDetailsService { // jwt filter @Override - public Member loadUserByUsername(String studentId) throws UsernameNotFoundException { - return memberRepository.findByEmail(studentId) + public Member loadUserByUsername(String email) throws UsernameNotFoundException { + return memberRepository.findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("가입되어 있지 않은 유저입니다.")); } } diff --git a/src/main/java/bootcamp/wssrs/domain/member/service/MemberService.java b/src/main/java/bootcamp/wssrs/domain/member/service/MemberService.java index 56e1abe..9bd6b42 100644 --- a/src/main/java/bootcamp/wssrs/domain/member/service/MemberService.java +++ b/src/main/java/bootcamp/wssrs/domain/member/service/MemberService.java @@ -18,6 +18,7 @@ import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -28,6 +29,7 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) +@Slf4j public class MemberService { private final MemberRepository memberRepository; @@ -48,7 +50,7 @@ public TokenDTO login(LoginRequestDTO requestDTO) { String refreshToken = jwtProvider.createRefreshToken(); // refresh token 저장. - if(redisService.getRefreshToken(email).equals(refreshToken)) { + if(redisService.getRefreshToken(email) != null) { redisService.deleteRefreshToken(email); } redisService.saveRefreshToken(email, refreshToken); @@ -84,7 +86,6 @@ public TokenDTO findMember(FindMemberRequestDTO requestDTO) { @Transactional public PasswordUpdateResponseDTO updatePassword(String email, PasswordUpdateRequestDTO requestDTO) { memberRepository.findByEmail(email) - .filter(member -> passwordEncoder.matches(requestDTO.getNewPassword(), member.getPassword())) // 암호화된 비밀번호와 일치 여부 확인 .map(member -> { member.setPassword(requestDTO.getNewPassword(), passwordEncoder); // 새 비밀번호를 암호화하도록 수정 return new PasswordUpdateResponseDTO(requestDTO.getNewPassword()); @@ -109,14 +110,13 @@ public ReissueAccessTokenResponseDTO reissueAccessToken(String refreshToken, Mem // 로그아웃 @Transactional - public void logout(String studentId, TokenDTO tokenDTO) { + public void logout(TokenDTO tokenDTO) { String accessToken = tokenDTO.getAccessToken(); + String studentId = jwtProvider.getEmailFromAccessToken(accessToken); // redis에서 refresh Token 제거 - if (redisService.getRefreshToken(studentId) != null) { - redisService.deleteRefreshToken(studentId); - } + redisService.deleteRefreshToken(studentId); // 해당 엑세스 토큰의 남은 유효시간을 얻어서 Access Token blacklist에 등록하여 만료시키기 Long tokenExpiration = jwtProvider.getTokenExpiration(accessToken); diff --git a/src/main/java/bootcamp/wssrs/domain/recruit/entity/Recruit.java b/src/main/java/bootcamp/wssrs/domain/recruit/entity/Recruit.java new file mode 100644 index 0000000..6e7deee --- /dev/null +++ b/src/main/java/bootcamp/wssrs/domain/recruit/entity/Recruit.java @@ -0,0 +1,28 @@ +package bootcamp.wssrs.domain.recruit.entity; + + +import bootcamp.wssrs.domain.Notice.entity.Notice; +import bootcamp.wssrs.domain.member.entity.Member; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter @Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Recruit { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "notice_id") + private Notice notice; +} diff --git a/src/main/java/bootcamp/wssrs/global/config/SecurityConfig.java b/src/main/java/bootcamp/wssrs/global/config/SecurityConfig.java index aa080ed..2ff4980 100644 --- a/src/main/java/bootcamp/wssrs/global/config/SecurityConfig.java +++ b/src/main/java/bootcamp/wssrs/global/config/SecurityConfig.java @@ -40,11 +40,11 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti .csrf(AbstractHttpConfigurer::disable) // csrf 비활성화 -> cookie를 사용하지 않으면 꺼도 된다. (cookie를 사용할 경우 httpOnly(XSS 방어), sameSite(CSRF 방어)로 방어해야 한다.) .formLogin(AbstractHttpConfigurer::disable) // security 기본 로그인 비활성화 .httpBasic(AbstractHttpConfigurer::disable) // REST API이므로 basic auth 사용 x - .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) // cors 설정 // 특정 URL에 대한 권한 설정. .authorizeHttpRequests(authz -> authz .requestMatchers("/swagger", "/swagger-ui/index.html", "/swagger-ui/**", "/api-docs", "/api-docs/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/", "/api/auth/login", "/api/auth/sign-up").permitAll() // 특정 url에 대한 인가 요청 허용 + .requestMatchers("/", "/api/auth/*", "/api/auth/pw/*").permitAll() // 특정 url에 대한 인가 요청 허용 .requestMatchers("/api/user/*").hasRole("USER") // USER 권한일 때 요청 가능. .requestMatchers("/api/admin/*").hasRole("ADMIN") // ADMIN 권한일 때 요청. .anyRequest().authenticated() // 그 외 요청은 인증 필요. diff --git a/src/main/java/bootcamp/wssrs/global/jwt/JwtAuthenticationFilter.java b/src/main/java/bootcamp/wssrs/global/jwt/JwtAuthenticationFilter.java index 3354521..d62ccb2 100644 --- a/src/main/java/bootcamp/wssrs/global/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/bootcamp/wssrs/global/jwt/JwtAuthenticationFilter.java @@ -36,15 +36,10 @@ protected void doFilterInternal(HttpServletRequest request, log.info("Authentication filter 작동."); log.info("accessToken: {}", accessToken); - if(accessToken == null) { - filterChain.doFilter(request, response); - return ; - } - try { if(jwtProvider.validate(accessToken)) { - String studentId = jwtProvider.getStudentIdFromAccessToken(accessToken); - Authentication authentication = getAuthentication(studentId); + String email = jwtProvider.getEmailFromAccessToken(accessToken); + Authentication authentication = getAuthentication(email); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (TokenExpiredException e) { @@ -69,8 +64,8 @@ private String resolveToken(HttpServletRequest request) { } // 인증 정보 가져오기 - private Authentication getAuthentication(String studentId) { - Member userDetails = loadUserService.loadUserByUsername(studentId); + private Authentication getAuthentication(String email) { + Member userDetails = loadUserService.loadUserByUsername(email); return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); } diff --git a/src/main/java/bootcamp/wssrs/global/jwt/JwtProvider.java b/src/main/java/bootcamp/wssrs/global/jwt/JwtProvider.java index a996a19..f28197e 100644 --- a/src/main/java/bootcamp/wssrs/global/jwt/JwtProvider.java +++ b/src/main/java/bootcamp/wssrs/global/jwt/JwtProvider.java @@ -29,8 +29,8 @@ public JwtProvider(@Value("${jwt.secret-key}") String secretKey, RedisService re this.redisService = redisService; } - public String getStudentIdFromAccessToken(String token) { - return JWT.decode(token).getClaim("studentId").asString(); + public String getEmailFromAccessToken(String token) { + return JWT.decode(token).getClaim("email").asString(); } public boolean validate(String token) { diff --git a/src/main/java/bootcamp/wssrs/global/jwt/TokenDTO.java b/src/main/java/bootcamp/wssrs/global/jwt/TokenDTO.java index e0d4f89..f9f2ebc 100644 --- a/src/main/java/bootcamp/wssrs/global/jwt/TokenDTO.java +++ b/src/main/java/bootcamp/wssrs/global/jwt/TokenDTO.java @@ -2,11 +2,9 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter -@NoArgsConstructor @AllArgsConstructor public class TokenDTO { private String accessToken;