Express 영역에서 HTTP Only Cookie 값을 가지고 오는 서비스를 구현하였습니다.
Express 영역
import 영역
import '@angular/localize/init';
import { APP_BASE_HREF } from '@angular/common';
import * as bodyParser from 'body-parser';
import * as express from 'express';
import jwt from 'jsonwebtoken';
import { environment } from '@/environments/environment'
Express 애플리케이션을 생성
export function app() {
const server = express();
// ...
}
Middleware 설정
server.use(cookieParser());
server.use(bodyParser.text());
server.use(bodyParser.urlencoded({ extended: true }));
server.use((req, res, next) => {
res.set('Cache-Control', 'no-store');
next();
});
사용자 로그인 관련하여 로직 처리
/**
* 로그인 처리
*/
server.get('/get-userinfo', async (req, res) => {
const headers = Object.keys(req.headers).reduce((acc, cur) => {
if (excludeKey.indexOf(cur) > -1) return acc;
return {...acc, [cur]: req.headers[cur]};
}, {});
const YBSSTK = req.cookies?._YBSSTK || null;
try {
const decoded = jwt.verify(YBSSTK, environment.ENV_ACCOUNT_JWT_KEY);
/** 토큰 검증 정상일 경우 시간 체크 */
const expried = decoded.exp;
/** 현재시간 Unix 타임 변환 */
const now = Math.floor(Date.now() / 1000);
/** 시간 계산 - 분 */
const timeDiff = Math.floor((expried - now) / 60);
/** 토큰 시간 30분 초과 */
if (timeDiff > 30) {
const userInfoOptions = {
method: 'POST',
baseURL: ota_url,
headers: {
...headers,
userToken: YBSSTK,
},
url: '/user/get-info',
data: JSON.stringify({
language: 'KO',
condition: {stationTypeCode: 'STN01'},
}),
};
const signinUserRes = await axios(userInfoOptions);
res.status(signinUserRes.status).json(signinUserRes.data);
res.end();
return;
} else {
if (YBSSTK.indexOf(environment.adminAuthCookie) > -1) {
res.clearCookie('_YBSSTK');
return;
}
}
const options = {
method: 'PUT',
baseURL: account_url,
headers: {...headers, userToken: YBSSTK},
url: '/api/signin/token', // 로그인 토큰 유효성 체크 API
};
/** 세션 토큰 업데이트 */
const signinTokenRes = await axios(options);
if (signinTokenRes.data.code !== '0000') throw new Error('TokenUpdateFail');
const {_YBSSTK} = cookie.parse(signinTokenRes.headers['set-cookie'].at(0));
res.setHeader(
'Set-Cookie',
cookie.serialize('_YBSSTK', _YBSSTK, {
httpOnly: true,
path: '/',
domain: req.headers.host.match(/\\..+/)[0],
secure: true,
sameSite: 'Lax',
})
);
const userInfoOptions = {
method: 'POST',
baseURL: ota_url,
headers: {...headers, userToken: YBSSTK},
url: '/user/get-info', // 로그인 토큰 유효성 체크 API
data: JSON.stringify({
language: 'KO',
condition: {stationTypeCode: 'STN01'},
}),
};
const userInfoRes = await axios(userInfoOptions);
res.status(signinTokenRes.status).json(userInfoRes.data);
res.end();
} catch (error) {
const json = {
code: '9999',
succeedYn: false,
message: error.message,
body: {
description: `${error.name}: ${error.message}`,
},
};
switch (error.name) {
case 'TokenUpdateFail':
json.message = '토큰 업데이트 실패';
break;
case 'UserinquiryFail':
json.message = '사용자 정보 조회 실패';
break;
default:
}
res.status(200);
res.json(json);
res.end();
}
});
서버 설정 및 시작
function run() {
const port = process.env.PORT || environment.serverPort;
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on <http://localhost>:${port}`);
});
}
run();
RestCall 공통 서비스 영역
import {HttpClient, HttpErrorResponse, HttpHeaders, HttpParams} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable, throwError} from 'rxjs';
import {catchError, timeout} from 'rxjs/operators';
import {environment} from '@/environments/environment';
import {RequestParam} from '@/app/common-source/models/common/condition.model';
@Injectable({
providedIn: 'root',
})
export class RestCallService {
private baseUrl: string;
private nethruUrl: string;
private httpOptions: any;
private accountUrl: string;
constructor(private readonly http: HttpClient) {
this.initialize();
}
private initialize() {
this.baseUrl = environment.API_URL;
this.nethruUrl = environment.NETHRU_URL;
this.accountUrl = environment.ACCOUNT_URL;
this.httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json', // You can set other headers here
}),
withCredentials: true,
};
}
/**
* handleError
* console에 api 에러 로그 남기기
*/
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
console.error('통신 에러:', error.error.message);
} else {
console.error(`서버 리턴 코드 ${error.status}, ` + `에러 내용: ${error.error}`);
}
return throwError('서버 통신 에러 발생하였습니다. 잠시 후 다시 시도하세요');
}
public httpGet(path: string, data: RequestParam, isInnerAPI?: boolean): Observable<any> {
return this.http
.get(isInnerAPI === undefined ? path : this.baseUrl + path, {...this.httpOptions, param: data})
.pipe(timeout(Number(environment.timeOut)), catchError(this.handleError));
}
public httpPost(path: string, data?: RequestParam, opt?: any, timeOut?: number): Observable<any> {
const url: string = this.baseUrl + path;
console.log('httpPost', url);
const body: RequestParam = _.cloneDeep(data) || undefined;
if (body) {
body.condition = _.omit(body.condition, ['_gac', '_ga']);
}
const options: any = {...this.httpOptions, ...opt};
return this.http.post(url, body, options).pipe(timeout(Number(timeOut || environment.timeOut)), catchError(this.handleError));
}
public httpPut(path: string, data: RequestParam, opt?: any): Observable<any> {
const url: string = this.baseUrl + path;
const body: RequestParam = _.cloneDeep(data) || undefined;
if (body) {
body.condition = _.omit(body.condition, ['_gac', '_ga']);
}
const options: any = {...this.httpOptions, ...opt};
return this.http.put(url, body, options).pipe(timeout(Number(environment.timeOut)), catchError(this.handleError));
}
public httpDelete(path: string, data: RequestParam): Observable<any> {
const url: string = this.baseUrl + path + qs.stringify(data);
return this.http.delete(url).pipe(timeout(Number(environment.timeOut)), catchError(this.handleError));
}
}
API URL 영역
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {RestCallService} from '../rest-call/rest-call.service';
import {RequestParam} from '@/app/common-source/models/common/condition.model';
@Injectable({
providedIn: 'root',
})
export class ApiUserService {
constructor(private readonly restCallS: RestCallService) {}
public GET_SSR_USERINFO(body?: RequestParam): Observable<any> {
return this.restCallS.httpGet('/get-userinfo', body);
}
}
Service 영역
...
public getSSRUserInfo(): Observable<boolean> {
return this.apiUserS.GET_SSR_USERINFO().pipe(
map((res: any) => {
if (res && res.succeedYn) {
console.log('tokenGetter api : ', res);
this.updateUserInfo(res);
return true;
} else {
this.clearToken();
return false;
}
}),
catchError((err: any) => {
console.log('Get SSRLoginToken Failed', err.errorMessage);
return of(false); // Emitting false value in case of error
})
);
}