-
2019-09-19 개발일지개발일지 2019. 9. 19. 17:37
오늘은 이슈 수정과 개인 공부로 passport를 통한 로그인 기능을 TS에 맞게 작업하면서 공부해봤다.
[이슈 리스트]
- 1:1 문의 목록 API 중 응답 객체에 답변 여부 판단을 할 수 있는 값이 누락됨
①
해당 내용은 내가 응답 객체에 빼먹어서 발생했다.
exports.list = async (req, res) => { try { ... const response = []; model.rows.forEach(row => { response.push({ ... answer: row.getDataValue('answer') === null ? false : true }); }); } catch (e) { ... } }
답변을 체크할 수 있는 속성을 하나 만들어서 boolean 타입으로 작성해줬다.
passport 로컬 전략 공부 내용이다.
로그인 기능을 만드는데 발생한 에러를 해결하느라 오늘 대략 2시간을 썼다.
다음과 같이 패스포트 로컬 전략 구성과 serializeUser(), deserializeUser()를 정의했다.
passport.ts
import User from '/models/userModel'; import passport = require('passport'); export default (passport: passport.PassportStatic) => { passport.serializeUser((user: User, done) => { done(null, user.id); }); passport.deserializeUser(async (id: number, done) => { try { const findOption = { where: { id } }; const user = await User.findOne(findOption); if (user) { return done(null, user); } } catch (e) { console.error(e); return done(e); } }); };
local-strategy.ts
import User from 'models/userModel'; import { Strategy as LocalStrategy } from 'passport-local'; import CustomError from 'error/customError'; import { PassportStatic } from 'passport'; export default (passport: PassportStatic) => { passport.use(new LocalStrategy(async (username: string, password: string, done) => { try { if (!username || username.length <= 0 || !password || password.length <= 0) { throw new CustomError(8); } const findOption = { where: { userId: username, userPw: password } }; const user = await User.findOne(findOption); if (user) { return done(null, user); } return done(null, false); } catch (e) { console.error(e); return done(e); } }); };
express_config.ts
... import express from 'express'; import expressSession from 'express-session'; import passport from 'passport'; import myPassport from './passport'; import myLocalStrategy from './local-strategy'; import authRoute from 'route/AuthRoute.ts'; class App { ... constructor() { this.app = express(); ... // body-parser를 설정하지 않으면 // passport가 req객체의 body속성에서 username과 password를 가져올 수 없으니 주의하자. this.app.use(express.urlencoded({ extended: true }); this.app.use(express.json()); // express-session을 설정해야 인증 후 로그인 세션을 컨트롤할 수 있다. this.app.use(expressSession({ secret: 'secret', name: 'name', resave: true, saveUninitailized: true })); // passport 초기화 및 세션 활성화 this.app.use(passport.initialize()); this.app.use(passport.session()); // 패스포트 모듈을 넘기고 로컬 전략 설정과 패스포트 설정을 호출 myLocalStrategy(passport); myPassport(passport); authRoute(this.app, passport); } }
body-parser를 설정하지 않으면 passport가 정상적으로 동작하지 않으니 주의하자.
authRoute.ts
import express from 'express'; import { PassportStatic } from 'passport'; export default (app: exprss.Application, passport: PassportStatic) => { app.post('/login', passport.authenticate('local', { successRedirect: '/', failureRedirect: '/err' }); };
http 통신 테스트를 해보자.
여기서 난 RESTClient를 사용했다.
여기서 문제가 있었다.
post http://localhost:5000/login http/1.1 Content-Type: application/json { "user": { "username": "hong", "password": "12341234" } } ###
위의 형식으로 로그인 요청을 보냈고 라우터까지 도달하였다.
이후 authenticate()가 실행이 되고 passport.use()까지 처리되는데 new LocalStrategy()가 처리되지 않고 failure가 발생했다.
왜 안되는거지 하면서 찾아보고 계속 고민하다가 예전에 작성했던 글에서 로그인 흐름도를 보고 원인이 무엇일지 파악했다.
참고: https://jamong-icetea.tistory.com/146?category=812194
LocalStrategy를 초기화할 때 초기화가 안되서 failure가 발생한거다.
여기까지 파악이 되니 이후는 술술 생각이 났다.
@types/passport-local 패키지의 index.d.ts를 보자.
Strategy의 생성자는 총 3개가 있고 options와 verify를 인자로 받는다.
옵션을 보면 별 거 없다.
- username 필드의 이름을 재정의할 수 있는 속성
- password 필드의 이름을 재정의할 수 있는 속성
- 세션 사용 속성
- 요청에 대한 콜백 연결 속성
4가지가 있다.
요건 그냥 요런게 있다 한번 본거고 문제의 핵심은 VerifyFunction이다.
얘는 2번째 인자로 들어가는 익명함수다.
보면 username 속성과 password, done을 인자로 받는다.
string 타입의 username 속성이나 password 속성이 만약 undefined라면?
그렇다.
Strategy를 초기화하는 과정에서 initializing error가 발생한거다.
(근데 왜 에러 메시지 안띄우고 /err 페이지를 띄우냐 ㅡㅡ)
고럼 이제 여기서 우리는 다음과 같은 생각을 할 수 있다.
"왜 username과 password가 undefined인가?"
다음 흐름도를 보자.
위는 요청 데이터를 request 객체에 파싱해주는 body-parser의 작업 흐름이다.
- 사용자는 컴퓨터에서 브라우저를 실행한다.
- 브라우저를 통해 데이터를 입력하고 서버에게 요청을 발송한다.
- 요청에는 사용자가 입력한 데이터가 포함되어 있다.
- 서버가 클라이언트의 요청을 받는다.
- body-parser가 express.use(express.json()) 미들웨어 실행 구문으로 인해 작동한다.
- 요청 데이터를 request 객체의 body에 파싱해준다.
- 파싱된 request를 서버에서 사용할 수 있게 된다.
그리고 이제 passport가 body-parser가 파싱해놓은 request를 뒤적거린다.
그렇다.
바로 body안에서 username과 password를 찾는다. (이게 로그인 데이터니까...)
오케 여기까지 흐름을 이해했으면 이제 원인을 찾을 수 있다.
아까 위에서 작성한 HTTP API 테스트 코드를 보자.
post http://localhost:5000/login http/1.1 Content-Type: application/json { "user": { "username": "hong", "password": "12341234" } } ###
무엇이 문제인지 보이는가?
만약 무엇이 문제인지 보이지 않는다면 위의 전송 데이터를 body-parser가 어떻게 처리할 지 예상해보면 된다.
요청 데이터는 "user"라는 객체안에 username과 password가 정의되어 있다.
그러니 아주 꼼꼼한 성격의 body-parser는 "user"를 빼먹지 않고 그대로 request의 body안에 파싱해준다.
그런데 passport는 body안에서 username과 password를 찾으니 찾지를 못하는 것 이었다.
user 안에 정의된 username과 password는 스캔 범위의 밖이다.
그래서 LocalStrategy() 객체를 초기화하지 못하고 failure가 발생한 것이다.
(초기화에 대한 에러 스택만 트레이싱해줬어도 내가 이렇게까지 고생은 안했다...)
결국 아래와 같이 HTTP API 테스트 코드를 수정하고 요청을 보내니 정상적으로 처리되더라.
post http://localhost:5000/login http/1.1 Content-Type: application/json { "username": "hong", "password": "12341234" } ###
계속 정상이었던 코드만 쳐다봤다.
'개발일지' 카테고리의 다른 글
2019-09-23 개발일지 (0) 2019.09.23 2019-09-20 개발일지 (0) 2019.09.20 2019-09-18 개발일지 (0) 2019.09.18 2019-09-17 개발일지 (0) 2019.09.17 2019-09-16 개발일지 (0) 2019.09.16