단계별 쿠키 처리 과정
config/express.js
- app.use(session(…))을 통해 connect.sid라는 쿠키 키가 등록됨
- session()은 express-session이라는 node moodule의 instance
- app.use(passport.initialize())와 app.use(passport.session())을 통해 패스포트가 미들웨어로 등록됨
node_modules/express-session/index.js
- session()에서 uid(random number)를 생성하고 req.sessionID에 기록함
- 이 값을 cookie signing(encryption) 과정을 거쳐 다음과 같은 쿠키를 만들어 냄
connect.sid=s2_BViuqciPgMdb9-7rjaWBjUc0Vd0dJv.xcpTrAHMnhYRgXFJBlvzbLyGvV2womKtCz0IVifGyqQ; path=/; domain=dev-mango.[[http://searchad.naver.com|searchad.naver.com]];
이렇게 signed cookie를 만드는 이유는 클라이언트에서 조작하지 못하도록 하기 위함임
- http://stackoverflow.com/questions/11897965/what-are-signed-cookies-in-connect-expressjs
- 이 sessionID에 해당되는데 DB sessions 테이블의 키로 저장되고 값으로 사용자 정보가 저장됨
2_BViuqciPgMdb9-7rjaWBjUc0Vd0dJv
{
"_id" : "2_BViuqciPgMdb9-7rjaWBjUc0Vd0dJv",
"session" : "{\\"cookie\\":{\\"originalMaxAge\\":null,\\"expires\\":null,\\"secure\\":false,\\"httpOnly\\":true,\\"path\\":\\"/\\"},\\"passport\\":{}}",
"expires" : ISODate("2014-09-18T06:46:54.395Z")
}
- 로그인 안 되어 있는 상태에서 위와 같이 저장되어 있다가 로그인하면서 passport.user값이 users 테이블의 _id값으로 채워짐
{
"_id" : "2_BViuqciPgMdb9-7rjaWBjUc0Vd0dJv",
"session" : "{\\"cookie\\":{\\"originalMaxAge\\":null,\\"expires\\":null,\\"secure\\":false,\\"httpOnly\\":true,\\"path\\":\\"/\\"},\\"passport\\":{\\"user\\":\\"53fbe331559b79e51b5d47fa\\"}}",
"expires" : ISODate("2014-09-18T06:50:00.934Z")
}
- 이렇게 만들어진 쿠키값은 응답 헤더의 Set-cookie: 항목으로 클라이언트에 전달됨
- httpOnly 플래그가 켜지게 되는데 이건 클라이언트에서 javascript로 해당 쿠키에 접근하는 것을 방지하는 보안 옵션임. off할 수 있으나 보안 상 취약해짐
쿠키에 들어갈 사용자 정보를 기록하는 과정(local strategy)
- packages/access/server/config/passport.js
- local strategy를 미들웨어로 등록함
passport.use(new LocalStrategy(...)); - strategy에 verify function으로 등록되는 함수가 다음과 같이 정의됨
- local strategy를 미들웨어로 등록함
function(email, password, done) {
User.findOne({ email: email }, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Unknown user' });
}
if (!user.authenticate(password)) {
return done(null, false, { message: 'Invalid password' });
}
console.log('passport.use() in access after user.authenticate()');
return done(null, user);
});
}
여기서 email을 키로 해서 검색된 사용자 정보가 verify에 성공하면 세션에 등록할 사용자 정보가 됨
packages/users/server/routes/users.js
app.route('/login').post()에 등록되는 미들웨어 중 하나인 passport.authenticate()가 호출됨
node_modules/passport-local/lib/strategy.js
Strategy.prototype.authenticate()에서 verify()를 실행하여 사용자 password의 일치여부를 확인함
verified가 callback으로 호출되면서 self.success(user)를 호출하여 user 객체를 전달함
node_modules/passport/lib/middleware/authentiate.js
strategy.success()를 정의하면서 user 파라미터를 넘겨받게 되어 있는데, 이 값이 req.logIn() 호출에 사용됨
authenticate()에서 req.logIn()을 호출하는데 이 함수는 바로 node_modules/passport-oauth/node_modules/passport/lib/passport/http /request.js에서 정의되어 있음
node_modules/passport-oauth/node_modules/passport/lib/passport/http/request.js
req.logIn()에서 this._passport.instance.serializeUser()를 호출해서 req.session.passport.user의 값을 채워넣음
node_modules/passport/lib/authenticator.js
직렬화 함수로 Authenticator.prototype.serializeUser()가 호출되는데 여기서 user 객체를 직렬화하여 req.session.passport에 달아놓게 됨
코드 분석 중 발견된 문제점
- passport.js가 config 디렉토리와 packages/access/server/config 디렉토리에 각각 존재하는데 후자의 passport.js가 실제로 사용되고 있음
- local strategy를 사용하는데도 불구하고 node_modules/passport/lib/http/request.js가 사용되어야 하는데 node_modules/passport-oauth/node_modules/passport/lib/passport/http /request.js이 대신 사용되고 있음 (의존성 충돌)
결론
- MEAN stack은 session 정보를 담는 쿠키 “connect.sid”를 이용하여 사용자가 인증되었는지 누군지 식별하고 있음
- support 사용자로 로그인하면서 특정 사용자로 행세하겠다는 의도 자체가 해킹과 다를 바 없음
- 쿠키 항목을 추가하거나 계속 쿠키를 원하는 값으로 전환하려면 위에서 언급된 수많은 node module의 로직을 수정해야 함
- request parameter로 사용자 id를 전달받아서 그걸 키로 해서 사용자를 조회하고 local strategy 등록할 때 전달하는 방법으로 조작이 가능할 것으로 보임
- 그러나 쿠키 조작이나 파라미터 방식이 아닌 특정 계정에 사용자별 ACL 기능을 추가하는 방향으로 진행해야 함