2017-11-10 1 views
0

NodeJS가 모든 비즈니스 로직에서 JSON REST 서비스를 노출하는 앵귤러 4 앱에 의해 소비되는 백엔드 역할을하는 방식으로 애플리케이션을 작성하려고합니다. 바보 같은 클라이언트. 지금까지는 그렇게 좋았지 만 세션 관리를 이해하는 데 어려움을 겪고 있습니다.JSONWebToken을 사용하여 NodeJS와 Angular 간의 세션 관리

토큰 기반 인증은 언젠가는 모바일 앱을 제공 할 수있는 방법이기 때문에 문제가 있습니다. 토큰 만료를 30 분으로 설정하고 서버 측에서 JSONWebToken 전략을 사용하면, 내 클라이언트는 30 분 후에 다시 인증을 받아야합니다. 그러면 클라이언트가 이미 웹 응용 프로그램이 아닌 클라이언트 응용 프로그램에서 작업 중이기 때문에 다시 로그인해야 할 수 있습니다. 공장. 서버에서 토큰이 만료되었지만 바보 클라이언트의 원칙에 위배되거나 자체적으로 NodeJS에서 자체적으로 세션을 구현해야하는 경우 세션 관리를 각도 수준으로 유지하고 자동 로그인해야합니까? 또 다른 것은 WebTokenStrategy를 구현하면 클라이언트에서 오는 모든 요청에 ​​대해 NodeJS에서 세션 관리를하고있는 경우 세션에서 캐시 할 수있는 사용자를 확인하기 위해 데이터베이스로 이동할 것입니다.

마지막으로 알아낼 시간이 없어도 NodeJS에서 리소스를 확보 할 수 있지만 내 클라이언트 응용 프로그램의 사용자 권한에 따라 내 경로와 페이지를 제공해야하며이 정보도 저장해야합니다. NodeJS 데이터베이스에서 동일한 API 서버에 의해 서비스를 제공하지만 이것이 다시 단일 책임 원칙을 위반하거나이 클라이언트 사이트 경로 및 사용자 관리를위한 다른 데이터베이스가 있어야한다고 생각합니다.

누군가가 좋은 접근 방식을 제안 할 수 있습니까?

감사합니다.

답변

0

아니요 JSON 웹 토큰은 페이로드에 원하는 정보를 인코딩하기 때문에 데이터베이스로 이동할 필요가 없습니다. 그러나이를 취소 할 수 있도록하려면 redis 전략을 구현할 수 있습니다 (예 : 올바른 변경의 경우). 서명 부분은 귀하의 서버가 신뢰성을 보증하는 데 사용됩니다 (귀하의 서버 측 JWT 비밀 덕분).

원하는 만료 시간을 선택할 수도 있습니다. 그러나 30 분으로 제한하려면 갱신 전략을 구현할 수도 있습니다. (곧 만료됩니다 이전하기 전에 새로운 토큰을 요청 : 서버가 바로 동일한 데이터 인코딩과 새로운 토큰을 제공 할 프런트 엔드에 대한 전략을 새롭게 당신이 그런 lib에 사용할 수 있습니다.

'use strict'; 
 

 
/** 
 
* Helper class to decode and find JWT expiration. 
 
*/ 
 
class JwtHelper { 
 

 
    urlBase64Decode(str) { 
 
    let output = str.replace(/-/g, '+').replace(/_/g, '/'); 
 
    switch (output.length % 4) { 
 
     case 0: { break; } 
 
     case 2: { output += '=='; break; } 
 
     case 3: { output += '='; break; } 
 
     default: { 
 
     throw 'Illegal base64url string!'; 
 
     } 
 
    } 
 
    return this.b64DecodeUnicode(output); 
 
    } 
 

 
    // credits for decoder goes to https://github.com/atk 
 
    b64decode(str) { 
 
    let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/='; 
 
    let output = ''; 
 

 
    str = String(str).replace(/=+$/, ''); 
 

 
    if (str.length % 4 == 1) { 
 
     throw new Error("'atob' failed: The string to be decoded is not correctly encoded."); 
 
    } 
 

 
    for (
 
     // initialize result and counters 
 
     let bc = 0, bs, buffer, idx = 0; 
 
     // get next character 
 
     buffer = str.charAt(idx++); 
 
     // character found in table? initialize bit storage and add its ascii value; 
 
     ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, 
 
     // and if not first of each 4 characters, 
 
     // convert the first 8 bits to one ascii character 
 
     bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 
 
    ) { 
 
     // try to find character in table (0-63, not found => -1) 
 
     buffer = chars.indexOf(buffer); 
 
    } 
 
    return output; 
 
    } 
 

 
    // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem 
 
    b64DecodeUnicode(str) { 
 
    return decodeURIComponent(Array.prototype.map.call(this.b64decode(str), (c) => { 
 
     return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 
 
    }).join('')); 
 
    } 
 

 
    decodeToken(token) { 
 
    let parts = token.split('.'); 
 

 
    if (parts.length !== 3) { 
 
     throw new Error('JWT must have 3 parts'); 
 
    } 
 

 
    let decoded = this.urlBase64Decode(parts[1]); 
 
    if (!decoded) { 
 
     throw new Error('Cannot decode the token'); 
 
    } 
 

 
    return JSON.parse(decoded); 
 
    } 
 

 
    getTokenExpirationDate(token) { 
 
    let decoded; 
 
    decoded = this.decodeToken(token); 
 

 
    if (!decoded.hasOwnProperty('exp')) { 
 
     return null; 
 
    } 
 

 
    let date = new Date(0); // The 0 here is the key, which sets the date to the epoch 
 
    date.setUTCSeconds(decoded.exp); 
 

 
    return date; 
 
    } 
 

 
    isTokenExpired(token, offsetSeconds) { 
 
    let date = this.getTokenExpirationDate(token); 
 
    offsetSeconds = offsetSeconds || 0; 
 

 
    if (date == null) { 
 
     return false; 
 
    } 
 

 
    // Token expired? 
 
    return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); 
 
    } 
 
} 
 

 
const jwtHelper = new JwtHelper(); 
 

 
const decodedData = jwtHelper.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ'); 
 

 
console.log(decodedData)

+0

그래서 본질적으로 클라이언트 측에서 세션 관리 메커니즘이 필요합니다.이 메커니즘은 사용자 이름과 암호를 캐시하고 서버 측에서 만료되었음을 발견하면 토큰을 호출합니다. 그렇지 않으면 사용자 이름과 암호를 페이로드 그것은 클라이언트 사이트에서 사용자 이름/암호의 캐싱을 막을 것인가? 그 모든 것을 말하면서, ACL 등의 모든 세션 정보를 페이로드 자체로 인코딩해야한다. DB 여행? – zhaider

+0

아니요, 토큰이 만료되기 전에 새 토큰을 요청하십시오. 'isTokenExpired (token, offsetSeconds)'. 토큰에 자격 증명을 저장하지 마십시오. 아무 의미가 없으며 모든 사람이 토큰을 디코딩 할 수 있으므로 엄청난 보안 문제가됩니다. 또한 토큰에 사용 권한을 저장할 수 있습니다. 참조 : https://www.npmjs.com/package/express-jwt-permissions –

+0

답변을 주셔서 감사합니다. 그러나 저는 여전히 약간 혼란 스럽습니다. 문제를 일으켜서 미안 해요. 만료되기 전에 토큰을 재발급 받아야한다는 것을 이해합니다. 그러나 클라이언트 사이트에서 클라이언트 측 세션을 유지하고 만료되기 전에 토큰을 요청합니다. 또한 사용자 자격 증명에서 새 토큰을 생성하지 않고도 토큰을 갱신 할 수 있습니까?둘째, 토큰 관리와 직접 관련이 없지만 서버의 REST 리소스가 아닌 토큰에 클라이언트 측의 페이지 또는 경로 정보를 저장하거나 원 자성의 원칙을 위반하는 정보를 저장할 수 있습니까? – zhaider