feat(customer vue, user-service): customer vue OAuth 인증
- customer vue 인증페이지 구현 - customer OAuth 로그인 구현 - user-service success handling 구현
This commit is contained in:
@@ -31,6 +31,8 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/io.netty/netty-resolver-dns-native-macos
|
||||
implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64'
|
||||
|
||||
implementation 'javax.xml.bind:jaxb-api:2.3.1'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
package com.justpickup.customerapigatewayservice.filter;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.justpickup.customerapigatewayservice.security.JwtTokenProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
|
||||
@@ -44,9 +51,7 @@ public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<Auth
|
||||
// JWT 토큰 판별
|
||||
String token = authorizationHeader.replace("Bearer", "");
|
||||
|
||||
if (!jwtTokenProvider.validateJwtToken(token)) {
|
||||
return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
jwtTokenProvider.validateJwtToken(token);
|
||||
|
||||
String subject = jwtTokenProvider.getUserId(token);
|
||||
if (false == jwtTokenProvider.getRoles(token).contains("Customer")) {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.justpickup.customerapigatewayservice.filter;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
|
||||
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
|
||||
|
||||
public GlobalFilter(){
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
return (exchange, chain) -> {
|
||||
ServerHttpRequest request = exchange.getRequest(); // reactive포함된거로 import
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
log.info("Global com.example.scg.filter baseMessgae: {}", config.getBaseMessage());
|
||||
|
||||
// Global pre Filter
|
||||
if (config.isPreLogger()){
|
||||
log.info("Global Filter Start: request id -> {}" , request.getId());
|
||||
log.info("Global Filter Start: request path -> {}" , request.getPath());
|
||||
}
|
||||
|
||||
// Global Post Filter
|
||||
//Mono는 webflux에서 단일값 전송할때 Mono값으로 전송
|
||||
return chain.filter(exchange).then(Mono.fromRunnable(()->{
|
||||
|
||||
if (config.isPostLogger()){
|
||||
log.info("Global Filter End: response statuscode -> {}" , response.getStatusCode());
|
||||
}
|
||||
}));
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Config {
|
||||
private String baseMessage;
|
||||
private boolean preLogger;
|
||||
private boolean postLogger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.justpickup.customerapigatewayservice.handler;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
List<Class<? extends RuntimeException>> jwtExceptions =
|
||||
List.of(SignatureException.class,
|
||||
MalformedJwtException.class,
|
||||
UnsupportedJwtException.class,
|
||||
IllegalArgumentException.class);
|
||||
Class<? extends Throwable> exceptionClass = ex.getClass();
|
||||
|
||||
Map<String, Object> 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!");
|
||||
} else if (jwtExceptions.contains(exceptionClass)){
|
||||
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
responseBody.put("code", "INVALID");
|
||||
responseBody.put("message", "Invalid Access Token");
|
||||
}else{
|
||||
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
responseBody.put("code", "INVALID");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,25 +73,12 @@ public class JwtTokenProvider {
|
||||
return (List<String>) 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,25 @@ spring:
|
||||
|
||||
cloud:
|
||||
gateway:
|
||||
default-filters:
|
||||
- name: GlobalFilter
|
||||
args:
|
||||
baseMessage: Spring Cloud Gateway Global Filter
|
||||
preLogger: true
|
||||
postLogger: true
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowedOrigins: "http://just-pickup.com:8080"
|
||||
allowedMethods:
|
||||
- GET
|
||||
- POST
|
||||
- DELETE
|
||||
- PUT
|
||||
- OPTIONS
|
||||
- DELETE
|
||||
allowedHeaders: '*'
|
||||
allow-credentials: true
|
||||
routes:
|
||||
- id: owner-frontend-service
|
||||
uri: lb://CUSTOMER-FRONTEND-SERVICE
|
||||
@@ -33,6 +52,7 @@ spring:
|
||||
predicates:
|
||||
- Path=/store-service/**
|
||||
filters:
|
||||
- AuthorizationHeaderFilter
|
||||
- RewritePath=/store-service/(?<segment>.*),/$\{segment}
|
||||
- id: user-service
|
||||
uri: lb://USER-SERVICE
|
||||
@@ -55,6 +75,13 @@ spring:
|
||||
- Method=POST
|
||||
filters:
|
||||
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
|
||||
- id: user-service
|
||||
uri: lb://USER-SERVICE
|
||||
predicates:
|
||||
- Path=/user-service/auth/reissue
|
||||
- Method=GET
|
||||
filters:
|
||||
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
|
||||
- id: user-service
|
||||
uri: lb://USER-SERVICE
|
||||
predicates:
|
||||
|
||||
3
customer-vue/.env
Normal file
3
customer-vue/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
VUE_APP_BASEURL=http://just-pickup.com:8080
|
||||
VUE_APP_OWNER_SERVICE_BASEURL=http://just-pickup.com:8001
|
||||
VUE_APP_CUSTOMER_SERVICE_BASEURL=http://just-pickup.com:8000
|
||||
@@ -19,6 +19,8 @@
|
||||
"@vue/cli-plugin-babel": "^5.0.0",
|
||||
"@vue/cli-plugin-eslint": "^5.0.0",
|
||||
"@vue/cli-service": "^5.0.0",
|
||||
"axios": "^0.26.0",
|
||||
"moment": "^2.29.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
|
||||
26
customer-vue/src/api/auth.js
Normal file
26
customer-vue/src/api/auth.js
Normal file
@@ -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(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+"/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(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+"/user-service/auth/check/access-token");
|
||||
}
|
||||
}
|
||||
34
customer-vue/src/api/store.js
Normal file
34
customer-vue/src/api/store.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
getCategoryList(){
|
||||
return axios.get(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+'/store-service/category/');
|
||||
},
|
||||
putCategoryList(data){
|
||||
return axios({
|
||||
method:'put',
|
||||
url:process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+'/store-service/category',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
},
|
||||
data: data,
|
||||
responseType:'json'
|
||||
})
|
||||
},
|
||||
getItemById(itemId){
|
||||
return axios.get(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+'/store-service/item/'+itemId)
|
||||
},
|
||||
saveItem(method, itemData){
|
||||
return axios({
|
||||
method:method,
|
||||
url: process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+'/store-service/item',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
},
|
||||
data: itemData,
|
||||
responseType:'json'
|
||||
})
|
||||
},
|
||||
}
|
||||
32
customer-vue/src/api/user.js
Normal file
32
customer-vue/src/api/user.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import axios from "axios";
|
||||
import jwt from '../common/jwt.js';
|
||||
|
||||
export default {
|
||||
requestRegisterUser(user) {
|
||||
return axios.post(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+"/user-service/store-owner", user);
|
||||
},
|
||||
|
||||
async requestLoginUser(email, password) {
|
||||
const user = {
|
||||
email: email,
|
||||
password: password
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+"/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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
BIN
customer-vue/src/assets/justLogo.png
Normal file
BIN
customer-vue/src/assets/justLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
customer-vue/src/assets/logo_google.png
Normal file
BIN
customer-vue/src/assets/logo_google.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
19
customer-vue/src/assets/logo_naver.svg
Normal file
19
customer-vue/src/assets/logo_naver.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="53" height="52" viewBox="0 0 53 52">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g transform="translate(-794.000000, -2855.000000) translate(166.000000, 2282.000000) translate(0.500000, 381.500000) translate(486.500000, 191.500000) translate(73.500000, 0.000000) translate(68.000000, 0.000000)">
|
||||
<circle cx="26" cy="26" r="26" fill="#1EC800"/>
|
||||
<path fill="#FFF" fill-opacity="0" d="M12.9 13.265H38.9V39.265H12.9z"/>
|
||||
<path fill="#FFF" d="M28.997 17.524L28.997 26.343 22.823 17.524 16.15 17.524 16.15 35.006 22.801 35.006 22.801 26.186 28.976 35.006 35.65 35.006 35.65 17.524z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1000 B |
38
customer-vue/src/common/jwt.js
Normal file
38
customer-vue/src/common/jwt.js
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import vuetify from "@/plugins/vuetify";
|
||||
import router from "./router/router.js";
|
||||
import axios from "axios";
|
||||
import auth from "@/api/auth";
|
||||
|
||||
axios.defaults.withCredentials = true;
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
@@ -10,3 +13,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 = process.env.VUE_APP_BASEURL+"/login";
|
||||
alert("권한이 없습니다. 다시 로그인 해주세요");
|
||||
}
|
||||
}
|
||||
window.location.href = process.env.VUE_APP_BASEURL+"/login";
|
||||
alert("권한이 없습니다. 다시 로그인해주세요.");
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
@@ -2,9 +2,17 @@ import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
|
||||
import HomeLayout from '../views/Layout/HomeLayout.vue';
|
||||
const ACCESS_TOKEN_NAME = "accessToken";
|
||||
const EXPIRED_TIME_NAME = "expiredTime";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const auth = async function (to, from, next) {
|
||||
localStorage.setItem(ACCESS_TOKEN_NAME, to.query.accessToken);
|
||||
localStorage.setItem(EXPIRED_TIME_NAME, to.query.expiredTime)
|
||||
next();
|
||||
};
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
@@ -17,7 +25,26 @@ const routes = [
|
||||
component: () => import('../views/HomeView')
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
redirect: 'login',
|
||||
component: HomeLayout,
|
||||
children: [
|
||||
{
|
||||
path: "/login",
|
||||
name: 'login',
|
||||
component: () => import('../views/LoginPage')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/auth',
|
||||
name: 'auth',
|
||||
beforeEnter: auth,
|
||||
component: () => import('../views/Layout/AuthSuccess.vue')
|
||||
|
||||
},
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
|
||||
20
customer-vue/src/views/Layout/AuthSuccess.vue
Normal file
20
customer-vue/src/views/Layout/AuthSuccess.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<div>인증에 성공하였습니다.</div>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "AuthSuccess",
|
||||
components: {
|
||||
},mounted() {
|
||||
opener.document.location.href=process.env.VUE_APP_BASEURL;
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
120
customer-vue/src/views/LoginPage.vue
Normal file
120
customer-vue/src/views/LoginPage.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<v-container
|
||||
fill-height
|
||||
>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div align="center" ><v-img
|
||||
max-height="150"
|
||||
max-width="250"
|
||||
:src="logo"></v-img></div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
justify="center"
|
||||
>
|
||||
<v-col class="align-content-center">
|
||||
|
||||
|
||||
<v-form ref="form" lazy-validation>
|
||||
<v-text-field
|
||||
:rules="[v => /.+@.+\..+/.test(v) || 'E-mail must be valid', v => !!v || '이메일은 필수 값입니다']"
|
||||
label="이메일"
|
||||
prepend-icon="mdi-account-circle"
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
:rules="[v => !!v || '비밀번호는 필수 값입니다']"
|
||||
label="비밀번호"
|
||||
type="Password"
|
||||
prepend-icon="mdi-lock"
|
||||
append-icon="mdi-eye-off"
|
||||
></v-text-field>
|
||||
<v-btn
|
||||
block
|
||||
>
|
||||
Login
|
||||
</v-btn>
|
||||
<div class="d-block my-7" align="center">
|
||||
<v-subheader class="d-inline" >소셜 아이디로 로그인해보세요!</v-subheader>
|
||||
</div>
|
||||
<div class="d-block " align="center">
|
||||
<v-btn
|
||||
class="mx-2"
|
||||
fab
|
||||
small
|
||||
>
|
||||
<v-img
|
||||
class="d-inline-block align-lg-center mx-5"
|
||||
style=""
|
||||
max-width="38"
|
||||
max-height="38"
|
||||
:src="logo_naver"
|
||||
@click="login_auth('naver')"
|
||||
/>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
class="mx-2"
|
||||
fab
|
||||
small
|
||||
>
|
||||
<v-img
|
||||
class="d-inline-block mx-5"
|
||||
max-width="38"
|
||||
max-height="38"
|
||||
min-width="38"
|
||||
min-height="38"
|
||||
:src="logo_google"
|
||||
@click="login_auth('google')"
|
||||
/>
|
||||
</v-btn>
|
||||
|
||||
</div>
|
||||
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import logo from '@/assets/justLogo.png'
|
||||
import logo_naver from '@/assets/logo_naver.svg'
|
||||
import logo_google from '@/assets/logo_google.png'
|
||||
|
||||
export default {
|
||||
name: "LoginPage",
|
||||
data (){
|
||||
return {
|
||||
logo : logo,
|
||||
logo_naver: logo_naver,
|
||||
logo_google : logo_google,
|
||||
auth_popup: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
auth_popup : function () {
|
||||
this.auth_popup.addEventListener('beforeunload', function() {
|
||||
window.location.href=process.env.VUE_APP_BASEURL;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
login_auth: async function(target) {
|
||||
const _url = process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+'/user-service/oauth2/authorization/'+target
|
||||
this.auth_popup = window.open(
|
||||
_url,
|
||||
"",
|
||||
"width=600,height=400,left=200,top=200"
|
||||
);
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,3 +1,6 @@
|
||||
module.exports = {
|
||||
transpileDependencies: true
|
||||
transpileDependencies: true,
|
||||
devServer:{
|
||||
allowedHosts: 'all',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import reactor.core.publisher.Mono;
|
||||
@Slf4j
|
||||
|
||||
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
|
||||
private static final String TEST_CIRCUIT_BREAKER = "testCircuitBreaker";
|
||||
|
||||
public GlobalFilter(){
|
||||
super(Config.class);
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -75,10 +76,20 @@ public class OAuthService implements OAuth2UserService<OAuth2UserRequest, OAuth2
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Long customerId = customer.getId();
|
||||
return new DefaultOAuth2User(
|
||||
authorities
|
||||
, attributeDto.getAttributes()
|
||||
, attributeDto.getNameAttributeKey());
|
||||
|
||||
}
|
||||
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
String userEmail = String.valueOf(((DefaultOAuth2User) authentication.getPrincipal()).getAttributes().get("email"));
|
||||
|
||||
|
||||
String refreshToken = jwtTokenProvider.createJwtRefreshToken();
|
||||
|
||||
Long customerId = customerRepository.findByEmail(userEmail).get().getId();
|
||||
refreshTokenService.updateRefreshToken(customerId, jwtTokenProvider.getRefreshTokenId(refreshToken));
|
||||
|
||||
// 쿠키 설정
|
||||
@@ -89,13 +100,19 @@ public class OAuthService implements OAuth2UserService<OAuth2UserRequest, OAuth2
|
||||
response.setContentType(APPLICATION_JSON_VALUE);
|
||||
response.addCookie(cookie);
|
||||
|
||||
return new DefaultOAuth2User(
|
||||
authorities
|
||||
, attributeDto.getAttributes()
|
||||
, attributeDto.getNameAttributeKey());
|
||||
// body 설정
|
||||
String accessToken = jwtTokenProvider.createJwtAccessToken(String.valueOf(customerId), request.getRequestURI(), authentication.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toList()));
|
||||
Date expiredTime = jwtTokenProvider.getExpiredTime(accessToken);
|
||||
|
||||
response.sendRedirect("http://just-pickup.com:8080/auth?" +
|
||||
"accessToken="+accessToken+
|
||||
"&expiredTime="+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(expiredTime));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
public Customer saveCustomer(OAuthAttributeDto attributeDto){
|
||||
return customerRepository.save(
|
||||
|
||||
@@ -52,11 +52,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
.deleteCookies("refresh-token");
|
||||
|
||||
http.oauth2Login()
|
||||
.defaultSuccessUrl("http://just-pickup.com:8080/")
|
||||
.userInfoEndpoint()
|
||||
.userService(oAuthService)
|
||||
.and()
|
||||
.failureUrl("http://just-pickup.com:8080/login");
|
||||
.failureUrl("http://just-pickup.com:8080/login")
|
||||
.successHandler(oAuthService::onAuthenticationSuccess);
|
||||
|
||||
http.addFilter(loginAuthenticationFilter);
|
||||
// http.addFilterBefore(new HeaderAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
Reference in New Issue
Block a user