diff --git a/.gitignore b/.gitignore index 2d435e8..5312ce4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,6 @@ out/ .vscode/ application.yml -application.properties \ No newline at end of file +application.properties +logback.xml +log/ \ No newline at end of file diff --git a/build.gradle b/build.gradle index a7a72bd..b0d0df5 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,13 @@ dependencies { implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2' //firebase implementation group: 'com.google.firebase', name: 'firebase-admin', version: '8.1.0' + //cache + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-cache' + //ELK + implementation group: 'net.logstash.logback', name: 'logstash-logback-encoder', version: '6.3' + //junit + } tasks.named('test') { diff --git a/src/main/java/com/example/UnderTheSea_Server/UnderTheSeaServerApplication.java b/src/main/java/com/example/UnderTheSea_Server/UnderTheSeaServerApplication.java index 3f126d6..a5d85c7 100644 --- a/src/main/java/com/example/UnderTheSea_Server/UnderTheSeaServerApplication.java +++ b/src/main/java/com/example/UnderTheSea_Server/UnderTheSeaServerApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableCaching @EnableJpaAuditing @SpringBootApplication public class UnderTheSeaServerApplication { diff --git a/src/main/java/com/example/UnderTheSea_Server/controller/FirebaseController.java b/src/main/java/com/example/UnderTheSea_Server/controller/FirebaseController.java index 39eaf4d..366100b 100644 --- a/src/main/java/com/example/UnderTheSea_Server/controller/FirebaseController.java +++ b/src/main/java/com/example/UnderTheSea_Server/controller/FirebaseController.java @@ -1,22 +1,13 @@ //package com.example.UnderTheSea_Server.controller; // -//import com.example.UnderTheSea_Server.config.BaseException; -//import com.example.UnderTheSea_Server.config.BaseResponse; -//import com.example.UnderTheSea_Server.model.PostGUserReq; -//import com.example.UnderTheSea_Server.model.PostUserRes; -//import com.fasterxml.jackson.core.JsonProcessingException; //import com.google.auth.oauth2.GoogleCredentials; //import com.google.firebase.FirebaseApp; //import com.google.firebase.FirebaseOptions; -//import com.google.firebase.auth.FirebaseAuth; //import lombok.RequiredArgsConstructor; //import org.springframework.context.annotation.Configuration; -//import org.springframework.web.bind.annotation.RequestBody; -//import org.springframework.web.bind.annotation.RequestMapping; //import org.springframework.web.bind.annotation.RestController; // //import javax.annotation.PostConstruct; -//import javax.servlet.http.HttpServletResponse; //import java.io.FileInputStream; //@Configuration //@RestController diff --git a/src/main/java/com/example/UnderTheSea_Server/controller/KakaoLoginController.java b/src/main/java/com/example/UnderTheSea_Server/controller/KakaoLoginController.java index d70413d..78cbe8b 100644 --- a/src/main/java/com/example/UnderTheSea_Server/controller/KakaoLoginController.java +++ b/src/main/java/com/example/UnderTheSea_Server/controller/KakaoLoginController.java @@ -2,11 +2,12 @@ import com.example.UnderTheSea_Server.config.BaseException; import com.example.UnderTheSea_Server.config.BaseResponse; -import com.example.UnderTheSea_Server.model.PostPlanRes; import com.example.UnderTheSea_Server.model.PostUserReq; import com.example.UnderTheSea_Server.model.PostUserRes; import com.example.UnderTheSea_Server.service.KakaoUserService; import com.fasterxml.jackson.core.JsonProcessingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; @@ -16,6 +17,7 @@ @RequiredArgsConstructor public class KakaoLoginController { private final KakaoUserService kakaoUserService; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * Post User API (kakao) @@ -26,6 +28,7 @@ public class KakaoLoginController { public BaseResponse kakaoLogin(@RequestBody PostUserReq postUserReq, HttpServletResponse response) throws JsonProcessingException { try { PostUserRes postUserRes = kakaoUserService.kakaoLogin(postUserReq, response); + logger.debug("debug level test: " + postUserRes.userId); return new BaseResponse<>(postUserRes); } catch(BaseException exception){ return new BaseResponse<>((exception.getStatus())); diff --git a/src/main/java/com/example/UnderTheSea_Server/controller/PlanController.java b/src/main/java/com/example/UnderTheSea_Server/controller/PlanController.java index 53ea6be..2b298a1 100644 --- a/src/main/java/com/example/UnderTheSea_Server/controller/PlanController.java +++ b/src/main/java/com/example/UnderTheSea_Server/controller/PlanController.java @@ -6,6 +6,7 @@ import com.example.UnderTheSea_Server.model.*; import com.example.UnderTheSea_Server.service.PlanService; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/example/UnderTheSea_Server/controller/TestController.java b/src/main/java/com/example/UnderTheSea_Server/controller/TestController.java new file mode 100644 index 0000000..b5e80c0 --- /dev/null +++ b/src/main/java/com/example/UnderTheSea_Server/controller/TestController.java @@ -0,0 +1,44 @@ +package com.example.UnderTheSea_Server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * 권한 확인을 위한 테스트 API + */ +@Slf4j +@RestController +@RequestMapping("/test") +public class TestController { + + + @RequestMapping(value = "/permitAll", method = RequestMethod.GET) + public ResponseEntity permitAll() { + return ResponseEntity.ok("누구나 접근이 가능합니다.\n"); + } + + @RequestMapping(value = "/authenticated", method = RequestMethod.GET) + public ResponseEntity authenticated(@RequestHeader String Authorization) { + log.debug(Authorization); + return ResponseEntity.ok("로그인한 사람 누구나 가능합니다.\n"); + } + + @PreAuthorize("hasAnyRole('User')") + @RequestMapping(value = "/user", method = RequestMethod.GET) + public ResponseEntity user() { + return ResponseEntity.ok("user 가능합니다.\n"); + } + + @PreAuthorize("hasAnyRole('Admin')") + @RequestMapping(value = "/admin", method = RequestMethod.GET) + public ResponseEntity admin(@RequestHeader String Authorization) { + log.debug(Authorization); + return ResponseEntity.ok("admin 가능합니다.\n"); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/UnderTheSea_Server/domain/Quiz.java b/src/main/java/com/example/UnderTheSea_Server/domain/Quiz.java index e0c7082..e69de29 100644 --- a/src/main/java/com/example/UnderTheSea_Server/domain/Quiz.java +++ b/src/main/java/com/example/UnderTheSea_Server/domain/Quiz.java @@ -1,40 +0,0 @@ -package com.example.UnderTheSea_Server.domain; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -import javax.persistence.*; -import java.util.Date; - -@Entity -@Getter -@Setter -@NoArgsConstructor -@Table(name = "Quiz") -public class Quiz { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long quiz_id; - - @Column(nullable = false) - private String content; - - @Column(nullable = false) - private String question; - - @Column(nullable = false) - private String answer; - - @Column(nullable = false) - private String option; - - @Temporal(value = TemporalType.TIMESTAMP) - @Column(nullable = false) - private Date created_at; - - @Temporal(value = TemporalType.TIMESTAMP) - @Column(nullable = false) - private Date updated_at; -} diff --git a/src/main/java/com/example/UnderTheSea_Server/domain/User.java b/src/main/java/com/example/UnderTheSea_Server/domain/User.java index 4027890..d2c34eb 100644 --- a/src/main/java/com/example/UnderTheSea_Server/domain/User.java +++ b/src/main/java/com/example/UnderTheSea_Server/domain/User.java @@ -1,26 +1,23 @@ package com.example.UnderTheSea_Server.domain; -import io.swagger.annotations.Contact; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.*; import org.hibernate.annotations.ColumnDefault; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; -import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; @Data @Entity @Getter @Setter -@AllArgsConstructor @NoArgsConstructor @Table(name = "User") +@JsonIgnoreProperties(ignoreUnknown =true) public class User implements UserDetails{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -57,6 +54,7 @@ public class User implements UserDetails{ @Column(nullable = false) private Date updated_at; + @JsonIgnoreProperties(ignoreUnknown =true) @Builder public User(Long user_id, String email, String nickname, String profileImgUrl, Long character_id, String character_name, Long mileage, UserStatus status, Date created_at, Date updated_at) { this.userId = user_id; @@ -71,42 +69,54 @@ public User(Long user_id, String email, String nickname, String profileImgUrl, L this.updated_at = updated_at; } + /* + @JsonIgnoreProperties(ignoreUnknown =true) @ElementCollection(fetch = FetchType.EAGER) private List roles = new ArrayList<>(); + */ - + @JsonIgnoreProperties(ignoreUnknown =true) @Override public Collection getAuthorities() { + return null; + /* return this.roles.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); + */ } + @JsonIgnoreProperties(ignoreUnknown =true) @Override public String getPassword() { return null; } + @JsonIgnoreProperties(ignoreUnknown =true) @Override public String getUsername() { return userId.toString(); } + @JsonIgnoreProperties(ignoreUnknown =true) @Override public boolean isAccountNonExpired() { return true; } + @JsonIgnoreProperties(ignoreUnknown =true) @Override public boolean isAccountNonLocked() { return true; } + @JsonIgnoreProperties(ignoreUnknown =true) @Override public boolean isCredentialsNonExpired() { return true; } + @JsonIgnoreProperties(ignoreUnknown =true) @Override public boolean isEnabled() { return true; diff --git a/src/main/java/com/example/UnderTheSea_Server/dto/UserDto.java b/src/main/java/com/example/UnderTheSea_Server/dto/UserDto.java index b8d594d..00b6907 100644 --- a/src/main/java/com/example/UnderTheSea_Server/dto/UserDto.java +++ b/src/main/java/com/example/UnderTheSea_Server/dto/UserDto.java @@ -2,6 +2,7 @@ import com.example.UnderTheSea_Server.domain.User; import com.example.UnderTheSea_Server.domain.UserStatus; +import com.example.UnderTheSea_Server.jwt.RefreshToken; import org.springframework.stereotype.Repository; import java.sql.Timestamp; @@ -23,4 +24,11 @@ public User insertUser(String email, String nickname, String profile) { .build(); return userEntity; } + + public RefreshToken insertRefreshToken(String token) { + RefreshToken refreshTokenEntity = RefreshToken.builder() + .refreshToken(token) + .build(); + return refreshTokenEntity; + } } \ No newline at end of file diff --git a/src/main/java/com/example/UnderTheSea_Server/jwt/JwtAuthenticationFilter.java b/src/main/java/com/example/UnderTheSea_Server/jwt/JwtAuthenticationFilter.java index 6601b39..f5ec0a6 100644 --- a/src/main/java/com/example/UnderTheSea_Server/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/UnderTheSea_Server/jwt/JwtAuthenticationFilter.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties; +import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.GenericFilterBean; @@ -11,23 +12,41 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; @RequiredArgsConstructor public class JwtAuthenticationFilter extends GenericFilterBean { private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenRepository refreshTokenRepository; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //헤더에서 jwt 받아오기 - String token = jwtTokenProvider.resolveToken((HttpServletRequest) request); + String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);//.split(" ")[1]; + //System.out.println(token.split(" ")[1]); //유효한 토큰인지 확인 if(token != null && jwtTokenProvider.validateToken(token)){ - //토큰 유효하면 토큰으로부터 유저 정보 받아오기 + System.out.println("mp"); + //토큰 유효하면 토큰으로부터 유저 정보 받아오기 <인증> Authentication authentication = jwtTokenProvider.getAuthentication(token); - //SecurityContext에 Authentication 객체 저장 + System.out.println("nono"); + //SecurityContext에 Authentication 객체 저장 <인가> SecurityContextHolder.getContext().setAuthentication(authentication); } + else if(!jwtTokenProvider.validateToken(token)){ + String refresh = jwtTokenProvider.resolveRefreshToken((HttpServletRequest) request); + if(refresh != null){ + RefreshToken refreshToken = refreshTokenRepository.findByRefreshToken(refresh).get(); + String accessToken = jwtTokenProvider.validateRefreshToken(refreshToken, (HttpServletResponse) response); + if(accessToken != null) { + //토큰 유효하면 토큰으로부터 유저 정보 받아오기 + Authentication authentication = jwtTokenProvider.getAuthentication(accessToken); + //SecurityContext에 Authentication 객체 저장 + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } chain.doFilter(request, response); } } diff --git a/src/main/java/com/example/UnderTheSea_Server/jwt/JwtService.java b/src/main/java/com/example/UnderTheSea_Server/jwt/JwtService.java index 1806556..0e0d7bf 100644 --- a/src/main/java/com/example/UnderTheSea_Server/jwt/JwtService.java +++ b/src/main/java/com/example/UnderTheSea_Server/jwt/JwtService.java @@ -5,6 +5,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.net.http.HttpHeaders; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -17,26 +19,26 @@ public class JwtService { private final JwtTokenProvider jwtTokenProvider; private final RefreshTokenRepository refreshTokenRepository; - @Transactional - public void login(Token tokenDto) { - - RefreshToken refreshToken = RefreshToken.builder().keyId(tokenDto.getKey()).refreshToken(tokenDto.getRefreshToken()).build(); - String loginUserId = refreshToken.getKeyId(); - if (refreshTokenRepository.existsByKeyId(loginUserId)) { - log.info("기존의 존재하는 refresh 토큰 삭제"); - refreshTokenRepository.deleteByKeyId(loginUserId); - } - refreshTokenRepository.save(refreshToken); - - } +// @Transactional +// public void login(Token tokenDto) { +// +// RefreshToken refreshToken = RefreshToken.builder().keyId(tokenDto.getKey()).refreshToken(tokenDto.getRefreshToken()).build(); +// String loginUserId = refreshToken.getKeyId(); +// if (refreshTokenRepository.existsByKeyId(loginUserId)) { +// log.info("기존의 존재하는 refresh 토큰 삭제"); +// refreshTokenRepository.deleteByKeyId(loginUserId); +// } +// refreshTokenRepository.save(refreshToken); +// +// } public Optional getRefreshToken(String refreshToken) { return refreshTokenRepository.findByRefreshToken(refreshToken); } - public Map validateRefreshToken(String refreshToken) { + public Map validateRefreshToken(String refreshToken, HttpServletResponse response) { RefreshToken refreshToken1 = getRefreshToken(refreshToken).get(); - String createdAccessToken = jwtTokenProvider.validateRefreshToken(refreshToken1); + String createdAccessToken = jwtTokenProvider.validateRefreshToken(refreshToken1, response); return createRefreshJson(createdAccessToken); } diff --git a/src/main/java/com/example/UnderTheSea_Server/jwt/JwtTokenProvider.java b/src/main/java/com/example/UnderTheSea_Server/jwt/JwtTokenProvider.java index 60d671d..50e8864 100644 --- a/src/main/java/com/example/UnderTheSea_Server/jwt/JwtTokenProvider.java +++ b/src/main/java/com/example/UnderTheSea_Server/jwt/JwtTokenProvider.java @@ -1,6 +1,7 @@ package com.example.UnderTheSea_Server.jwt; import com.example.UnderTheSea_Server.domain.User; +import com.example.UnderTheSea_Server.dto.UserDto; import com.example.UnderTheSea_Server.service.UserService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; @@ -17,6 +18,7 @@ import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.util.Base64; import java.util.Date; import java.util.List; @@ -31,6 +33,8 @@ public class JwtTokenProvider { private static final long refreshTokenValidTime = 3000 * 60 * 1000L; //jwt 토큰 생성 private final UserService userDetailsService; + private static RefreshTokenRepository refreshTokenRepository; + private static UserDto userDto; //객체 초기화, secretKey를 Base64로 인코딩 @PostConstruct @@ -91,7 +95,11 @@ public boolean validateToken(String jwtToken) { } } - public String validateRefreshToken(RefreshToken refreshTokenObj){ + public String resolveRefreshToken(HttpServletRequest request) { + return request.getHeader("Refresh"); + } + + public String validateRefreshToken(RefreshToken refreshTokenObj, HttpServletResponse response){ // refresh 객체에서 refreshToken 추출 String refreshToken = refreshTokenObj.getRefreshToken(); @@ -101,7 +109,9 @@ public String validateRefreshToken(RefreshToken refreshTokenObj){ //refresh 토큰의 만료시간이 지나지 않았을 경우, 새로운 access 토큰을 생성합니다. if (!claims.getBody().getExpiration().before(new Date())) { - recreationAccessToken(claims.getBody().get("sub").toString(), claims.getBody().get("roles")); + Token accessToken = recreationAccessToken(claims.getBody().get("sub").toString(), claims.getBody().get("roles")); + response.addHeader("Authorization", "BEARER" + " " + accessToken.getAccessToken()); + return accessToken.getAccessToken(); } }catch (Exception e) { //refresh 토큰이 만료되었을 경우, 로그인이 필요합니다. @@ -110,7 +120,7 @@ public String validateRefreshToken(RefreshToken refreshTokenObj){ return null; } - public String recreationAccessToken(String userId, Object roles){ + public Token recreationAccessToken(String userId, Object roles){ Claims claims = Jwts.claims().setSubject(userId); // JWT payload 에 저장되는 정보단위 claims.put("roles", roles); // 정보는 key / value 쌍으로 저장된다. @@ -125,7 +135,7 @@ public String recreationAccessToken(String userId, Object roles){ // signature 에 들어갈 secret값 세팅 .compact(); - return accessToken; + return Token.builder().accessToken(accessToken).key(userId).build(); } } diff --git a/src/main/java/com/example/UnderTheSea_Server/jwt/RefreshToken.java b/src/main/java/com/example/UnderTheSea_Server/jwt/RefreshToken.java index d83ff62..f771999 100644 --- a/src/main/java/com/example/UnderTheSea_Server/jwt/RefreshToken.java +++ b/src/main/java/com/example/UnderTheSea_Server/jwt/RefreshToken.java @@ -1,18 +1,14 @@ package com.example.UnderTheSea_Server.jwt; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import javax.persistence.*; -@Builder @Entity @Getter -@Table(name = "T_REFRESH_TOKEN") +@Setter @NoArgsConstructor -@AllArgsConstructor +@Table(name = "T_REFRESH_TOKEN") public class RefreshToken { @Id @@ -23,7 +19,13 @@ public class RefreshToken { @Column(name = "REFRESH_TOKEN", nullable = false) private String refreshToken; - @Column(name = "KEY", nullable = false) + @Column(name = "KEY_ID", nullable = false) private String keyId; + @Builder + public RefreshToken(Long refreshTokenId, String refreshToken, String keyId) { + this.refreshTokenId = refreshTokenId; + this.refreshToken = refreshToken; + this.keyId = keyId; + } } diff --git a/src/main/java/com/example/UnderTheSea_Server/jwt/RefreshTokenRepository.java b/src/main/java/com/example/UnderTheSea_Server/jwt/RefreshTokenRepository.java index d7231f2..ee5c87e 100644 --- a/src/main/java/com/example/UnderTheSea_Server/jwt/RefreshTokenRepository.java +++ b/src/main/java/com/example/UnderTheSea_Server/jwt/RefreshTokenRepository.java @@ -1,11 +1,15 @@ package com.example.UnderTheSea_Server.jwt; +import com.example.UnderTheSea_Server.domain.User; +import com.example.UnderTheSea_Server.domain.UserStatus; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; import java.util.Optional; +@Repository public interface RefreshTokenRepository extends JpaRepository { Optional findByRefreshToken(String refreshToken); - boolean existsByKeyId(String userId); - void deleteByKeyId(String userId); +// boolean existsByKeyId(String userId); +// void deleteByKeyId(String userId); } \ No newline at end of file diff --git a/src/main/java/com/example/UnderTheSea_Server/jwt/SecurityConfig.java b/src/main/java/com/example/UnderTheSea_Server/jwt/SecurityConfig.java index bd422c8..a93dab6 100644 --- a/src/main/java/com/example/UnderTheSea_Server/jwt/SecurityConfig.java +++ b/src/main/java/com/example/UnderTheSea_Server/jwt/SecurityConfig.java @@ -14,6 +14,7 @@ @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenRepository refreshTokenRepository; //authenticationManager를 Bean 등록(AuthenticationFilter가 생성한 토큰을 받아 AuthenticationProvider에게 넘겨줌 @Bean @@ -30,11 +31,11 @@ public void configure(HttpSecurity http) throws Exception { http.httpBasic().disable() .authorizeRequests() //요청에 대한 사용 권한 확인 .antMatchers("/test").authenticated() - .antMatchers("/admin/**").hasRole("ADMIN") - .antMatchers("/user/**").hasRole("USER") + .antMatchers("/admin/**").hasRole("Admin") + .antMatchers("/user/**").hasRole("User") .antMatchers("/**").permitAll() .and() - .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, refreshTokenRepository), UsernamePasswordAuthenticationFilter.class); //토큰에 저장된 유저정보를 활용하여야 하기 때문에 CustomUserDetailService 클래스 생성 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); diff --git a/src/main/java/com/example/UnderTheSea_Server/jwt/Token.java b/src/main/java/com/example/UnderTheSea_Server/jwt/Token.java index 4a48945..f5e4f4d 100644 --- a/src/main/java/com/example/UnderTheSea_Server/jwt/Token.java +++ b/src/main/java/com/example/UnderTheSea_Server/jwt/Token.java @@ -14,5 +14,4 @@ public class Token { private String accessToken; private String refreshToken; private String key; - } \ No newline at end of file diff --git a/src/main/java/com/example/UnderTheSea_Server/model/PostUserRes.java b/src/main/java/com/example/UnderTheSea_Server/model/PostUserRes.java index eed5106..4f3f61a 100644 --- a/src/main/java/com/example/UnderTheSea_Server/model/PostUserRes.java +++ b/src/main/java/com/example/UnderTheSea_Server/model/PostUserRes.java @@ -6,7 +6,7 @@ @AllArgsConstructor @NoArgsConstructor public class PostUserRes { - public Long id; + public Long userId; public String nickname; public String email; public String profileImgUrl; diff --git a/src/main/java/com/example/UnderTheSea_Server/repository/PlanRepository.java b/src/main/java/com/example/UnderTheSea_Server/repository/PlanRepository.java index a478ddd..cbf03c6 100644 --- a/src/main/java/com/example/UnderTheSea_Server/repository/PlanRepository.java +++ b/src/main/java/com/example/UnderTheSea_Server/repository/PlanRepository.java @@ -2,6 +2,7 @@ import com.example.UnderTheSea_Server.domain.Plan; import com.example.UnderTheSea_Server.domain.User; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -15,8 +16,10 @@ @Repository public interface PlanRepository extends JpaRepository { + @Cacheable(value = "plans", key = "#user", cacheManager = "cacheManager", unless = "#userId == null") List findByUserAndDate(User user, LocalDate date); + //@Cacheable(value = "plan", key = "#plan_id", cacheManager = "cacheManager", unless = "#userId == null") Plan findByUserAndPlanId(User user, Long plan_id); @Modifying diff --git a/src/main/java/com/example/UnderTheSea_Server/repository/QuizRepository.java b/src/main/java/com/example/UnderTheSea_Server/repository/QuizRepository.java deleted file mode 100644 index cdcbdc1..0000000 --- a/src/main/java/com/example/UnderTheSea_Server/repository/QuizRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.UnderTheSea_Server.repository; - -import com.example.UnderTheSea_Server.domain.Quiz; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface QuizRepository extends JpaRepository { - -} diff --git a/src/main/java/com/example/UnderTheSea_Server/repository/UserRepository.java b/src/main/java/com/example/UnderTheSea_Server/repository/UserRepository.java index 57fd32c..a464c2d 100644 --- a/src/main/java/com/example/UnderTheSea_Server/repository/UserRepository.java +++ b/src/main/java/com/example/UnderTheSea_Server/repository/UserRepository.java @@ -1,6 +1,7 @@ package com.example.UnderTheSea_Server.repository; import com.example.UnderTheSea_Server.domain.User; +import org.springframework.cache.annotation.*; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -9,14 +10,19 @@ import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; -import java.util.Date; @Repository public interface UserRepository extends JpaRepository{ + //@Cacheable(value = "kakao_user_id", key = "#userId", cacheManager = "cacheManager", unless = "#userId == null") + //@CacheEvict(value = "kakao_user_id", key = "#userId", cacheManager = "cacheManager") + @CachePut(value = "kakao_user_id", key = "#userId", cacheManager = "cacheManager", unless = "#userId == null") User findByUserId(Long userId); // JPA Query Method + //@Cacheable(value = "kakao_user_email", key = "#email", cacheManager = "cacheManager", unless = "#userId == null") User findByEmail(String email); + //@CachePut(key = "#user_id") + //@CacheEvict(key = "#user_id") @Modifying @Transactional @Query("UPDATE User u set u.characterId = :character_id, u.characterName = :character_name, u.updated_at = :updated_at where u.userId = :user_id") diff --git a/src/main/java/com/example/UnderTheSea_Server/service/GoogleUserService.java b/src/main/java/com/example/UnderTheSea_Server/service/GoogleUserService.java index 150e18e..d8c0d79 100644 --- a/src/main/java/com/example/UnderTheSea_Server/service/GoogleUserService.java +++ b/src/main/java/com/example/UnderTheSea_Server/service/GoogleUserService.java @@ -39,9 +39,10 @@ private User registerGoogleUserIfNeed(GoogleUserInfoDto googleUserInfo) { private void googleUsersAuthorizationInput(HttpServletResponse response) { // response header에 token 추가 - Token token = JwtTokenProvider.createToken(googleUser, "user_id"); + Token token = JwtTokenProvider.createToken(googleUser, "user"); //jwtService.login(token); response.addHeader("Authorization", "BEARER" + " " + token.getAccessToken()); + response.addHeader("Refresh", token.getRefreshToken()); } public PostUserRes googleLogin(PostGUserReq postGUserReq, HttpServletResponse response) throws BaseException { diff --git a/src/main/java/com/example/UnderTheSea_Server/service/KakaoUserService.java b/src/main/java/com/example/UnderTheSea_Server/service/KakaoUserService.java index 30df599..493ccd8 100644 --- a/src/main/java/com/example/UnderTheSea_Server/service/KakaoUserService.java +++ b/src/main/java/com/example/UnderTheSea_Server/service/KakaoUserService.java @@ -5,22 +5,18 @@ import com.example.UnderTheSea_Server.dto.KakaoUserInfoDto; import com.example.UnderTheSea_Server.domain.User; import com.example.UnderTheSea_Server.dto.UserDto; -import com.example.UnderTheSea_Server.jwt.JwtService; -import com.example.UnderTheSea_Server.jwt.JwtTokenProvider; -import com.example.UnderTheSea_Server.jwt.Token; +import com.example.UnderTheSea_Server.jwt.*; import com.example.UnderTheSea_Server.model.PostUserReq; import com.example.UnderTheSea_Server.model.PostUserRes; import com.example.UnderTheSea_Server.repository.UserRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.firebase.auth.FirebaseAuth; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; @@ -33,6 +29,7 @@ @RequiredArgsConstructor public class KakaoUserService { private final UserRepository userRepository; + private final RefreshTokenRepository refreshTokenRepository; public final UserDto userDto; public final JwtService jwtService; private User kakaoUser; @@ -46,12 +43,13 @@ private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcess headers.add("Authorization", "Bearer " + accessToken); headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); - //firebase jwt 생성 + /*firebase jwt 생성 try { customToken = FirebaseAuth.getInstance().createCustomToken(accessToken); } catch (Exception e) { e.printStackTrace(); } + */ // HTTP 요청 보내기 HttpEntity> kakaoUserInfoRequest = new HttpEntity<>(headers); @@ -94,11 +92,14 @@ private User registerKakaoUserIfNeed(KakaoUserInfoDto kakaoUserInfo) { private void kakaoUsersAuthorizationInput(HttpServletResponse response) { // response header에 token 추가 - Token token = JwtTokenProvider.createToken(kakaoUser, "user_id"); + Token token = JwtTokenProvider.createToken(kakaoUser, "User"); //jwtService.login(token); response.addHeader("Authorization", "BEARER" + " " + token.getAccessToken()); - } + response.addHeader("Refresh", token.getRefreshToken()); + RefreshToken refreshToken = RefreshToken.builder().keyId(token.getKey()).refreshToken(token.getRefreshToken()).build(); + refreshTokenRepository.save(refreshToken); + } public PostUserRes kakaoLogin(PostUserReq postUserReq, HttpServletResponse response) throws BaseException { try { diff --git a/src/main/java/com/example/UnderTheSea_Server/util/redis/CacheConfig.java b/src/main/java/com/example/UnderTheSea_Server/util/redis/CacheConfig.java new file mode 100644 index 0000000..7eb29f2 --- /dev/null +++ b/src/main/java/com/example/UnderTheSea_Server/util/redis/CacheConfig.java @@ -0,0 +1,29 @@ +package com.example.UnderTheSea_Server.util.redis; + +import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +@EnableCaching +@Configuration +public class CacheConfig { + @Bean + public CacheManager cacheManager(RedisConnectionFactory cf) { + RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경 + .entryTtl(Duration.ofMinutes(3L)); // 캐시 수명 30분 + + return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf).cacheDefaults(redisCacheConfiguration).build(); + } +} diff --git a/src/main/java/com/example/UnderTheSea_Server/util/redis/RedisConfig.java b/src/main/java/com/example/UnderTheSea_Server/util/redis/RedisConfig.java new file mode 100644 index 0000000..0e3f0d9 --- /dev/null +++ b/src/main/java/com/example/UnderTheSea_Server/util/redis/RedisConfig.java @@ -0,0 +1,38 @@ +package com.example.UnderTheSea_Server.util.redis; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/**Redis 연결을 위한 기본 설정**/ +@Configuration +@EnableRedisRepositories +public class RedisConfig { + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + +/* + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory());; + return redisTemplate; + } + */ +} diff --git a/src/main/java/com/example/UnderTheSea_Server/util/redis/RedisDao.java b/src/main/java/com/example/UnderTheSea_Server/util/redis/RedisDao.java new file mode 100644 index 0000000..4242413 --- /dev/null +++ b/src/main/java/com/example/UnderTheSea_Server/util/redis/RedisDao.java @@ -0,0 +1,44 @@ +package com.example.UnderTheSea_Server.util.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class RedisDao { + private final RedisTemplate redisTemplate; + + public void setValues(String key, String data) { + ValueOperations values = redisTemplate.opsForValue(); + values.set(key, data); + } + + public void setValuesList(String key, String data) { + redisTemplate.opsForList().rightPushAll(key,data); + } + + public List getValuesList(String key) { + Long len = redisTemplate.opsForList().size(key); + return len == 0 ? new ArrayList<>() : redisTemplate.opsForList().range(key, 0, len-1); + } + + public void setValues(String key, String data, Duration duration) { + ValueOperations values = redisTemplate.opsForValue(); + values.set(key, data, duration); + } + + public String getValues(String key) { + ValueOperations values = redisTemplate.opsForValue(); + return values.get(key); + } + + public void deleteValues(String key) { + redisTemplate.delete(key); + } +} diff --git a/src/test/java/com/example/UnderTheSea_Server/UnderTheSeaServerApplicationTests.java b/src/test/java/com/example/UnderTheSea_Server/UnderTheSeaServerApplicationTests.java index c6e8af8..71100c0 100644 --- a/src/test/java/com/example/UnderTheSea_Server/UnderTheSeaServerApplicationTests.java +++ b/src/test/java/com/example/UnderTheSea_Server/UnderTheSeaServerApplicationTests.java @@ -1,13 +1,48 @@ package com.example.UnderTheSea_Server; +import com.example.UnderTheSea_Server.domain.User; +import com.example.UnderTheSea_Server.domain.UserStatus; +import com.example.UnderTheSea_Server.repository.UserRepository; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; + +// 실제 application.yaml 내용 기반으로 db 연결 및 활용 @SpringBootTest +// 실제 db 없이 인 메모리 db 활용 +//@DataJpaTest +//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class UnderTheSeaServerApplicationTests { + @Autowired + private UserRepository userRepository; @Test + @Transactional // rollback 수행 void contextLoads() { + //given + String email = "wyoung2@gmail.com"; + User user = User.builder() + //.user_id(13l) + .nickname("woo2") + .email("wyoung2@gmail.com") + .updated_at(new Date()) + .created_at(new Date()) + .profileImgUrl("http") + .status(UserStatus.ACTIVE) + .build(); + + //when + User saved = userRepository.save(user); + + //then + assertThat(saved.getEmail()).isEqualTo(email); } }