반응형

아래는 이 글의 동영상 강좌입니다.

https://youtu.be/QO1NunXmDuU

 

Jwt Token을 이용하여 frontend에서 로그인을 하는 방법을 알아보겠습니다.

 

Store 기능 사용하기

로그인 정보 저장을 위해 store를 사용하도록 합니다.

store 폴더 아래에 index.js 를 추가하고 다음 코드를 입력합니다.

/store/index.js

export const state = () => ({
    accessToken: {}
});

export const mutations = {
    accessToken (state, data) {
        state.accessToken = data;
    }
}

 

Navbar 만들기

Navbar를 만드는 다양한 방법이 있습니다. 이번엔 bootstrap vue를 이용하도록 합니다.

Nuxt 프로젝트에 bootstrap vue를 사용하는 방법은 다음 사이트를 참조하세요. https://bootstrap-vue.org/docs 

아래 명령으로 slider-front 프로젝트에 설치를 합니다.

yarn add bootstrap-vue

nuxt-config.js에 다음 설정을 추가합니다.

modules = {
  'bootstrap-vue/nuxt'
}

프로젝트에 layouts 폴더를 만들고 default.vue를 만듭니다.

bootstrap vue 사이트의 navbar 샘플을 복사하여 default.vue의 template에 붙여놓고 아래와 같이 변경합니다.

<template>
  <div>
  <b-navbar toggleable="lg" type="dark" variant="info">
    <b-navbar-brand href="#">Slider</b-navbar-brand>

    <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

    <b-collapse id="nav-collapse" is-nav>
      <b-navbar-nav class="ml-auto" v-if="!accessToken">
        <b-nav-item href="/login" right>로그인</b-nav-item>
      </b-navbar-nav>
      <b-navbar-nav class="ml-auto" v-if="accessToken && accessToken!==''">
        <b-nav-item @click="logout" right>로그아웃</b-nav-item>
      </b-navbar-nav>
    </b-collapse>
  </b-navbar>
  <nuxt/>
  </div>
</template>
<script>
export default {
  name: 'Layout',
  methods: {
    async logout() {
      await this.$store.commit('accessToken', '');
      await this.$router.push('/');
    }
  },
  computed: {
    accessToken() {
      return this.$store.state.accessToken
    }
  }
}
</script>

 

Login

기존 index.vue를 login.vue로 변경합니다.

index.vue를 만들고 다음과 같이 입력합니다.

index 페이지에서 로그인이 잘 되었는지를 확인하기 위해 accessToken을 보여주는 예제입니다.

/pages/index.vue

<template>
  <div>
    <div>Welcome!</div>
    <div v-if="accessToken">{{accessToken}}</div>
  </div>
</template>
<script>
export default {
  name: 'IndexPage',
  computed: {
    accessToken() {
      return this.$store.state.accessToken
    }
  }
}
</script>

로그인 API 호출 로직 수정

pages/kakao-callback.vue를 다음과 같이 수정합니다.

<template>
  <div>로그인중....</div>
</template>

<script>
  export default {
    async mounted() {
      try{
        if(!this.$route.query.code){
          return this.$router.push('/')
        }
        console.log(`code : ${this.$route.query.code}`)
        const body = {
          code: this.$route.query.code,
          domain: window.location.origin
        }
        const response = await this.$axios.$post(
            '/auth/login', body, {}
        )
        if(response){
          console.log(response.accessToken);
          this.$store.commit('accessToken', response.accessToken);
        }
        return this.$router.push('/')
      }catch(error){
        console.log(error)
      }
    }
  }
</script>

프로젝트 실행화면

프로젝트를 실행하면 다음과 같은 화면을 보실 수 있습니다.

 

첫화면

 

로그인 화면

 

로그인 성공 후 화면

 

반응형
반응형

이번 시간은 JWT 인증 기능을 추가해 보겠습니다.

 

다음은 이 강의의 동영상입니다.

https://youtu.be/SClJMs2BlWk

 

 

JWT에 관한 자세한 사항은 제가 이전에 작성한 글을 참조해 주세요.

https://codegear.tistory.com/72

 

[Nodejs 프레임워크]NestJS - 12. JWT 토큰 생성

다음은 이글의 동영상 강의 입니다. https://youtu.be/H4VS-Osylvo 토큰을 이용한 API 호출 방법 REST API 호출시 토큰을 이용하면 사용자 인증과 권한을 체크할 수 있습니다. 다음 그림은 토큰의 생성과 호

codegear.tistory.com

 

JWT 관련 패키지 추가

jwt 인증을 위해서는 다음과 같은 패키지가 필요합니다.

- @nestjs/jwt

- @nestjs/passport

- passport

- passport-jwt

아래와 같이 패키지를 추가합니다.

yarn add @nestjs/jwt @nestjs/passport passport passport-jwt

 

Payload 인터페이스 생성

jwt인증시엔 payload interface가 필요합니다.

아래와 같이 같이 Payload interface를 만듭니다.(참조 : https://jwt.io/)

/src/auth/security/payload.interface.ts

export interface Payload {
    id: number;
    name: string;
    authorities?: any[];
}

JwtStrategy 를 아래와 같이 작성합니다.

/src/auth/security/passport.jwt.stategy.ts

import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { AuthService } from "../auth.service";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy){
    constructor(private authService: AuthService){
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: true,
            secretOrKey: process.env.JWT_SECRET
        })
    }
}

 

로그인 및 회원 가입

카카오 로그인 후에 회원 로그인 및 회원 가입을 처리합니다.

- 회원 데이터가 없을 경우는 회원 가입을 처리합니다.

- 회원 데이터가 있을 경우는 로그인 처리를 합니다.

 

우선 auth.controller에서 카카오 로그인을 한 후 다음 코드를 추가합니다.

/src/auth/auth.controller.ts

			console.log(`kakaoUserInfo : ${JSON.stringify(kakao)}`);
            if (!kakao.id) {
                throw new BadRequestException('카카오 로그인 실패!');
            }

            // 로그인 처리 - 회원 가입이 안되어 있을 경우 가입 처리
            const jwt =  await this.authService.login(kakao);
            console.log(`jwt.accessToken : ${jwt.accessToken}`);
            res.send({
                accessToken: jwt.accessToken,
                message: 'success'
            });

authService에서 고객을 조회하고, 로그인을 처리합니다.

/src/auth/auth.service.ts

async login(kakao): Promise<{accessToken: string} | undefined> {
	let userFind: User = await this.userService.findByFields({
            where: { kakaoId: kakao.id }
        });
        if(!userFind) {
            isFirstLogin = true;
            // 회원 가입
            const user = new User();
            user.kakaoId = kakao.id;
            user.email = kakao.kakao_account.email;
            user.name = kakao.kakao_account.name;
            
            userFind = await this.registerUser(user);
        }

        const payload: Payload = {
            id: userFind.id,
            name: userFind.name,
            authorities: userFind.authorities
        };
        return {
            accessToken: this.jwtService.sign(payload)
        };
}

userService에  findByFields, registerUser를 추가합니다.

/src/auth/user.service.ts

   constructor(
        @InjectRepository(User)
        private userRepository: Repository<User>,
        @InjectRepository(UserAuthority)
        private userAuthorityRepository: Repository<UserAuthority>,
    ){}
    
    async findByFields(options: FindOneOptions<User>): Promise<User | undefined> {
        return await this.userRepository.findOne(options);
    }
    
    async registerUser(newUser: User): Promise<User> {
        let userFind: User = await this.findByFields({
            where: { kakaoId: newUser.kakaoId }
        });
        if(userFind) {
            throw new HttpException('Username aleady used!', HttpStatus.BAD_REQUEST);
        }
        const registeredUser = await this.userService.save(newUser);
        if(registeredUser){
            await this.saveAuthority(registeredUser.id);
        }else {
            throw new HttpException('Username register error!', HttpStatus.INTERNAL_SERVER_ERROR);
        }

        return registeredUser;
    }
    
    async save(user: User): Promise<User | undefined> {
        return await this.userRepository.save(user);
    }
    
    async saveAuthority(userId: number): Promise<UserAuthority | undefined> {
        let userAuth = new UserAuthority();
        userAuth.userId = userId;
        userAuth.authorityName = RoleType.USER;
        return await this.userAuthorityRepository.save(userAuth);
    }

권한 Type을 선언한 RoleType을 만들어줍니다.

/src/auth/role-type.ts

export enum RoleType {
    USER = 'ROLE_USER',
    ADMIN = 'ROLE_ADMIN',
}

 

auth.module에 jwtModule을 추가하고 import 합니다.

/src/auth/auth.module.ts

import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './security/passport.jwt.strategy';
...
@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    JwtModule.register({
      secret: process.env.JWT_SECRET,
      signOptions: {expiresIn: '300s'},
    }),
    PassportModule
  ],
  exports: [TypeOrmModule, UserService],
  controllers: [AuthController],
  providers: [AuthService, UserService, JwtStrategy]
})
export class AuthModule {}

 

프론트에서 로그인을 하면 브라우저의 콘솔에서 아래와 같이 jwt token을 확인할 수 있습니다.

반응형
반응형

이 글은 아래 동영상 강의로 제공됩니다.

 

이번엔 Nestjs로 백엔드 개발을 진행합니다.

 

전체적인 진행 순서는 다음과 같습니다.

  1. kakao developer에서 애플리케이션 생성
  2. 전체 Workflow 이해하기
  3. Nuxtjs(Frontend) 개발
  4. Nestjs(Backend) 개발

 

아래 명령으로 프로젝트를 생성합니다.

nest new kakao-login-back

- package manager : yarn

 

axios package를 추가합니다.

yarn add axios

server port를 3001로 바꿔줍니다.

src/main.ts

await app.listen(3001);

 

src/app.controller에 login api를 만듭니다.

@Post('/login')
  async login(@Body() body: any, @Response() res): Promise<any> {
    try {
      // 카카오 토큰 조회 후 계정 정보 가져오기
      const { code, domain } = body;
      if (!code || !domain) {
        throw new BadRequestException('카카오 정보가 없습니다.');
      }
      const kakao = await this.appService.kakaoLogin({ code, domain });

      console.log(`kakaoUserInfo : ${JSON.stringify(kakao)}`);
      if (!kakao.id) {
        throw new BadRequestException('카카오 정보가 없습니다.');
      }

      res.send({
        user: kakao,
        message: 'success',
      });
    } catch (e) {
      console.log(e);
      throw new UnauthorizedException();
    }
  }

src/app.service.ts에 다음 내용을 추가합니다.

async kakaoLogin(options: { code: string; domain: string }): Promise<any> {
    const { code, domain } = options;
    const kakaoKey = '87073966cb41...';
    const kakaoTokenUrl = 'https://kauth.kakao.com/oauth/token';
    const kakaoUserInfoUrl = 'https://kapi.kakao.com/v2/user/me';
    const body = {
      grant_type: 'authorization_code',
      client_id: kakaoKey,
      redirect_uri: `${domain}/kakao-callback`,
      code,
    };
    const headers = {
      'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
    };
    try {
      const response = await axios({
        method: 'POST',
        url: kakaoTokenUrl,
        timeout: 30000,
        headers,
        data: qs.stringify(body),
      });
      if (response.status === 200) {
        console.log(`kakaoToken : ${JSON.stringify(response.data)}`);
        // Token 을 가져왔을 경우 사용자 정보 조회
        const headerUserInfo = {
          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
          Authorization: 'Bearer ' + response.data.access_token,
        };
        console.log(`url : ${kakaoTokenUrl}`);
        console.log(`headers : ${JSON.stringify(headerUserInfo)}`);
        const responseUserInfo = await axios({
          method: 'GET',
          url: kakaoUserInfoUrl,
          timeout: 30000,
          headers: headerUserInfo,
        });
        console.log(`responseUserInfo.status : ${responseUserInfo.status}`);
        if (responseUserInfo.status === 200) {
          console.log(
            `kakaoUserInfo : ${JSON.stringify(responseUserInfo.data)}`,
          );
          return responseUserInfo.data;
        } else {
          throw new UnauthorizedException();
        }
      } else {
        throw new UnauthorizedException();
      }
    } catch (error) {
      console.log(error);
      throw new UnauthorizedException();
    }
  }

서버를 실행합니다.

yarn start:dev

브라우저의 콘솔 로그에 사용자 정보가 리턴된것을 확인할 수 있습니다.

이상으로 카카오로그인을 RestAPI를 이용하여 처리하는 방법에 대해 알아보았습니다.

반응형
반응형

이 글은 아래 동영상 강의로 제공됩니다.

 

Frontend 개발은 Nuxtjs를 이용하여 진행하도록 합니다.

 

전체적인 진행 순서는 다음과 같습니다.

  1. kakao developer에서 애플리케이션 생성
  2. 전체 Workflow 이해하기
  3. Nuxtjs(Frontend) 개발
  4. Nestjs(Backend) 개발

 

우선 아래 명령으로 Nuxt 프로젝트를 생성합니다.

npx create-nuxt-app kakao-login-front

- 프로젝트 명 : kakao-login-front

- programing language : Javascript

- Package manager : Yarn

- UI framework : None

- Nuxt.js modules : Axios

- Linting tools : ESLint

- Testing framework : None

- Rendering mode : Single Page App

- Server : Node.js

- Development tools : Enter

- Continuous integration : None

- Version control system : None

 

프로젝트 생성이 완료되면 아래와 같은 메시지가 나옵니다.

에디터에서 프로젝트를 열고, yarn dev를 사용해서 프로젝트를 실행합니다.

브라우저에서 http://localhost:3000번을 입력합니다.

이때 아래와 같은 페이지가 나오면 프로젝트가 정상적으로 만들어진 것입니다.

 

카카오 로그인 기능을 만들기 위해 아래와 같은 버튼이미지가 필요합니다.

아래 사이트에서 다운로드 하실 수 있습니다.

https://developers.kakao.com/tool/resource/login

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

이미지를 static 폴더 아래에 copy합니다.

 

카카오로그인을 사용하기 위해서 kakao.js 파일이 필요합니다.
nuxt.config.js의 'head' 아래에 다음 코드를 추가합니다.

script: [
  {
    src: 'https://developers.kakao.com/sdk/js/kakao.js'
  }
]

 

pages/index.vue 파일에 카카오 로그인 버튼을 추가하고 로그인 method를 작성 합니다.

<template>
  <a @click="loginWithKakao">
    <img src="/kakao_login_large_narrow.png"/>
  </a>
</template>

<script>
export default {
  name: 'IndexPage',
  methods: {
    kakaoInit () {
      Kakao.init('329a6a74...')// KaKao client key
      Kakao.isInitialized()
    },
    async loginWithKakao () {
      await Kakao.Auth.authorize({
        redirectUri: `${window.location.origin}/kakao-callback`
      })
    }
  },
  mounted () {
    this.kakaoInit()
  }
}
</script>

다음과 같이 컴파일 오류가 발생할 수 있습니다.

이 경우 .eslintrc.js 파일을 열고 아래 내용을 추가하면 오류를 해결 할 수 있습니다.

'Kakao' 변수가 선언되지 않았다는 오류이고, 선언되지 않은 변수를 eslint에서 인식할 수 있도록 해 준것입니다.

  globals: {
    Kakao: true
  }

카카오 인증 후 callback 페이지를 아래와 같이 만듭니다.

axios를 이용해 서버API를 호출할 것이기 때문에 nuxt.config.js 다음을 추가합니다.

- baseURL은 서버의 주소를 지정합니다.

  modules: [
    '@nuxtjs/axios'
  ],

  axios: {
    baseURL: 'http://localhost:3001/'
  },

 

pages/kakao-callback.vue

<template>
  <div>
    로그인 중 입니다...
  </div>
</template>

<script>
export default {
  async mounted () {
    try {
      // code 유무 체크
      if (!this.$route.query.code) {
        return this.$router.push('/')
      }
      console.log(`code : ${this.$route.query.code}`)

      // 카카오 로그인 요청
      const body = {
        code: this.$route.query.code,
        domain: window.location.origin
      }
      const response = await this.$axios.$post(
        '/login', body, {}
      )
      console.log(response)
    } catch (error) {
      console.log(error)
    }
  }
}
</script>

브라우저에서 확인을 해봅니다.

확인하고 계속하기를 클릭하면 아래와 같은 화면이 보입니다.

브라우저의 콘솔을 확인해보면 다음과 같이 code 값이 넘어온것을 확인 할 수 있습니다.

이렇게 해서 frontend 작업이 완료되었습니다.

다음시간은 backend 작업을 진행해보겠습니다.

반응형

+ Recent posts