diff --git a/order-service/src/main/java/com/justpickup/orderservice/OrderServiceApplication.java b/order-service/src/main/java/com/justpickup/orderservice/OrderServiceApplication.java index 698d9fd..28ddf86 100644 --- a/order-service/src/main/java/com/justpickup/orderservice/OrderServiceApplication.java +++ b/order-service/src/main/java/com/justpickup/orderservice/OrderServiceApplication.java @@ -1,9 +1,17 @@ package com.justpickup.orderservice; +import com.justpickup.orderservice.domain.order.entity.Order; +import com.justpickup.orderservice.domain.order.repository.OrderRepository; +import com.justpickup.orderservice.domain.orderItem.entity.OrderItem; +import com.justpickup.orderservice.domain.orderItemOption.entity.OrderItemOption; +import com.justpickup.orderservice.domain.transaction.entity.Transaction; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; +import org.springframework.transaction.annotation.Transactional; @SpringBootApplication @EnableEurekaClient @@ -14,4 +22,32 @@ public class OrderServiceApplication { SpringApplication.run(OrderServiceApplication.class, args); } + @Bean + @Transactional + CommandLineRunner run(OrderRepository orderRepository) { + return args -> { + Long userId = 1L; + Long userCouponId = null; + Long storeId = 1L; + Long orderPrice = 1000L; + + for (int i = 1; i <= 20; i++) { + Transaction transaction = Transaction.of(); + + Long itemId = 1L; + Long price = 100L; + Long count = 1L; + OrderItem orderItem = OrderItem.of(itemId + i, price * i, count + i, + OrderItemOption.of(), OrderItemOption.of()); + + OrderItem orderItem1 = OrderItem.of(itemId + i + 1, price * (i + 1), count + (i + 1), + OrderItemOption.of(), OrderItemOption.of()); + + Order order = Order.of(userId, userCouponId, storeId, orderPrice * i, transaction, orderItem, orderItem1); + + orderRepository.save(order); + } + }; + } + } diff --git a/order-service/src/main/java/com/justpickup/orderservice/domain/order/entity/Order.java b/order-service/src/main/java/com/justpickup/orderservice/domain/order/entity/Order.java index 7b2554e..f85b943 100644 --- a/order-service/src/main/java/com/justpickup/orderservice/domain/order/entity/Order.java +++ b/order-service/src/main/java/com/justpickup/orderservice/domain/order/entity/Order.java @@ -10,6 +10,7 @@ import org.hibernate.annotations.BatchSize; import javax.persistence.*; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; @Entity @@ -37,11 +38,39 @@ public class Order extends BaseEntity { @Enumerated(EnumType.STRING) private OrderStatus orderStatus; - @OneToOne(mappedBy = "order", fetch = FetchType.LAZY) + @OneToOne(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private Transaction transaction; @BatchSize(size = 100) - @OneToMany(mappedBy = "order") - private List orderItems; + @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) + private List orderItems = new ArrayList<>(); + public static Order of(Long userId, Long userCouponId, Long storeId, Long orderPrice, + Transaction transaction, OrderItem... orderItems) { + Order order = new Order(); + order.userId = userId; + order.userCouponId = userCouponId; + order.storeId = storeId; + order.orderPrice = orderPrice; + + order.setTransaction(transaction); + for (OrderItem orderItem : orderItems) { + order.addOrderItem(orderItem); + } + + order.usedPoint = 0L; + order.orderStatus = OrderStatus.PLACED; + order.orderTime = LocalDateTime.now(); + return order; + } + + public void setTransaction(Transaction transaction) { + this.transaction = transaction; + transaction.setOrder(this); + } + + public void addOrderItem(OrderItem orderItem) { + this.orderItems.add(orderItem); + orderItem.setOrder(this); + } } diff --git a/order-service/src/main/java/com/justpickup/orderservice/domain/orderItem/entity/OrderItem.java b/order-service/src/main/java/com/justpickup/orderservice/domain/orderItem/entity/OrderItem.java index 2b80748..b94e069 100644 --- a/order-service/src/main/java/com/justpickup/orderservice/domain/orderItem/entity/OrderItem.java +++ b/order-service/src/main/java/com/justpickup/orderservice/domain/orderItem/entity/OrderItem.java @@ -9,6 +9,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import javax.persistence.*; +import java.util.ArrayList; import java.util.List; @Entity @@ -27,11 +28,30 @@ public class OrderItem extends BaseEntity { @JoinColumn(name = "order_id") private Order order; - @OneToMany(mappedBy = "orderItem") - private List orderItemOptions; + @OneToMany(mappedBy = "orderItem", cascade = CascadeType.ALL) + private List orderItemOptions = new ArrayList<>(); private Long price; private Long count; + public void setOrder(Order order) { + this.order = order; + } + + public void addOrderItemOption(OrderItemOption orderItemOption) { + this.orderItemOptions.add(orderItemOption); + orderItemOption.setOrderItem(this); + } + + public static OrderItem of(Long itemId, Long price, Long count, OrderItemOption... orderItemOptions) { + OrderItem orderItem = new OrderItem(); + orderItem.itemId = itemId; + orderItem.price = price; + orderItem.count = count; + for (OrderItemOption orderItemOption : orderItemOptions) { + orderItem.addOrderItemOption(orderItemOption); + } + return orderItem; + } } diff --git a/order-service/src/main/java/com/justpickup/orderservice/domain/orderItemOption/entity/OrderItemOption.java b/order-service/src/main/java/com/justpickup/orderservice/domain/orderItemOption/entity/OrderItemOption.java index 28148bb..14ce23a 100644 --- a/order-service/src/main/java/com/justpickup/orderservice/domain/orderItemOption/entity/OrderItemOption.java +++ b/order-service/src/main/java/com/justpickup/orderservice/domain/orderItemOption/entity/OrderItemOption.java @@ -24,4 +24,12 @@ public class OrderItemOption extends BaseEntity { private Long itemOptionId; + public void setOrderItem(OrderItem orderItem) { + this.orderItem = orderItem; + } + + public static OrderItemOption of() { + OrderItemOption orderItemOption = new OrderItemOption(); + return orderItemOption; + } } diff --git a/order-service/src/main/java/com/justpickup/orderservice/domain/transaction/entity/Transaction.java b/order-service/src/main/java/com/justpickup/orderservice/domain/transaction/entity/Transaction.java index 3f230df..6be578c 100644 --- a/order-service/src/main/java/com/justpickup/orderservice/domain/transaction/entity/Transaction.java +++ b/order-service/src/main/java/com/justpickup/orderservice/domain/transaction/entity/Transaction.java @@ -22,4 +22,13 @@ public class Transaction extends BaseEntity { @JoinColumn(name = "order_id") private Order order; + public void setOrder(Order order) { + this.order = order; + } + + public static Transaction of() { + Transaction transaction = new Transaction(); + return transaction; + } + } diff --git a/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/OwnerApigatewayServiceApplication.java b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/OwnerApigatewayServiceApplication.java index a739e2a..2bc155f 100644 --- a/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/OwnerApigatewayServiceApplication.java +++ b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/OwnerApigatewayServiceApplication.java @@ -1,8 +1,11 @@ package com.justpickup.ownerapigatewayservice; +import com.justpickup.ownerapigatewayservice.handler.GlobalExceptionHandler; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; +import org.springframework.context.annotation.Bean; @SpringBootApplication @EnableEurekaClient @@ -12,4 +15,9 @@ public class OwnerApigatewayServiceApplication { SpringApplication.run(OwnerApigatewayServiceApplication.class, args); } + @Bean + public ErrorWebExceptionHandler globalExceptionHandler() { + return new GlobalExceptionHandler(); + } + } diff --git a/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/filter/AuthorizationHeaderFilter.java b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/filter/AuthorizationHeaderFilter.java index 406aeef..c723899 100644 --- a/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/filter/AuthorizationHeaderFilter.java +++ b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/filter/AuthorizationHeaderFilter.java @@ -44,9 +44,7 @@ public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory handle(ServerWebExchange exchange, Throwable ex) { + + Class exceptionClass = ex.getClass(); + + Map responseBody = new HashMap<>(); + if (exceptionClass == ExpiredJwtException.class) { + exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); + exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON); + responseBody.put("code", "EXPIRED"); + responseBody.put("message", "Access Token is Expired!"); + } + + DataBuffer wrap = null; + try { + byte[] bytes = objectMapper.writeValueAsBytes(responseBody); + wrap = exchange.getResponse().bufferFactory().wrap(bytes); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + return exchange.getResponse().writeWith(Flux.just(wrap)); + } +} diff --git a/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/security/JwtTokenProvider.java b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/security/JwtTokenProvider.java index f5752f5..1b42fe8 100644 --- a/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/security/JwtTokenProvider.java +++ b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/security/JwtTokenProvider.java @@ -73,25 +73,12 @@ public class JwtTokenProvider { return (List) getClaimsFromJwtToken(token).get("roles"); } - public boolean validateJwtToken(String token) { + public void validateJwtToken(String token) { try { Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); - return true; - } catch (SignatureException e) { - log.error("Invalid JWT signature: {}", e.getMessage()); - return false; - } catch (MalformedJwtException e) { - log.error("Invalid JWT token: {}", e.getMessage()); - return false; - } catch (ExpiredJwtException e) { - log.error("JWT token is expired: {}", e.getMessage()); - return false; - } catch (UnsupportedJwtException e) { - log.error("JWT token is unsupported: {}", e.getMessage()); - return false; - } catch (IllegalArgumentException e) { - log.error("JWT claims string is empty: {}", e.getMessage()); - return false; + } catch (SignatureException | MalformedJwtException | + UnsupportedJwtException | IllegalArgumentException | ExpiredJwtException jwtException) { + throw jwtException; } } diff --git a/owner-apigateway-service/src/main/resources/application.yml b/owner-apigateway-service/src/main/resources/application.yml index 2c62442..8a02c6f 100644 --- a/owner-apigateway-service/src/main/resources/application.yml +++ b/owner-apigateway-service/src/main/resources/application.yml @@ -23,7 +23,7 @@ spring: globalcors: cors-configurations: '[/**]': - allowedOrigins: "*" + allowedOrigins: "http://localhost:8080" allowedMethods: - GET - POST @@ -32,6 +32,7 @@ spring: - OPTIONS - DELETE allowedHeaders: '*' + allow-credentials: true routes: - id: owner-frontend-service uri: lb://OWNER-FRONTEND-SERVICE @@ -39,18 +40,22 @@ spring: - Path=/owner-frontend-service/** filters: - RewritePath=/owner-frontend-service/(?.*),/$\{segment} + - id: order-service uri: lb://ORDER-SERVCIE predicates: - Path=/order-service/** filters: - RewritePath=/order-service/(?.*),/$\{segment} + - AuthorizationHeaderFilter + - id: store-service uri: lb://STORE-SERVCIE predicates: - Path=/store-service/** filters: - RewritePath=/store-service/(?.*),/$\{segment} + - id: user-service uri: lb://USER-SERVICE predicates: @@ -61,7 +66,7 @@ spring: - id: user-service uri: lb://USER-SERVICE predicates: - - Path=/user-service/refreshToken + - Path=/user-service/auth/reissue - Method=GET filters: - RewritePath=/user-service/(?.*),/$\{segment} @@ -85,6 +90,7 @@ spring: - Path=/user-service/** filters: - RewritePath=/user-service/(?.*),/$\{segment} + - AuthorizationHeaderFilter token: access-expired-time: 3600000 diff --git a/owner-vue/src/api/auth.js b/owner-vue/src/api/auth.js new file mode 100644 index 0000000..87c772b --- /dev/null +++ b/owner-vue/src/api/auth.js @@ -0,0 +1,26 @@ +import axios from "axios"; +import jwt from "@/common/jwt"; + +export default { + async requestReissue() { + const config = { + headers: { + "X-AUTH-TOKEN": jwt.getToken() + } + } + + const res = await axios.get("http://localhost:8001/user-service/auth/reissue", config); + + const accessToken = res.data.data.accessToken; + jwt.saveToken(accessToken); + jwt.saveExpiredTime(res.data.data.expiredTime); + axios.defaults.headers.common['Authorization'] = "Bearer " + accessToken; + + return accessToken; + }, + requestCheckAccessToken() { + axios.defaults.headers.common['Authorization'] = "Bearer " + jwt.getToken(); + + return axios.get("http://localhost:8001/user-service/auth/check/access-token"); + } +} \ No newline at end of file diff --git a/owner-vue/src/api/user.js b/owner-vue/src/api/user.js index 50ad23c..622da40 100644 --- a/owner-vue/src/api/user.js +++ b/owner-vue/src/api/user.js @@ -1,8 +1,32 @@ -import axios from "axios"; +import jwt from '../common/jwt.js'; export default { + requestRegisterUser(user) { + return axios.post("http://localhost:8001/user-service/store-owner", user); + }, - requestRegisterUser(user) { - return axios.post("http://localhost:8001/user-service/store-owner", user); + async requestLoginUser(email, password) { + const user = { + email: email, + password: password } -} \ No newline at end of file + + try { + const response = await axios.post("http://localhost:8001/user-service/login", user); + const data = response.data.data; + + jwt.saveToken(data.accessToken); + jwt.saveExpiredTime(data.expiredTime); + + axios.defaults.headers.common['Authorization'] = "Bearer " + data.accessToken; + + return true; + } catch (err) { + console.log("Error = ", err); + return false; + } + + } +} + +import axios from "axios"; diff --git a/owner-vue/src/common/jwt.js b/owner-vue/src/common/jwt.js new file mode 100644 index 0000000..0783eb5 --- /dev/null +++ b/owner-vue/src/common/jwt.js @@ -0,0 +1,38 @@ + +const moment = require('moment'); +const ACCESS_TOKEN_NAME = "accessToken"; +const EXPIRED_TIME_NAME = "expiredTime"; + +const tag = "[jwt]"; + +export default { + getToken() { + return localStorage.getItem(ACCESS_TOKEN_NAME); + }, + saveToken(token) { + localStorage.setItem(ACCESS_TOKEN_NAME, token); + }, + getExpiredTime() { + return localStorage.getItem(EXPIRED_TIME_NAME); + }, + saveExpiredTime(expiredTime) { + localStorage.setItem(EXPIRED_TIME_NAME, expiredTime); + }, + destroyAll() { + localStorage.removeItem(ACCESS_TOKEN_NAME); + localStorage.removeItem(EXPIRED_TIME_NAME); + }, + isExpired() { + const expiredTime = this.getExpiredTime(); + + const expiredMoment = moment(expiredTime); + let currentMoment = moment(); + + const difference = moment.duration(expiredMoment.diff(currentMoment)).asSeconds(); + + console.log(tag, "expireMoment = ", expiredMoment, "currentMoment = ", currentMoment, "diff = ", difference); + + // 만료 30초 전일 경우 만료로 판단 + return difference <= 30; + } +} \ No newline at end of file diff --git a/owner-vue/src/main.js b/owner-vue/src/main.js index d9e8961..b5462aa 100644 --- a/owner-vue/src/main.js +++ b/owner-vue/src/main.js @@ -1,10 +1,11 @@ -import Vue from 'vue' -import App from './App.vue' -import vuetify from './plugins/vuetify' -import router from './router' +import Vue from 'vue'; +import App from './App.vue'; +import vuetify from './plugins/vuetify'; +import router from './router'; import axios from "axios"; -import customUtil from './util/customUtil' +import auth from "./api/auth.js"; +axios.defaults.withCredentials = true; Vue.config.productionTip = false Vue.prototype.$axios = axios; @@ -17,3 +18,29 @@ new Vue({ router, render: h => h(App) }).$mount('#app') + +axios.interceptors.response.use( + (response) => { + return response; + }, + async (error) => { + const originalRequest = error.config; + if (error.response.status === 401) { + let code = error.response.data.code; + if (code === "EXPIRED") { + console.log("## expired"); + try { + const accessToken = await auth.requestReissue(); + originalRequest.headers.Authorization = "Bearer " + accessToken; + return axios(originalRequest); + } catch (reissueError) { + window.location.href = "http://localhost:8080"; + alert("권한이 없습니다. 다시 로그인 해주세요"); + } + } + window.location.href = "http://localhost:8080"; + alert("권한이 없습니다. 다시 로그인해주세요."); + } + return Promise.reject(error); + } +); \ No newline at end of file diff --git a/owner-vue/src/router/index.js b/owner-vue/src/router/index.js index 1fc109b..5d593a4 100644 --- a/owner-vue/src/router/index.js +++ b/owner-vue/src/router/index.js @@ -4,13 +4,30 @@ import VueRouter from 'vue-router' import DashboardLayout from "@/views/Layout/DashboardLayout"; import AuthLayout from "@/views/Layout/AuthLayout"; +import jwt from "../common/jwt.js"; +import auth from "../api/auth.js"; + Vue.use(VueRouter) +const authCheck = async function (to, from, next) { + try { + if (jwt.isExpired()) { + // refresh 호출 + await auth.requestReissue(); + } else { + await auth.requestCheckAccessToken(); + } + } catch (error) { + await router.replace("/login"); + } + next(); +}; const routes = [ { path: '/dashboard', redirect: 'dashboard', component: DashboardLayout, + beforeEnter: authCheck, children: [ { path: "/dashboard", @@ -64,4 +81,4 @@ const router = new VueRouter({ routes }) -export default router +export default router \ No newline at end of file diff --git a/owner-vue/src/views/LoginUser.vue b/owner-vue/src/views/LoginUser.vue new file mode 100644 index 0000000..2496b06 --- /dev/null +++ b/owner-vue/src/views/LoginUser.vue @@ -0,0 +1,61 @@ + + + + + \ No newline at end of file diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/jwt/exception/AccessTokenNotValidException.java b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/exception/AccessTokenNotValidException.java index a0ffe60..e4fd941 100644 --- a/user-service/src/main/java/com/justpickup/userservice/domain/jwt/exception/AccessTokenNotValidException.java +++ b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/exception/AccessTokenNotValidException.java @@ -6,6 +6,6 @@ import org.springframework.http.HttpStatus; public class AccessTokenNotValidException extends CustomException { public AccessTokenNotValidException(String message) { - super(HttpStatus.FORBIDDEN, message); + super(HttpStatus.UNAUTHORIZED, message); } } diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/AccessTokenService.java b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/AccessTokenService.java new file mode 100644 index 0000000..9db3834 --- /dev/null +++ b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/AccessTokenService.java @@ -0,0 +1,6 @@ +package com.justpickup.userservice.domain.jwt.service; + +public interface AccessTokenService { + + void checkAccessToken(String authorizationHeader); +} diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/AccessTokenServiceImpl.java b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/AccessTokenServiceImpl.java new file mode 100644 index 0000000..0c6cc0a --- /dev/null +++ b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/AccessTokenServiceImpl.java @@ -0,0 +1,23 @@ +package com.justpickup.userservice.domain.jwt.service; + +import com.justpickup.userservice.domain.jwt.exception.AccessTokenNotValidException; +import com.justpickup.userservice.global.utils.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AccessTokenServiceImpl implements AccessTokenService { + + private final JwtTokenProvider tokenProvider; + + @Override + public void checkAccessToken(String authorizationHeader) { + + String token = authorizationHeader.replace("Bearer", ""); + + if (!tokenProvider.validateJwtToken(token)) { + throw new AccessTokenNotValidException("Access Token is not Valid"); + } + } +} diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/RefreshTokenServiceImpl.java b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/RefreshTokenServiceImpl.java index fcd9981..115a9ad 100644 --- a/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/RefreshTokenServiceImpl.java +++ b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/service/RefreshTokenServiceImpl.java @@ -19,6 +19,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; @@ -69,9 +70,11 @@ public class RefreshTokenServiceImpl implements RefreshTokenService { .stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); String newAccessToken = jwtTokenProvider.createJwtAccessToken(userId, "/refreshToken", roles); + Date expiredTime = jwtTokenProvider.getExpiredTime(newAccessToken); return JwtTokenDto.builder() .accessToken(newAccessToken) + .accessTokenExpiredDate(expiredTime) .refreshToken(refreshToken) .build(); } diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/jwt/web/AuthController.java b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/web/AuthController.java index 39cfd79..f679a11 100644 --- a/user-service/src/main/java/com/justpickup/userservice/domain/jwt/web/AuthController.java +++ b/user-service/src/main/java/com/justpickup/userservice/domain/jwt/web/AuthController.java @@ -1,6 +1,7 @@ package com.justpickup.userservice.domain.jwt.web; -import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl; +import com.justpickup.userservice.domain.jwt.service.AccessTokenService; +import com.justpickup.userservice.domain.jwt.service.RefreshTokenService; import com.justpickup.userservice.domain.user.dto.JwtTokenDto; import com.justpickup.userservice.global.dto.Result; import com.justpickup.userservice.global.utils.CookieProvider; @@ -15,20 +16,22 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.ws.rs.core.HttpHeaders; +import java.text.SimpleDateFormat; @RestController @RequiredArgsConstructor +@RequestMapping("/auth") @Slf4j public class AuthController { - private final RefreshTokenServiceImpl refreshTokenServiceImpl; + private final RefreshTokenService refreshTokenService; + private final AccessTokenService accessTokenService; private final CookieProvider cookieProvider; - @GetMapping("/refreshToken") + @GetMapping("/reissue") public ResponseEntity refreshToken(@RequestHeader("X-AUTH-TOKEN") String accessToken, @CookieValue("refresh-token") String refreshToken) { - - JwtTokenDto jwtTokenDto = refreshTokenServiceImpl.refreshJwtToken(accessToken, refreshToken); + JwtTokenDto jwtTokenDto = refreshTokenService.refreshJwtToken(accessToken, refreshToken); ResponseCookie responseCookie = cookieProvider.createRefreshTokenCookie(refreshToken); @@ -42,9 +45,12 @@ public class AuthController { @AllArgsConstructor static class RefreshTokenResponse { private String accessToken; + private String expiredTime; public RefreshTokenResponse(JwtTokenDto jwtTokenDto) { this.accessToken = jwtTokenDto.getAccessToken(); + this.expiredTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + .format(jwtTokenDto.getAccessTokenExpiredDate()); } } @@ -52,7 +58,7 @@ public class AuthController { public ResponseEntity logout(@RequestHeader("X-AUTH-TOKEN") String accessToken, @RequestHeader("REFRESH-TOKEN") String refreshToken) { - refreshTokenServiceImpl.logoutToken(accessToken); + refreshTokenService.logoutToken(accessToken); ResponseCookie refreshCookie = cookieProvider.removeRefreshTokenCookie(); @@ -60,4 +66,13 @@ public class AuthController { .header(HttpHeaders.SET_COOKIE, refreshCookie.toString()) .body(Result.createErrorResult("")); } + + @GetMapping("/check/access-token") + public ResponseEntity checkAccessToken(@RequestHeader(name = "Authorization") String authorization) { + + accessTokenService.checkAccessToken(authorization); + + return ResponseEntity.status(HttpStatus.OK) + .body(Result.createSuccessResult(null)); + } } diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/user/dto/JwtTokenDto.java b/user-service/src/main/java/com/justpickup/userservice/domain/user/dto/JwtTokenDto.java index 9e1f015..6162ce6 100644 --- a/user-service/src/main/java/com/justpickup/userservice/domain/user/dto/JwtTokenDto.java +++ b/user-service/src/main/java/com/justpickup/userservice/domain/user/dto/JwtTokenDto.java @@ -3,14 +3,18 @@ package com.justpickup.userservice.domain.user.dto; import lombok.Builder; import lombok.Getter; +import java.util.Date; + @Getter public class JwtTokenDto { private String accessToken; + private Date accessTokenExpiredDate; private String refreshToken; @Builder - public JwtTokenDto(String accessToken, String refreshToken) { + public JwtTokenDto(String accessToken, String refreshToken, Date accessTokenExpiredDate) { this.accessToken = accessToken; this.refreshToken = refreshToken; + this.accessTokenExpiredDate = accessTokenExpiredDate; } } diff --git a/user-service/src/main/java/com/justpickup/userservice/global/exception/GlobalExceptionHandler.java b/user-service/src/main/java/com/justpickup/userservice/global/exception/GlobalExceptionHandler.java index 55d9c4a..e1e6473 100644 --- a/user-service/src/main/java/com/justpickup/userservice/global/exception/GlobalExceptionHandler.java +++ b/user-service/src/main/java/com/justpickup/userservice/global/exception/GlobalExceptionHandler.java @@ -36,7 +36,7 @@ public class GlobalExceptionHandler { // 쿠키 삭제 ResponseCookie responseCookie = cookieProvider.removeRefreshTokenCookie(); - return ResponseEntity.status(HttpStatus.FORBIDDEN) + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .header(HttpHeaders.SET_COOKIE, responseCookie.toString()) .body(e.getResult()); } diff --git a/user-service/src/main/java/com/justpickup/userservice/global/security/LoginAuthenticationFilter.java b/user-service/src/main/java/com/justpickup/userservice/global/security/LoginAuthenticationFilter.java index 1b6ad13..464b9cd 100644 --- a/user-service/src/main/java/com/justpickup/userservice/global/security/LoginAuthenticationFilter.java +++ b/user-service/src/main/java/com/justpickup/userservice/global/security/LoginAuthenticationFilter.java @@ -18,11 +18,12 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain; -import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -70,6 +71,7 @@ public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFil String userId = user.getUsername(); String accessToken = jwtTokenProvider.createJwtAccessToken(userId, request.getRequestURI(), roles); + Date expiredTime = jwtTokenProvider.getExpiredTime(accessToken); String refreshToken = jwtTokenProvider.createJwtRefreshToken(); refreshTokenServiceImpl.updateRefreshToken(Long.valueOf(userId), jwtTokenProvider.getRefreshTokenId(refreshToken)); @@ -83,17 +85,11 @@ public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFil response.addCookie(cookie); // body 설정 - Map tokens = Map.of( - "access_token", accessToken + Map tokens = Map.of( + "accessToken", accessToken, + "expiredTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(expiredTime) ); new ObjectMapper().writeValue(response.getOutputStream(), Result.createSuccessResult(tokens)); } - - @Override - protected void unsuccessfulAuthentication - (HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) - throws IOException, ServletException { - log.warn("로그인 실패!!"); - } } diff --git a/user-service/src/main/java/com/justpickup/userservice/global/utils/JwtTokenProvider.java b/user-service/src/main/java/com/justpickup/userservice/global/utils/JwtTokenProvider.java index 14c9d65..479174b 100644 --- a/user-service/src/main/java/com/justpickup/userservice/global/utils/JwtTokenProvider.java +++ b/user-service/src/main/java/com/justpickup/userservice/global/utils/JwtTokenProvider.java @@ -5,10 +5,6 @@ import io.jsonwebtoken.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; import java.util.Date; @@ -57,8 +53,6 @@ public class JwtTokenProvider { .compact(); } - - public String getUserId(String token) { return getClaimsFromJwtToken(token).getSubject(); } @@ -75,6 +69,10 @@ public class JwtTokenProvider { return getClaimsFromJwtToken(token).get("value").toString(); } + public Date getExpiredTime(String token) { + return getClaimsFromJwtToken(token).getExpiration(); + } + public List getRoles(String token) { return (List) getClaimsFromJwtToken(token).get("roles"); }