인증&인가_과제#1~3_11.9~11.11

송철진·2022년 11월 9일
0

과제1

  • Bcrypt 모듈을 사용하여 사용자 비밀번호 암호화
  • Express를 이용한 API 서버 만들기의 Assignment 1의 사용자 회원가입 하기 코드에 추가
  • 회원가입 API
    • 알맞은 API 호출 URL을 설정하여서 클라이언트와(httpie/postman) 통신을 성공해 주세요.
    • 알맞은 http 메소드를 선정하여서 유저의 정보를 백엔드 서버에 전달해 주세요.
    • bcrypt 모듈을 사용하여 회원가입 하는 사용자의 비밀번호 암호화를 진행해 주세요.
    • 데이터가 생성됬을 때에 알맞는 http 상태코드를 반환해 주세요.
    • http response로 반환하는 JSON 데이터의 형태는 다음과 같습니다.
{
  "message" : "userCreated"
}

준비

bcrypt 패키지를 설치한다

npm install bcrypt

새로운 브랜치를 생성 및 교체

git branch 새_브랜치_이름
git checkout 새_브랜치_이름

소스코드

bcrypt 모듈을 임포트해서 변수bcrypt에 할당한다(위쪽에 위치시킬 것!)

const bcrypt = require("bcrypt");

솔팅 횟수saltRounds를 지정하고,
변수bcrypt의 메소드hash()password(string)와 saltRounds(number)를 인자로 받아 hash값을 반환하는 함수makeHash()를 선언한다

const saltRounds = 12;
const makeHash = async (password, saltRounds) => {  
    return await bcrypt.hash(password, saltRounds); 
}

지난주에 만들었던 회원가입 엔드포인트에서;

  • req.body로 입력받은 password와 위에서 선언한 saltRounds를 인자로 받는 makeHash()를 호출하고,
  • 그 반환값을 기다려서 변수 hashedPassword에 할당한다
  • 쿼리를 수정한다: password 컬럼이 hashedPassword를 받도록.
app.post("/users/signup", async(req, res, next) => {
    const { name, email, profileImage, password } = req.body; 
    
    const hashedPassword = await makeHash(password, saltRounds); 

    await appDataSource.query(
        `INSERT INTO users(
            name, 
            email,
            profile_image,
            password
        ) VALUES (?, ?, ?, ?);
        `, [name, email, profileImage, hashedPassword]
    );

    res.status(201).json({ message : "userCreated"});
})

실행 결과

httpie
http -v POST 127.0.0.1:3000/users/signup name="Live Bear" email="lb34@gmail.com" profileImage="http://github.com/image_test_2.jpg" password="password"

mysql

TIL
왜인지 모르겠지만 async로 선언한 main()함수 안에 makeHash 함수를 호출하고 main()함수를 app.post(...) 안쪽에 선언했을 때는 커넥션 abort 에러?로 인해 동작이 되지 않았다. 쿼리에 hashedPassword값이 반환되지 않아서 sql syntax에러가 발생했는데.. 비동기를 겹쳐 써서?로 인해 값을 불러오지 못하는 거 같다(추측)

과제2

  • Bcrypt Verification을 진행하여 로그인 정보 확인
  • 로그인 정보 확인 후 JWT 토큰을 발급
  • 로그인 API
    • 알맞은 API 호출 URL을 설정하여서 클라이언트와(httpie/postman) 통신을 성공해주세요.
    • 알맞은 http 메소드를 선정하여서 유저의 정보를 백엔드 서버에 전달해주세요.
    • 비밀번호 검증이 안 되었을 때 적절한 에러를 반환해주세요.
    • 토큰이 발급 되었을 때와 에러가 발생했을 때 각 각 알맞는 http 상태코드를 반환해주세요.
    • http response로 반환하는 JSON 데이터의 형태는 다음과 같습니다.
// 로그인 성공, 토큰 발급
{
  "accessToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
// 로그인 실패
{
  "message" : "Invalid User"
}

준비

jsonwebtoken을 설치한다

npm install jsonwebtoken --save

.env파일에 TYPEORM_SECRETKEY = 내시크릿키를 입력한다

환경변수 dotenv

소스코드

임포트한 모듈을 변수 jwt에 할당한다

const jwt = require("jsonwebtoken");

.env파일에 환경변수를 선언했으므로 불러오기 위해서는 typeORM의 새 클래스 DataSource에도 넣어준다.

const appDataSource = new DataSource({
	// (생략)
    secretKey: process.env.TYPEORM_SECRETKEY
})

입력한 비밀번호와 저장된 비밀번호해시값을 비교하는 함수를 선언한다
👉 await를 쓰는 이유는 비교한 값의 계산 후 반환까지 시간이 걸려서 기다리겠다는 의미
👉 async를 쓰는 이유는 await를 쓰기 위해 비동기 함수를 별도 선언한 것

const checkHash = async (password, hashedPassword) => {
    return await bcrypt.compare(password, hashedPassword)
}
  1. Request: GET http Method, /login URL, email, password body
  2. typeORM의 쿼리로 users테이블에서 emailbody가 일치하는 사용자의 'id, email, password'를 조회하고 이를 변수userInfo에 할당한다
  3. DB에서 불러온 password는 변수hashedPassword에 할당하고
  4. hashedPassword와 password를 함수checkHash로 비교하여 이를 result에 할당한다
  5. secretKey: typeORM에서 불러와서 할당한다
  6. Response:
  • payload:
    • 사용자id(userInfo에서 할당),
    • 만료기한(1970-01-01을 기준으로 n초 후 까지)
      ex) 만료일이 2023-01-01이라면 53년*365일*86400초 = 1671408000 참고
  • jwtToken: payload와 secretKey을 조합해서 생성
  • 4.의 result가 true면 상태코드200, accessToken값을,
    false면 상태코드 401, Invalid User라는 메시지를 보낸다
app.get("/login", async(req, res, next) => {
    const { email, password } = req.body;

    const userInfo = await appDataSource.query(
        `SELECT 
                users.id,
                users.email,
                users.password 
        FROM users WHERE users.email=?;`, [email]);
    const hashedPassword = userInfo[0].password;

    const result = await checkHash(password, hashedPassword);
    
    const payLoad = { "user_id" : userInfo[0].id,
                        "exp" : 1671408000 }; 
    const secretKey = process.env.TYPEORM_SECRETKEY;
    const jwtToken = jwt.sign(payLoad, secretKey); 

    if ( result === true){
        res.status(200).json({ "accessToken" : jwtToken });
    }else{
        res.status(401).json({ "message" : "Invalid User" });
    }
})

실행결과

로그인 성공
http -v GET 127.0.0.1:3000/login email="lb34@gmail.com" password="password"

  • payload에 user_id, exp를 넣었을 때:

로그인 실패
http -v GET 127.0.0.1:3000/login email="lb34@gmail.com" password="password1"

과제3

  • Express를 이용한 API 서버 만들기의 Assignment 6의 게시글 등록하기 코드에 추가
  • 게시글 등록 API
    • 알맞은 API 호출 URL을 설정하여서 클라이언트와(httpie/postman) 통신을 성공해주세요.
    • 알맞은 http 메소드를 선정하여서 게시글 내용 및 토큰을 백엔드 서버에 전달해주세요.
    • 토큰 검증이 안 되었을 때 적절한 에러를 반환해 주세요.
    • 데이터가 생성됬을 때와 에러가 발생했을 때 각 각 알맞는 http 상태코드를 반환해주세요.
    • http response로 반환하는 JSON 데이터의 형태는 다음과 같습니다.
// 게시글 등록 성공
{
  "message" : "postCreated"
}
// 토큰 검증 실패
{
  "message" : "Invalid Access Token"
}

소스코드

과제2에서 알게 된 accessToken:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo5LCJleHAiOjE2NzE0MDgwMDAsImlhdCI6MTY2Nzk4NjE0MH0.y5dWrs8y2B6auoh2T1XpyxXclIUwJzts3MwfiA7lkdQ

이렇게 하는게 아닐까.. 생각했던 불완전한 소스코드:
👉 모듈화를 해서 끌어와야 한다

app.post("/posts/signup", async(req, res, next) => {
    const { title, content, imageUrl, userId, jwtToken } = req.body;
    const secretKey = process.env.TYPEORM_SECRETKEY;
    const decoded = jwt.verify(jwtToken, secretKey);
    console.log(decoded);
    
    await appDataSource.query(
        `INSERT INTO posts(
            title,
            content,
            image_url,
            user_id
        ) VALUES ( ?, ?, ?, ? );
        `, [title, content, imageUrl, userId]
    );
    if(decoded.user_id === userIdDB){
        res.status(201).json({ message : "postCreated"});
    }else{
        res.status(401).json({ message : "Invalid Access Token"})
    }
    
})

소스코드

TIL
route와 controller를 사용해서 따로 써야 하는데 아직 방법을 모르겠다

실행결과

profile
검색하고 기록하며 학습하는 백엔드 개발자

0개의 댓글