728x90
목차
030
인증 방식의 종류
Cookie
- Key - Value 형식의 문자열 덩어리이다.
- 클라이언트가 웹 사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 작은 기록 정보 파일이다.
- 각 사용자마다의 브라우저에 정보를 저장하니 고유 정보 식별이 가능하다.
Cookie 인증방식
- 클라이언트가 서버에 요청을 보낸다.
- 서버는 클라이언트의 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 Set-Cookie에 담는다.
- 이후 해당 클라이언트는 요청을 보낼 때마다, 매번 저장된 쿠키를 요청 헤더의 cookie에 담아 보낸다. 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별하거나 정보를 바탕으로 추천광고를 띄우거나 한다.
Cookie의 단점
- 보안에 취약하다.
- 요청 시 쿠키의 값을 그대로 보내기 때문에 유출 및 조작 당할 위험이 존재한다.
- 용량 제한이 있어, 많은 정보를 담을 수 없다.
- 웹 브라우저마다 쿠키에 대한 지원 형태가 다르기 때문에 브라우저간 공유가 불가능하다.
- 쿠키의 사이즈가 커질수록 네트워크에 부하가 심해진다.
Sesseion
- 쿠키의 보안이슈 때문에, 세션은 클라이언트의 민감한 인증정보(ex.비밀번호 등)를 브라우저가 아닌, 서버 측에 저장하고 관리한다.
- 서버의 메모리에 저장하기도 하고, 서버의 로컬 파일이나 데이터베이스에 저장하기도 한다.
Session 인증방식
- 유저가 웹 사이트에서 로그인하면 세션이 서버메모리(또는 데이터베이스)상에 저장된다.
- 이때, 세션을 식별하기 위한 Session id를 기준으로 정보를 저장한다.
- 서버에서 브라우저에 쿠키에다가 Session id를 저장한다.
- 쿠키에 정보가 담겨있기 때문에 브라우저는 해당 사이트에 대한 모든 Request에 Session id를 쿠키에 담아 전송한다.
- 서버는 클라이언트가 보낸 Session id와 서버 메모리로 관리하고 있는 Session id를 비교하여 인증을 수행한다.
Session의 단점
- 세션 id 자체에 유의미한 민감한 정보를 가지고 있진 않지만, 해커가 세션 id 자체를 탈취해 클라이언트인척 위장할 수 있다는 한계가 존재한다.
- 서버에서 ip특정을 통해 해결할 수 있다.
- 서버에서 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해진다.
Token
- 토큰 기반 인증 시스템은 클라이언트가 서버에 접속하면 서버에서 해당 클라이언트에게 인증되었단는 의미로 '토큰'을 부여한다.
- 이 토큰을 유일하며, 토큰을 발급받은 클라이언트는 또 다시 서버에 요청을 보낼 때 요청 헤더에 토큰을 심어서 보낸다.
- 서버에서는 클라이언트로부터 받은 토큰을 서버에서 제공한 토큰과 일치여부를 체크하여 인증과정을 처리하게된다.
- 토큰은 세션과 달리 서버가 아닌 클라이언트에 저장되기 때문에 메모리나 스토리지 등을 통해 세션을 관리했던 서버의 부담을 덜 수 있다.
- 세션 기반의 경우, 서버가 파일이나 데이터베이스에 세션정보를 가지고 있어야히고 이를 조회하는 과정이 필요하기 때문에 많은 오버헤드가 발생한다.
- 토큰 자체에 데이터가 들어있기 때문에 클라이언트에서 받아 위조되었는지 판별만 하면 된다.
Token 인증방식
- 사용자가 아이디와 비밀번호로 로그인을 한다.
- 서버 측에서 사용자(클라이언트)에게 유일한 토큰을 발급한다.
- 클라이언트는 서버 측에서 전달받은 토큰을 쿠키나 스토리지에 저장해두고, 서버에 요청할 때마다 해당 토큰을 HTTP 요청 헤더에 포함시켜 전달한다.
- 서버는 전달받은 토큰을 검증하고 요청에 응답한다. 토큰에는 요청한 정보가 담겨있기에 서버는 DB를 조회하지 않고 누가 요청하는지 알 수 있다.
Token의 단점
- 쿠키/세션과 다르게 토큰 자체의 데이터 길이가 길어, 인증요청이 많아질수록 네트워크 부하가 심해질 수 있다.
- Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.
- 토큰을 탈취당하면 대처하기 어렵다.(따라서 사용 기간 제한을 설정하는 식으로 극복한다.)
JWT 토큰
- JWT : JSON Web Token
- 인증에 필요한 정보들을 암호화 시킨 JSON 토큰을 의미한다.
- JWT 기반 인증은 JWT토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별하는 방식이다.
- 토큰 내부에는 위변조 방지를 위해 개인키를 통한 전자서명이 들어있다.
- 따라서, 사용자가 JWT를 서버로 전송하면 서버는 서명을 검증하는 과정을 거치게 되며 검증이 완료되면 요청한 응답을 돌려준다.
- 유저가 로그인을 요청하면 서버에서 유저의 정보를 가지고 정상적인 루트로 로그인을 요청한 유저면 토큰을 발급해서 전달해준다.
- 유저가 서버에 요청을 할 때, JWT 토큰을 포함해서 요청을 하면 서버가 요청을 받고 토큰이 썩은 토큰인지 검사를 해서 착한 토큰이면 요청한 작업을 처리해주고 응답해준다.
- 따라서, 사용자가 JWT를 서버로 전송하면 서버는 서명을 검증하는 과정을 거치게 되며 검증이 완료되면 요청한 응답을 돌려준다.
- 웹 표준으로 두 객체의 JSON 객체를 사용해서 정보를 안정성 있게 전달해준다.
- 사용할 정보를 자체적으로 가지고 있다.
- JWT로 발급한 토큰은 기본정보(유저의 정보 프로필)와 토큰이 정상인지 검증(전자서명.Signature)을 포함하고 있다.
- 주로 로그인이 정상적인지 회원 인증 권한에서 사용한다.
- JWT는 주로 로그인이 정상적인지 회원 인증 권한에서 사용한다.
- JWT를 사용하는 이유: 안정성 있게 정보를 전달해서 요청할 수 있다.
- JWT를 생성하면 사용할 모듈이 인코딩과 해싱 작업을 해준다
- HMAC: 해싱 시법을 적용해서 메시지의 위.변조를 방지하는 기법
- SHA256: 임의의 길이 메시지를 256 비트의 축약된 메시지로 만들어내는 해시 알고리즘
JWT 구조
- ' . ' 구분자로 나누어지는 세 가지 문자열의 조합이다.
- ' . ' 을 기준으로 Header, Payload, Signature를 의미한다.
Header
- 타입과 알고리즘의 정보를 가지고 있다.
let header = {
// 사용하는 해싱 알고리즘
alg : "SHA256",
// 토큰의 타입
type : "JWT"
}
- alg: 서명 암호화 알고리즘
- ex. HMAC, SHA256, RSA
- type: 토큰 유형
Payload
- 유저의 정보와 만료기간이 포함된 객체를 가지고 있다.
- 서버와 클라이언트가 주고 받는 시스템에서 실제로 사용될 정보에 대한 내용을 담고 있는 섹션이다.
let payload = {
// 토큰의 이름 제목
sub : "2342",
// 유저의 이름 (유저 프로필)
name: "affsfge",
// 토큰이 발급된 시간. 발급된지 얼마나 지났는지
lat: "1433"
}
- 정해진 타입은 없지만, 대표적으로 Registered claims, Public claims, Private claims 세 가지로 나뉜다.
- Registered claims: 미리 정의된 클레임이다.
- iss(issuer): 발행자
- exp(expireation time): 만료시간
- sub(subject): 제목
- iat(issued At): 발행시간
- jti: JWI ID
- Public claims: 사용자가 정의할 수 있는 클레임 공개용 정보 전달을 위해 사용
- Private claims: 해당하는 당사자들 간에 정보를 공유하기 위해 만들어진 사용자 지정 클레임
- 외부에 공개되도 상관없지만 해당 유저를 특정할 수 있는 정보들을 담는다.
- Registered claims: 미리 정의된 클레임이다.
Signature
- header, payload를 인코딩하고 합쳐서 해싱해 비밀키로 만든다.
- 시그니처의 구조
- 헤더, 페이로드와 서버가 갖고 있는 유일한 key 값을 합친 것을 헤더에서 정의한 알고리즘으로 암호화한다.
- Signature = Header(Base64Url) + . + Payload(Base64Url) + server's key
// 비밀키 생성
let signature = HMACSHA256(BASE64URL(header) + BASE64URL(payload));
JWT.sign( )
- 토큰을 생성할 수 있는 메소드이다.
- sign()메소드 구조
- jwt.sign(payload, secret Key, [ options, callback ])
- payload: 아이디, 비밀번호 등 사용자 정보가 들어간 object이다.
- secret Key: 여러가지 복잡한 문자열로 되어있는 키이다.
- options: 토큰에 대한 여러가지 정보를 설정한다.
- expiresIn: 토큰을 유지시킬 유효시간, 만료시간
- issuer: 토큰을 발급한 사람
- 등의 정보가 들어간다.
- callback: 토큰의 생성결과를 4번째 인자의 콜백함수로 받을 수 있으므로 넣어주는 함수이다.
- jwt.sign(payload, secret Key, [ options, callback ])
JWT.verify( )
- 토큰의 유효성을 검사할 수 있는 메소드이다.
- verify( )메소드 구조
- jwt.verify(token, key, callback)
- token: 클라이언트에게서 받은 토큰
- key: 토큰 생성 시 사용했던 secret Key
- callback: 유효성 검사를 처리할 callback함수
- err: 에러 내용 객체
- decided: 해석된 객체
- jwt.verify(token, key, callback)
jwt.verify(token, key, (err, decoded)=>{
// 유효하지 않은 토큰
if(err){
console.log("썩은 토큰");
res.send("토큰이 썩었거나, 변조된 것이다.");
}
// 해석된 객체. 유효한 토큰
else{
console.log(decoded);
res.send(decoded);
}
})
dotenv
- 환경변수를 .env 파일에 저장하고 process.env로 로드하는 의존성 모듈이다.
- 우리가 개발을 하는 과정에서 서버주소, 고유 API KEY값 등 필요한 정보들을 저장하게 된다. 이러한 정보들은 민감한 정보에 해당하여 보안이 이루어져야 하는데, .env에 담아두면 보안을 유지할 수 있게된다.
- 민감한 정보들이 오픈소스(깃허브 등)에 공개될 경우 해킹을 당하거나 보안적인 면에서 위험할 수 있다.
- 따라서 dotenv를 이용하여 환경변수 파일을 외부에 만들어 접근할 수 있게 하므로써 보안을 유지할 수 있는 것이다.
dotenv 사용법
dotenv 설치
터미널에서 npm 으로 dotenv를 설치한다.
PS D:\test> npm i dotenv
dotenv 경로지정
- env를 사용하고자 하는 파일안에 dotenv를 부른다.
- config( ): 현재 디렉토리의 ' .env '파일을 자동으로 인식하여 환경변수를 세팅한다.
- config( 경로 ): 원하는 ' .env '파일의 위치를 직접 지정하여 세팅할 수 있다.
const dot = require("dotenv").config();
dotenv 작성
- .env 파일을 루트경로에 생성한 후 안에 들어갈 텍스트는 반드시 "Key = Value"의 형식으로 적어준다.
KEY = "qwer1234"
dotenv 사용
- ' process.env.XXX ' 의 형태로 작성한다.
const KEY = process.env.KEY;
Token 발행하기_JWT / dotenv 활용
#. 폴더의 경로는 다음과 같다.
1. app.js_필요한 모듈을 설치하고 불러온다. (express, path, jwt, dotenv, ejs)
- 필요한 모듈을 설치한다.
PS D:\test> npm i express jsonwebtoken dotenv ejs
- 사용할 모듈을 불러온다.
const express = require("express");
const path = require("path");
// JWT 모듈 가져오기
const jwt = require("jsonwebtoken");
// dotenv 모듈 가져오기. 가져오면서 config 메소드 실행
// config의 매개변수가 비어있으니, 현재 디렉토리의 ' .env '파일을 읽어온다.
const dot = require("dotenv").config();
2. .env_키를 생성한다.
- ' .env ' 파일을 생성하여 해당 파일에 사용할 키와 키 값을 작성한다.
KEY = "mykey"
3. app.js_서버 인스턴스를 생성, views 파일경로 설정, view엔진 ejs파일 설정 등 설정
- 서버 인스턴스를 생성한다.
const app = express();
- views 파일 경로 설정
app.set("views", path.join(__dirname, "page"));
- view엔진 ejs파일 설정
app.set("view engine", "ejs");
- body객체를 사용할 수 있도록 설정한다.
app.use(express.urlencoded({extended : false}));
4. main.ejs / app.js_메인페이지를 생성하고, app.js에서 render후 post된 값을 받아온다.
- main.ejs_main페이지를 생성한다.
<body>
<h1>로그인 페이지</h1>
<form action="/login" method="post">
<input type="text" name="id" placeholder="아이디">
<input type="text" name="id" placeholder="비밀번호">
<button>로그인</button>
</form>
</body>
- app.js_get메소드를 이용하여 페이지를 렌더하고 post메소드를 이용하여 post된 값을 받아온다.
app.get("/",(req,res)=>{
res.render("main");
})
app.post('/login', (req,res)=>{
// 로그인을 정상적으로 했다 가정하고 토큰을 발급한다.
// 유저 정보는 객체로 만들어주자.
const name = "user1";
const KEY = process.env.KEY;
// sign()메소드로 JWT 토큰을 생성
let token = jwt.sign({
// 타입은 JWT
type : "JWT",
// 유저 이름
name : name
}, KEY, {
// 토큰을 유지시킬 유효시간, 만료시간.
expiresIn : "5m", // 5분 유지시킬 토큰
// 토큰 발급한 사람
issuer : "user1"
})
res.send(JSON.stringify(token));
// 결과: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiSldUIiwibmFtZSI6InVzZXIxIiwiaWF0IjoxNjgzODU4NTcwLCJleHAiOjE2ODM4NTg4NzAsImlzcyI6InVzZXIxIn0.ygd2xoVm2kX_y5ikXxAfUIupDsYa7MH7a5T_oPJePJQ"
// 점( . )을 기준으로 나뉜다.
// header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
// payload: eyJ0eXBlIjoiSldUIiwibmFtZSI6InVzZXIxIiwiaWF0IjoxNjgzODU4NTcwLCJleHAiOjE2ODM4NTg4NzAsImlzcyI6InVzZXIxIn0
// 서명(signature): ygd2xoVm2kX_y5ikXxAfUIupDsYa7MH7a5T_oPJePJQ
})
5. app.js_서버를 대기한다.
app.listen (8080,()=>{
console.log("서버가 잘 열렸어요!")
})
express - session
- 세션관리용 미들웨어
- 로그인 등의 이유 세션을 구현하거나, 특정 사용자를 위한 데이터를 임시적으로 저장해둘 때 매우 유용하다.
- 세션은 사용자별로 req.session 객체 안에 유지된다.
app.use(session({
secure: ture, // https 환경에서만 session 정보를 주고받도록처리
secret: process.env.COOKIE_SECRET, // 암호화하는 데 쓰일 키, 세션을 발급할 때 사용할 키.
resave: false, // 세션을 언제나 저장할지 설정함, 세션이 변경되거나 저장할 때나 불러올 때 다시 저장할지 여부를 결정
saveUninitialized: true, // 세션을 저장할 때 초기화할지 여부를 결정
cookie: { //세션 쿠키 설정 (세션 관리 시 클라이언트에 보내는 쿠키)
httpOnly: true, // 자바스크립트를 통해 세션 쿠키를 사용할 수 없도록 함
Secure: true
},
name: 'session-cookie' // 세션 쿠키명 디폴트값은 connect.sid이지만 다른 이름을 줄수도 있다.
}));
Token 발행 및 회원의 토큰정보 확인_JWT / express-session / dotenv
- JWT와 dotenv를 활용하여 Token을 발행하고 express-session을 활용하여 세션에 해당 토큰을 저장해 유효성을 검사한다.
#. 폴더의 경로는 다음과 같다.
1. app.js_필요한 모듈을 설치하고 불러온다. (express, path, jwt, dotenv, ejs, express-session)
- 필요한 모듈을 설치한다.
PS D:\test> npm i express jsonwebtoken dotenv ejs express-session
- 사용할 모듈을 불러온다.
const express = require("express");
const path = require("path");
const session = require("express-session");
2. app.js_서버 인스턴스를 생성, views 경로 설정, view엔진 ejs사용, body객체 사용을 설정한다.
- 서버 인스턴스 생성
const app = express();
- views경로 설정 및 view엔진 ejs 사용 설정
app.set("views", path.join(__dirname,"page"));
app.set("view engine", "ejs");
- body객체 사용
app.use(express.urlencoded({ extended : false }));
3. .env_ 보안을 위한 키 값을 생성한다.
- ' .env ' 파일을 생성하여 해당 파일에 사용할 키와 키 값을 작성한다.
KEY = "mykey"
KEY2 = "mykey2"
4. app.js_session 데이터를 메모리에 저장한다.
- session 데이터를 메모리에 저장한다.
app.use(session({
// 세션을 발급할 때 사용할 키. 이것도 나중에는 소스코드에 노출 안되게 바꿔놓자.
secret : process.env.KEY2,
// 세션이 변경되거나 저장할 때나 불러올 때 다시 저장할지 여부를 결정
resave : false,
// 세션을 저장할 때 초기화할지 여부를 결정
saveUninitialized : true,
name : "session-cookie"
}));
- 해당 방식은 session 정보를 디스크나 DB에 저장하는 것보다 훨씬 빠른 반응속도를 보인다는 장점이 있지만, 그만큼 메모리에 적제되는 session양이 많아졌을 때는 부하가 심한 방식이다.
- 따라서, 메모리에 적제되는 양을 조절하거나, 세션이 유지되는 기간을 조절하는것이 중요하다.
5. page.js_루트경로의 페이지 render를 설정한다.
- 사용할 모듈을 불러온 후, 루트경로에서 render될 페이지를 설정한다.
const express = require("express");
const router = express.Router();
router.get("/", (req,res)=>{
res.render("page");
})
module.exports = router;
6. Token.js_Token을 생성한다.
- 토큰 생성에 필요한 모듈을 불러온다.
// 익스프레스 받아서 실행하고 익스프레스 안에 있는 라우터 객체 실행
const router = require("express").Router();
const dot = require("dotenv").config();
const jwt = require('jsonwebtoken');
- '/login'페이지에서 post로 값을 받아 Token을 생성한다.
router.post("/login", (req,res)=>{
const name = "mr.hong";
const key = process.env.KEY;
//토큰을 생성하여 'token'변수에 담는다.
let token = jwt.sign({
type : "JWT",
name : name,
}, key, {
// 토큰의 유효시간
expiresIn : "3m",
// 토큰 발급자
issuer : name
} )
// 요청받은 세션의 토큰에는 token이 담긴다.
req.session.token = token;
res.render("page2");
})
7. page.ejs / page2.ejs_동작을 실행할 수 있는 페이지를 생성한다.
- 버튼을 클릭하면 사용자가 로그인을 진행하여, 토큰을 생성한다. 생성한 토큰은 세션에 저장된다.
<body>
<h1>첫 로그인 페이지</h1>
<form action="/login" method="post">
<button>사용자 로그인</button>
</form>
</body>
- 버튼을 클릭하면 토큰의 유효성 검사를 처리하고, 에러를 띄우거나(썩은 토큰일 경우) 복호화된 토큰(유효한 토큰)을 보여준다.
<body>
<form action="/userVerify" method="post">
<button>사용자 토큰 정보</button>
</form>
</body>
8. verify.js_Token의 유효성을 검사하고 복호화된 토큰을 노출한다.
- 유효성 검사를 위해 필요한 모듈을 불러온다.
- 루트경로에서 post받은 세션 토큰의 유효성을 검사한다.
- 토큰이 시간이 지나 썩거나, 위변조가 되었을 경우 err로 넘어간다.
- 유효한 토큰일 경우 해석된 객체가 나타난다.
router.post('/', (req,res)=>{
const token = req.session.token;
const key = process.env.KEY;
// 토큰이 유효한지 검증
jwt.verify(token, key, (err, decoded)=>{
if(err){
console.log("썩은 토큰");
res.send("토큰이 썩었거나, 변조된 것이다.");
}
else{
// 해석된 객체
console.log(decoded);
res.send(decoded);
}
})
})
9. app.js_라우터 설정을 한 후, 서버를 대기 시킨다.
- 라우터 설정을 불러온다.
const pageRouter = require("./routers/page");
const tokenRouter = require("./routers/token");
const verifyRouter = require("./routers/verify");
app.use(pageRouter);
app.use(tokenRouter);
app.use("/userVerify", verifyRouter);
- 서버를 대기시킨다.
app.listen(8000, ()=>{
console.log("JWT 서버 잘 열림!")
})
결과_토큰이 썩었을 경우
결과_토큰이 유효할 경우
더보기
- JWT
- dotenv
- express-session
728x90
'블록체인_9기 > 💚 Node.js' 카테고리의 다른 글
32강_230516_Node.js(암호화, Hash 암호화, crypto, 해시화, salt, 키 스트레칭 기법, Bcrypt) (2) | 2023.05.19 |
---|---|
31강_230515_Node.js(Refresh token, Access token, cookie-parser) (0) | 2023.05.17 |
29강(과제X)_230508_Node.js(MVC 패턴으로 게시판 페이지 제작) (1) | 2023.05.09 |
28강_Node.js (express, ejs, mysql2, path로 게시판 만들기) (0) | 2023.05.08 |
27강(과제X)_230502_Nodejs(express, get / post, path, ejs) (0) | 2023.05.03 |