Skip to content

Commit

Permalink
유저 - 로그아웃 구현(Redis사용)
Browse files Browse the repository at this point in the history
유저 - 로그아웃 구현
  • Loading branch information
lej8924 authored Jul 19, 2024
2 parents 03d7458 + 00b1e7e commit 160169c
Show file tree
Hide file tree
Showing 26 changed files with 408 additions and 106 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ src/main/resources/*.yml
src/main/resources/data.sql

### crawling ###
crawling/chromedriver.exe
crawling/velog_url.csv
**/crawling/chromedriver.exe
**/crawling/velog_url.csv
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-security'

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-cache'

//embedded redis
// implementation group: 'it.ozimov' ,name: 'embedded-redis' ,version: '0.7.2'

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
Expand Down
Binary file removed crawling/chromedriver.exe
Binary file not shown.
1 change: 0 additions & 1 deletion crawling/velog_url.csv

This file was deleted.

1 change: 1 addition & 0 deletions src/main/java/com/ticle/server/ServerApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;

Expand Down
74 changes: 74 additions & 0 deletions src/main/java/com/ticle/server/global/config/CacheConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.ticle.server.global.config;

import com.ticle.server.user.redis.CacheNames;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@EnableCaching
@Configuration
public class CacheConfig {


@Bean
public RedisCacheManager cacheManager(
RedisConnectionFactory connectionFactory, ResourceLoader resourceLoader) {
RedisCacheConfiguration defaultConfig
= RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer())// value Serializer 변경
);

//disableCachingNullValues()를 호출하여 null 값 캐싱을 비활성화합니다. 이는 Redis에 null 값을 저장하지 않도록 설정합니다.
//serializeValuesWith()를 호출하여 값 직렬화를 구성합니다.
// 위의 코드에서는 GenericJackson2JsonRedisSerializer를 사용하여 값 직렬화를 수행합니다.
// 이는 값이 JSON 형식으로 직렬화되어 Redis에 저장되고 검색됩니다.
//구성이 완료된 RedisCacheConfiguration을 사용하여 RedisCacheManager를 생성합니다.
//이렇게 구성된 RedisCacheManager는 Spring Boot 애플리케이션에서 Redis 캐시를 관리하는 데 사용됩니다.
// 캐시 설정과 Redis 연결을 제어하고 캐시 작업을 수행할 때 RedisCacheManager를 주입받아 사용할 수 있다.

//redisCacheConfigMap은 Redis 캐시의 구성 정보를 담는 맵입니다.
Map<String, RedisCacheConfiguration> redisCacheConfigMap
= new HashMap<>();

redisCacheConfigMap.put(
CacheNames.USERBYEMAIL,
defaultConfig.entryTtl(Duration.ofHours(4)) //entryTtl()을 호출하여 캐시 항목의 만료 시간(TTL)을 설정합니다. 캐시 수명 4시간
);

// ALLUSERS에 대해서만 다른 Serializer 적용
redisCacheConfigMap.put(
CacheNames.ALLUSERS,
defaultConfig.entryTtl(Duration.ofHours(4))
.serializeValuesWith( //serializeValuesWith()를 호출하여 값을 직렬화하는 방식을 설정합니다.
RedisSerializationContext
.SerializationPair
.fromSerializer(new JdkSerializationRedisSerializer())
)
);
redisCacheConfigMap.put(
CacheNames.LOGINUSER,
defaultConfig.entryTtl(Duration.ofHours(2))
);


return RedisCacheManager.builder(connectionFactory)
.withInitialCacheConfigurations(redisCacheConfigMap)
.build();
}

}
14 changes: 7 additions & 7 deletions src/main/java/com/ticle/server/mypage/service/MyPageService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.ticle.server.mypage.dto.response.NoteResponse;
import com.ticle.server.mypage.dto.response.QnAResponse;
import com.ticle.server.mypage.dto.response.SavedTicleResponse;
import com.ticle.server.mypage.repository.NoteRepository;
import com.ticle.server.post.repository.MemoRepository;
import com.ticle.server.opinion.domain.Comment;
import com.ticle.server.opinion.repository.CommentRepository;
import com.ticle.server.opinion.repository.OpinionRepository;
Expand Down Expand Up @@ -34,7 +34,7 @@ public class MyPageService {
private final UserRepository userRepository;
private final ScrappedRepository scrappedRepository;
private final OpinionRepository opinionRepository;
private final NoteRepository noteRepository;
private final MemoRepository memoRepository;
private final PostRepository postRepository;
private final CommentRepository commentRepository;

Expand Down Expand Up @@ -109,34 +109,34 @@ public void deleteComment(Long userId, Long questionId) {
//////////////////////////////////////////////티클노트///////////////////////////////////////////////////////////////

public List<NoteResponse> getMyNotes(Long userId) {
List<Memo> memos = noteRepository.findByUserId(userId);
List<Memo> memos = memoRepository.findByUserId(userId);
return memos.stream()
.map(NoteResponse::toDto)
.collect(toList());
}

@Transactional
public void updateNote(CustomUserDetails customUserDetails, Long noteId, NoteUpdateRequest noteUpdateRequest){
Memo memo = noteRepository.findById(noteId)
Memo memo = memoRepository.findById(noteId)
.orElseThrow(() -> new RuntimeException("Memo not found"));

if (!memo.getUser().getId().equals(customUserDetails.getUserId())) {
throw new RuntimeException("You do not have permission to edit this memo");
}

memo.updateNote(noteUpdateRequest.getContent());
noteRepository.save(memo);
memoRepository.save(memo);
}

@Transactional
public void deleteNote(CustomUserDetails customUserDetails, Long noteId) {
Memo memo = noteRepository.findById(noteId)
Memo memo = memoRepository.findById(noteId)
.orElseThrow(() -> new RuntimeException("Memo not found"));

if (!memo.getUser().getId().equals(customUserDetails.getUserId())) {
throw new RuntimeException("You do not have permission to delete this memo");
}
noteRepository.delete(memo);
memoRepository.delete(memo);
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ticle.server.mypage.repository;
package com.ticle.server.post.repository;

import com.ticle.server.memo.domain.Memo;
import com.ticle.server.user.domain.User;
Expand All @@ -8,7 +8,7 @@
import java.util.List;

@Repository
public interface NoteRepository extends JpaRepository<Memo, Long> {
public interface MemoRepository extends JpaRepository<Memo, Long> {
List<Memo> findByUserId(Long userId);

Memo findByUserAndTargetTextAndContent(User user, String targetText, String content);
Expand Down
9 changes: 4 additions & 5 deletions src/main/java/com/ticle/server/post/service/PostService.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.ticle.server.post.service;

import com.ticle.server.memo.domain.Memo;
import com.ticle.server.post.repository.MemoRepository;
import com.ticle.server.mypage.repository.NoteRepository;
import com.ticle.server.post.domain.type.PostSort;
import com.ticle.server.post.dto.*;
import com.ticle.server.scrapped.dto.ScrappedDto;
import com.ticle.server.user.domain.type.Category;
import com.ticle.server.post.domain.Post;
import com.ticle.server.post.repository.PostRepository;
Expand All @@ -13,7 +13,6 @@
import com.ticle.server.user.domain.User;
import com.ticle.server.user.repository.UserRepository;
import com.ticle.server.user.service.CustomUserDetails;
import com.ticle.server.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;

Expand All @@ -39,7 +38,7 @@ public class PostService {
private final PostRepository postRepository;
private final ScrappedRepository scrappedRepository;
private final UserRepository userRepository;
private final NoteRepository noteRepository;
private final MemoRepository memoRepository;


// 카테고리에 맞는 글 찾기
Expand Down Expand Up @@ -158,7 +157,7 @@ public Object writeMemo(long id, CustomUserDetails customUserDetails, String tar
User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("해당 id의 user 찾을 수 없음 id: " + userId));

// 같은 내용의 targetText-content 세트가 있는지 확인
Memo existingMemo = noteRepository.findByUserAndTargetTextAndContent(user, targetText, content);
Memo existingMemo = memoRepository.findByUserAndTargetTextAndContent(user, targetText, content);

if (existingMemo != null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "이미 동일한 메모가 존재합니다.");
Expand All @@ -173,7 +172,7 @@ public Object writeMemo(long id, CustomUserDetails customUserDetails, String tar
.targetText(targetText)
.content(content)
.build();
return noteRepository.save(memo);
return memoRepository.save(memo);
}

boolean isValidResponse(GeminiResponse response) {
Expand Down
60 changes: 41 additions & 19 deletions src/main/java/com/ticle/server/user/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
package com.ticle.server.user.controller;

import com.ticle.server.global.dto.ResponseTemplate;
import com.ticle.server.user.dto.JoinRequest;
import com.ticle.server.user.dto.JwtToken;
import com.ticle.server.user.dto.LoginRequest;
import com.ticle.server.user.dto.UserDto;
import com.ticle.server.user.domain.User;
import com.ticle.server.user.dto.request.JoinRequest;
import com.ticle.server.user.dto.request.ReissueTokenRequest;
import com.ticle.server.user.dto.response.JwtTokenResponse;
import com.ticle.server.user.dto.request.LoginRequest;
import com.ticle.server.user.dto.response.UserResponse;
import com.ticle.server.user.jwt.JwtTokenProvider;
import com.ticle.server.user.jwt.SecurityUtil;
import com.ticle.server.user.repository.UserRepository;
import com.ticle.server.user.service.CustomUserDetails;
import com.ticle.server.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

import static org.springframework.http.HttpStatus.OK;

@Tag(name = "User", description = "유저 관련 API")
@Slf4j
@RestController
Expand All @@ -30,29 +42,39 @@ public class UserController {

@Operation(summary = "로그인", description = "로그인하기")
@PostMapping("/sign-in")
public ResponseEntity<ResponseTemplate<Object>> signIn(@RequestBody LoginRequest loginRequest) {
String email = loginRequest.email();
String password = loginRequest.password();
log.info("request username = {}, password = {}", email, password);
JwtToken jwtToken = userService.signIn(email, password);
log.info("jwtToken accessToken = {}, refreshToken = {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken());
public ResponseEntity<ResponseTemplate<Object>> signIn(@RequestBody LoginRequest loginRequest, HttpServletResponse response) {

JwtTokenResponse jwtTokenResponse = userService.signIn(loginRequest);
response.addHeader("Authorization",jwtTokenResponse.getAccessToken());
return ResponseEntity
.status(HttpStatus.OK)
.body(ResponseTemplate.from(jwtToken));
.status(OK)
.body(ResponseTemplate.from(jwtTokenResponse));
}

@Operation(summary = "회원가입", description = "회원가입하기")
@PostMapping("sign-up")
public ResponseEntity<ResponseTemplate<Object>> signUp(@RequestBody JoinRequest joinRequest) {
UserDto savedUserDto = userService.signUp(joinRequest);
UserResponse savedUserDto = userService.signUp(joinRequest);
return ResponseEntity
.status(HttpStatus.OK)
.status(OK)
.body(ResponseTemplate.from(savedUserDto));
}
@Operation(summary = "로그아웃", description = "로그아웃하기")
@DeleteMapping("/logout")
public ResponseEntity<ResponseTemplate<Object>> logout(@AuthenticationPrincipal CustomUserDetails userDetails, HttpServletRequest request){

@Operation(summary = "테스트", description = "테스트하기")
@PostMapping("/test")
public String test() {
return SecurityUtil.getCurrentUsername();
return ResponseEntity
.status(OK)
.body(ResponseTemplate.from(userService.logout(userDetails, request)));
}

@PostMapping("/reissue-token")
public ResponseEntity<ResponseTemplate<Object>> reissueToken(@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody ReissueTokenRequest tokenRequest){
//유저 객체 정보를 이용하여 토큰 발행
return ResponseEntity
.status(OK)
.body(ResponseTemplate.from(userService.reissueAtk(userDetails,tokenRequest.getRefreshToken())));
}

}
2 changes: 1 addition & 1 deletion src/main/java/com/ticle/server/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
@Entity
@Getter
@Builder
@NoArgsConstructor()
@NoArgsConstructor
@AllArgsConstructor
public class User {
// 애플리케이션의 핵심 비즈니스 로직을 담고 있는 개체
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.ticle.server.user.dto;
package com.ticle.server.user.dto.request;

import com.ticle.server.user.domain.User;
import com.ticle.server.user.domain.type.Category;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
Expand All @@ -16,7 +18,7 @@
public class JoinRequest {
// DTO는 주로 데이터 전송을 위한 객체
// 클라이언트와 서버 간 또는 애플리케이션 계층 간에 데이터를 전송하는 데 사용됨

@Email
private String email;
private String password;
// private String passwordCheck;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ticle.server.user.dto;
package com.ticle.server.user.dto.request;


public record LoginRequest(String email,String password) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ticle.server.user.dto.request;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
public class ReissueTokenRequest {
private final String refreshToken;

public ReissueTokenRequest(String refreshToken) {
this.refreshToken = refreshToken;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.ticle.server.user.dto;
package com.ticle.server.user.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@AllArgsConstructor
public class JwtToken {
@NoArgsConstructor
public class JwtTokenResponse {
private String grantType;
private String accessToken;
private String refreshToken;
Expand Down
Loading

0 comments on commit 160169c

Please sign in to comment.