Seize the day

SEARCH RESAULT : 글 검색 결과 - 전체 글 (총 474개)

POST : Backend study

Deno oak 서버에서 JWT 인증하기

error_code.ts

export class ErrorCode extends Error {
    code: number
    
    constructor(message: string, code: number) {
        super(message)
        this.code = code
    }
}

export const kCodeMaintenance: number = 1
export const kCodeJwtAuth: number = 2

export function throwJwtError(message: string) {
    throw new ErrorCode("jwt auth error: " + message, kCodeJwtAuth)
}

verify_token.ts

import {encode as base64url_encode, decode as base64url_decode} from "https://deno.land/std@0.103.0/encoding/base64url.ts"
import {hmac as createHmac} from "https://deno.land/x/crypto@v0.10.0/hmac.ts"
import {throwJwtError} from "./error_code.ts"

const PRIVATE_SECRET: Uint8Array = new TextEncoder().encode("private secret")

/*
 *  JWT token 유효성을 검사하고, 데이타를 추출하여 리턴한다.
 *  예외가 발생하면 ErrorCode를 던진다.
 *  const [uid, muid, did] = verifyJwtToken(token)
 */
export function verifyJwtToken(token: string): [string, string, string] {
    if (token == null) {
        throwJwtError("empty token")
    }
    const [header, payload, signiture] = token.split('.')
    const value = new TextEncoder().encode(header + "." + payload)
    const hmacValue = createHmac('sha256', PRIVATE_SECRET, value)
    const signiture2 = base64url_encode(hmacValue)
    if (signiture !== signiture2) {
        throwJwtError("invalid signiture")
    }
    const payloadJson: string = new TextDecoder().decode(base64url_decode(payload))
    const payloadObj = JSON.parse(payloadJson)
    const exp = payloadObj.exp || null
    if (typeof exp != "number") {
        throwJwtError("invalid exp")
    }
            
    if (exp <= Math.floor(Date.now()/1000)) {
        throwJwtError("expired token")
    }
    
    const uid = payloadObj.uid || null
    if (uid == null) {
        throwJwtError("empty uid")
    }
        
    const muid = payloadObj.muid || ""
    const did = payloadObj.did || ""
    return [uid, muid, did]
}

// request: oak request
export function verifyJwtRequest(request: any): [string, string, string] {
    return verifyJwtToken(request.headers.get("json-web-token"))
}

routes.ts

import { Router } from "https://deno.land/x/oak/mod.ts"
import getHello from "./v1/hello.ts"
import getUser from "./v1/user.ts"

const router = new Router()

router
    .get("/lt/v1/hello", getHello)
    .post("/lt/v1/user", getUser)

export default router

server.ts

import { Application, Context } from "https://deno.land/x/oak/mod.ts"
import router from './routes.ts'

const errorHandler = async (ctx: Context, next: any) => {
    try {
      await next();
    } catch (err) {
      ctx.response.status = 200;
      ctx.response.body = { code: err.code || 500, message: err.message }
    }
}

const _404 = async (ctx: Context) => {
    ctx.response.status = 200;
    ctx.response.body = { code: 404, message: "404 Not Found!" }
}


const app = new Application()
const port = 8000
app.use(errorHandler);
app.use(router.routes())
app.use(router.allowedMethods())
app.use(_404)

console.log("running:  api server port: " + port);
await app.listen({ port: port });

 

v1/hello.ts

import { verifyJwtRequest } from "../verify_token.ts"

const getHello = async ({ request, response }: { request: any; response: any }) => {
    const [uid, muid, did] = verifyJwtRequest(request)
    
    console.log(uid + ":" + muid + ":" + did)

    //const body = await request.body()
    //const text: string = body.value
    response.body = { 
        message: 'Hello OK',
        uid: uid,
        muid: muid,
        did: did
    }
    response.status = 200
}

export default getHello
top

posted at

2021. 8. 10. 22:08


POST : Backend study

Mongo db의 Array정보의 find,update,delete 실습

# 하나 추가

> db.user.insertOne({uid: "uid1", name: "name1", email: "email1"})
> db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1" }

# db 배열에 아이템 하나 추가하기

> db.user.updateOne({uid: "uid1"}, { $push: { db: {did: "did1", name: "db1"} } })
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" } ] }

# db 배열에 아이템  여러개 추가하기

> db.user.updateOne(
  {uid: "uid1"}, 
  { $push: 
    { 
      db: { 
        $each: [ 
          {did: "did2", name: "db2"},
          {did: "did3", name: "db3"}
        ] 
      }
    }
  })
  
  > db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" }, { "did" : "did2", "name" : "db2" }, { "did" : "did3", "name" : "db3" } ] }

# db안에 db3인 요소만 제거

> db.user.updateOne(
   {uid: "uid1"},
   { $pull: {
      db: { did: "did3" }
     }
   })
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" }, { "did" : "did2", "name" : "db2" } ] }

# db안에 did2인 요소의 name을 수정하기
Update Arrays in a Document — Node.js (mongodb.com)

> db.user.updateOne(
  {uid: "uid1", "db.did": "did2"},
  { $set: { "db.$.name": "name22" } }
  )
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

> db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" }, { "did" : "did2", "name" : "name22" } ] }
>

# array에 매치된 document 찾기

> db.user.find({
  "db.did": "did1"
})


{ "_id" : ObjectId("611361f9bef2364fbcaf7d3f"), "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" }, { "did" : "did2", "name" : "name22" } ] }
top

posted at

2021. 8. 10. 22:06


POST : Backend study

안드로이드 앱에서 Firebase의 JWT token 가져오기 실습

https://dajkim76.tistory.com/533 에서 이어서 ...

Firebase에 프로젝트 추가하기

1. firebase에 안드로이드 앱추가 : 

    sha1 hash 추가 : Authenticating Your Client  |  Google Play services  |  Google Developers  

./gradlew signingReport

2. google-services.json 다운로드하여, app 폴더에 복사
   gradle 설정 추가 :   Android 프로젝트에 Firebase 추가 (google.com)

3. gradle에 auth 라이브러리 추가 : Firebase 인증 (google.com)

4. 각종. 로그인 처리 : FirebaseUI로 손쉽게 Android 앱에 로그인 추가 (google.com)

 

구글 로그인 테스트

snippets-android/auth at master · firebase/snippets-android (github.com) 참고

인증 체계에서 지원하는 인증 방식에 email 과 구글 로그인을 체크하고, 
Firebase 설정에 지원 email을 지정한다. 
OAuth2 web_client_id 는 인증탭에서 구하기 (R.string.default_web_client_id를 사용해도 문제는 없는 것 같다 )

    private val googleSignInClient: GoogleSignInClient by lazy {
        val googleSignInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.web_client_id))
            .requestEmail()
            .build()
        GoogleSignIn.getClient(this, googleSignInOptions)
    }

    @OnMenuClick(R.id.menu_google_login)
    fun onClickGoogleLogin(menuItem: MenuItem) {
        FirebaseAuth.getInstance().currentUser?.let {
            ToastUtils.show("이미 로그인됨: " + it.uid)
            return
        }
        startActivityForResult(googleSignInClient.signInIntent, 1)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 1) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                // Google Sign In was successful, authenticate with Firebase
                val account = task.getResult(ApiException::class.java)!!
                Log.d(TAG, "firebaseAuthWithGoogle:" + account.id)
                firebaseAuthWithGoogle(account.idToken!!)
            } catch (e: ApiException) {
                // Google Sign In failed, update UI appropriately
                ToastUtils.show("Google sign in failed" + e.toString())
            }
        }
    }

    private fun firebaseAuthWithGoogle(idToken: String) {
        val credential = GoogleAuthProvider.getCredential(idToken, null)
        FirebaseAuth.getInstance().signInWithCredential(credential)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // Sign in success, update UI with the signed-in user's information

                    val user = FirebaseAuth.getInstance().currentUser
                    ToastUtils.show("signInWithCredential:success = " + user?.uid)
                    //updateUI(user)
                } else {
                    // If sign in fails, display a message to the user.
                    ToastUtils.show("signInWithCredential:failure" +  task.exception.toString())
                    //updateUI(null)
                }
            }
    }

 

Firbase functions 호출하기 (Firbasebse 로그인되어 있지 않으면 실패를 응답하므로 먼저 로그인)

앱에서 함수 호출  |  Firebase (google.com)  참고

    @OnMenuClick(R.id.menu_create_jwt)
    fun onClickCreateJwt(menuItem: MenuItem) {
        val text: String = JSONObject()
            .put("expireSeconds", 60 * 60 * 24)
            .put("did", "my_did")
            .put("muid", "my_muid")
            .toString()

        val data = hashMapOf(
            "text" to text
        )

        FirebaseFunctions.getInstance()
            .getHttpsCallable("createJwtToken")
            .call(data)
            .addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    val dataString = task.result?.data?.toString()
                    val dataMap = task.result?.data as? Map<String, Object>;
                    if (dataMap != null) {
                        if ((dataMap["code"] as? Int) == 0) {
                            val resultMap = dataMap["result"] as? Map<String, Object>
                            val token = resultMap?.get("token") as? String
                            val exp = resultMap?.get("exp") as? Int
                            Log.d("__T", "token= $token\nexp=$exp")
                            DUtils.alert("token= $token\nexp=$exp")
                        }
                    }
                } else {
                    val e = task.exception
                    if (e is FirebaseFunctionsException) {
                        val code = e.code
                        val details = e.details
                    }
                    DUtils.alert(e.toString())
                }
            }
    }

 

TODO:  App check라는게 있어서 호출하는 앱이 유효한지 한 번 더 검증해주는 것 같다. 
Android에서 SafetyNet으로 앱 확인 활성화  |  Firebase (google.com) 

top

posted at

2021. 8. 9. 16:30


POST : Backend study

GCP 우분투 focal에 Nginx/Mongodb/Deno 다시 설치하기

https://dajkim76.tistory.com/532 의 과정을 다시한다. 

MongoDB 5.0.x 설치

Mongodb 5.0이 우분투 하위버전에서 설치가 되지 않아서 GCP에 디스크를 추가하고 상위버전 우분투 이미지를 사용했다.
Mongodb 5.0.2 설치는 간단히 완료됬다. 
Install MongoDB Community Edition on Ubuntu — MongoDB Manual

kimdaejeong@base-backend-1:~$ history
    1  wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -
    2  echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list
    3  sudo apt-get update
    4  sudo apt-get install -y mongodb-org
    5  ps --no-headers -o comm 1
    6  sudo systemctl start mongod
    7  mongo

 

Nginx 설치하기 

    9  sudo apt-get install nginx
   10  nginx -V
   11  sudo service nginx start

nginx version: nginx/1.18.0 (Ubuntu) 

Nginx에 SSL 적용하기

   20  sudo apt-get install certbot
   21  cat /etc/nginx/sites-enabled/default
   22  sudo certbot certonly --webroot -w /var/www/html -d app.mdiwebma.com
   23  sudo ls -al /etc/letsencrypt/live/app.mdiwebma.com
   24  sudo vi  /etc/nginx/sites-enabled/default
   25  sudo service nginx restart

Deno 설치

   14  sudo apt-get install unzip -y
   15  curl -fsSL https://deno.land/x/install/install.sh | sh
   16  vi .bashrc
   17  . .bashrc
   18  deno

 

서버 동작 테스트

   28  mkdir server
   29  cd server
   30  vi server.ts
   31  deno run --inspect  --allow-net --unstable server.ts &

https://app.mdiwebma.com/lt/v1/tslist 로 연결 확인 

이 모든 과정을 삽질없이 완료했다. 이전 영구 디스크 이미지는 이제 지워도 될 것 같다. 

top

posted at

2021. 8. 5. 22:10


POST : Backend study

Firebase firestore의 보안 규칙 강화

예전에 공부했둔 것 꺼내서 정리했다. Firestore는 Nosql 이라서 대부분의 db코드가 클라에 있고, 그러다 보니 보안을 클라에서만 의존하기에는 불안해서, 서버쪽에서 강화하는쪽으로 고민했었다. 

real-groups/{groupId} 는 멤버만 조회할 수 있어야 했다. 
real-groups/{groupId}/members/{userId} 의 멤버의 가입은 초대 코드를 알아야만 가입할 수 있다. 
이 두 가지 조건을 보안 규칙에 적용해서, 클라이언트에서 잘 못된 호출이나 변조된 호출이 있더라도 데이타를 보호할 수 있도록 했다. 

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {    

    // real
    match /real-users/{userId} {
      allow create, read, update, delete: if request.auth.uid == userId;
    }    
    match /real-users/{userId}/groups/{groupId} {
      allow create, read, update, delete: if request.auth.uid == userId;
    }
    match /real-open-groups/{groupId} {      
      // 로그인된 사용자는 생성, 읽기 가능
      allow create, read: if request.auth.uid != null;
      // 삭제는 document를 만든사람만 가능
      allow delete: if get(/databases/$(database)/documents/real-groups/$(groupId)).data.owner == request.auth.uid;
      // 업데이트는 document를 만든사람이거나, 이 그룹에 참여한 사람만 가능
      allow update: if get(/databases/$(database)/documents/real-groups/$(groupId)).data.owner == request.auth.uid ||
        exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid));
    }
    match /real-groups/{groupId} {      
      allow create: if request.auth.uid != null;
      allow delete: if resource.data.owner == request.auth.uid;
      allow read, update: if resource.data.owner == request.auth.uid ||
        exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid));      
    }
    match /real-groups/{groupId}/members/{userId} {
      // 멤버추가는 그룹의 관리자이거나, 가입코드를 알고 있는 경우에만 가능
      allow create: if get(/databases/$(database)/documents/real-groups/$(groupId)).data.owner == request.auth.uid ||
        get(/databases/$(database)/documents/real-groups/$(groupId)).data.code == request.resource.data.code;
      // 멤버만 읽기 업데이트 가능
      allow read, update: if exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid));
      // 멤버 삭제는 그룹멤버이면서, 잔고가 0인 경우만 가능
      allow delete: if exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid)) &&
      	get(/databases/$(database)/documents/real-groups/$(groupId)/members/$(userId)).data.money == 0;
    }
    match /real-groups/{groupId}/restaurants/{restId} {
      // 식당추가는 그룹 멤버만 가능
      allow create, read, update, delete: if exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid));
    }
  }
}
top

posted at

2021. 8. 5. 11:23


POST : Backend study

Firebase functions에서 Json web token(JWT) 만들기

Type스크립트로 jwt를 만들기 

[JWT] JSON Web Token 소개 및 구조 | VELOPERT.LOG  의 실습을 deno로 해 본다. 
test_jwt.ts 

import {encode as base64url_encode} from "https://deno.land/std@0.103.0/encoding/base64url.ts"
import {assertEquals} from "https://deno.land/std@0.103.0/testing/asserts.ts"

function json2Uint8Array(json: any) : Uint8Array {
    return new TextEncoder().encode(JSON.stringify(json))
}

// encode header
const header = {
    "typ": "JWT",
    "alg": "HS256"
  };
  
const encodedHeader = base64url_encode(json2Uint8Array(header));
assertEquals(encodedHeader, "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9")


// encode payload
const payload = {
    "iss": "velopert.com",
    "exp": "1485270000000",
    "https://velopert.com/jwt_claims/is_admin": true,
    "userId": "11028373727102",
    "username": "velopert"
};

const encodedPaylod = base64url_encode(json2Uint8Array(payload));
assertEquals(encodedPaylod, "eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0")


// encode signiture
import {hmac as createHmac} from "https://deno.land/x/crypto@v0.10.0/hmac.ts"
const private_secret: Uint8Array = new TextEncoder().encode("secret")
const value = new TextEncoder().encode(encodedHeader + "." + encodedPaylod)
const hmacValue = createHmac('sha256', private_secret, value)
const signature = base64url_encode(hmacValue)
assertEquals(signature, "WE5fMufM0NDSVGJ8cAolXGkyB5RmYwCto1pQwDIqo2w")

console.log("JWT token: " + encodedHeader+ "." + encodedPaylod + "." + signature)

 

Firebase 로그인에서 JWT를 만들기

그것을 api 서버에서 private_secret로 검증해서 유효한 API call인지 검증할 수 있을 것 같다. 검증은 nginx에서 할 수 있을 듯...  ( Examples (nginx.org) )

제일 좋은 것은.. Nginx에서 JWT 검증도 하고, exp 만료도 체크하고, 데이타를 까서 특정 값을 헤더로 넣어주는 것이다.  API 서버에서는 헤더에서 가져와서 바로 사용하면 된다.
https://mostafa-asg.github.io/post/nginx-authentication-with-jwt/  아마도 nginx 를 새로 빌드를 해야할 듯하다. 


https://firebase.google.com/docs/functions/callable?hl=ko#java  참고..

# 라이브러리 설치
$ npm install -g firebase-tools

# firebase 로그인
$ firebase login

# 프로젝트 생성
$ firebase init

# 프로젝트 배포
$ firebase depoly

base64url은 base64의 문자중 3개를 (+ / =) 다른 것으로 교체하는 간단한 (jwt.io 방문해서 스펙을 확인)

npm install base64url

 

Firebase functions에서 JWT를 생성하는 코드, exp를 24시간을 준다.  
Index.js

const functions = require("firebase-functions");
const base64url = require('base64url');
const crypto = require('crypto');

const PRIVATE_SECRET = "blabla";
const MAX_EXPIRE_SECONDS = 60 * 60 * 24;    //24 hours

function generate_hs256_jwt(claims) {
    var header = { typ: "JWT",  alg: "HS256" };   
    var s = [header, claims].map(JSON.stringify)
                            .map(v=>base64url(v))
                            .join('.');

    var h = crypto.createHmac('sha256', PRIVATE_SECRET);

    return s + '.' + base64url(h.update(s).digest())
}

exports.createJwtToken = functions.https.onCall((data, context) => {
    // Message text passed from the client.
    const text = data.text;
    // Checking attribute.
    if (!(typeof text === 'string') || text.length === 0) {
        // Throwing an HttpsError so that the client gets the error details.
        throw new functions.https.HttpsError('invalid-argument', 'The function must be called with ' +
            'one arguments "text" containing the message text to add.');
    }
    // Checking that the user is authenticated.
    if (!context.auth) {
        // Throwing an HttpsError so that the client gets the error details.
        throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
            'while authenticated.');
    }

    // Authentication / user information is automatically added to the request.
    const fuid = context.auth.uid;
    
    var expireSeconds = MAX_EXPIRE_SECONDS;
    const exp = Math.floor(Date.now()/1000) + expireSeconds;

    var claims = {
        "iss": "lt",
        "exp": exp,
        "fuid": fuid
    };    

    try {
        const token = generate_hs256_jwt(claims);
        return {
            code: 0,
            result: {
                "exp": exp,
                "token": token
            }
        };
    } catch(error) {
        console.log(error);
        return  {
            code: 500,
            message: "generate_hs256_jwt() failed"
        };
    }
});

 

배포

firebase deploy --only functions:createJwtToken

 

안드로이드에서 호출하기는  https://dajkim76.tistory.com/537 참고

top

posted at

2021. 8. 5. 01:52


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크