저번 시간에 이어서 JWT 인증 부분이다. (PPT 27p)

의존 설정 부분 다시보기.

 

# Web Service Security

SpringBoot는 @Configuration 어노테이션이 붙어있는 클래스부터 찾는다.

따라서 어느 패키지에 속해있어도 상관은 없다.

 

@Bean 어노테이션이 붙어 있으면 아래 메소드가 자동으로 실행된다. (메소드의 리턴값이 중요한 것)

 

Form은 프론트에서 만들고 백으로 넘겨준다.

 

http.formLogin(config -> config.disable()); 를 넣는 이유 --> Spring Framwork와 비교

 

# Spring과 비교해보자.

 

<security:http pattern="/resources/**" security="none"/>  --> HttpSecurity

 

** REST API는 정적 자원 파일이 없다?

 

Spring [ security.xcml ]

 

SpringBoot [ WebSecurityConfig ]

 

[ Spring ]

여러가지의 필터가 순차적으로 아랫방향으로 검사를 한다.

 

이부분을 SpringBoot에서는 Jwt를 만들어 추가하여 순차적으로 필터링 작업을 할 수 있다.

http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // Before를 쓰는 이유 : 정리 // 아이디 패스워드를 필요로하는 필터 이전에 먼저 해주어야 함

★ AccessToken을 확인하면 아이디와 패스워드를 검사할 필요가 없으므로 jwtAuthenticationFilter를 가장 먼저 검사해준다.

 

 

403 에러는 권한이 없다는 의미의 에러

 

REST API는 사이트위조방지가 활성화 되어있음

 

이전 Spring에서 사이트간 요청 위조 방지 비활성화 설정.

현재 SpringBoot에서 사이트간 요청 위조 방지 비활성화 설정

 


AccessToken을 주지 않았다 ? --> 로그인을 하지 않은 상태라고 볼 수 있다.

확인했으니 다시 주석..

 

# security 패키지에 AppUserDetails.java 파일을 만들어주자(클래스)

#  security 패키지에 AppUserDetailsService.java 파일을 만들어주자(클래스)

==> 교수님 코드 받음

인터페이스의 상속을 받는 클래스들

 

스프링 시큐리티에 대한 정보를 갖고있는 것이 holder 

 

...

다시 컨트롤러에서 리스트를 조회하는 메소드 위에 권한을 ADMIN으로 주어보자

현재 토큰의 권한은 USER 이기 때문에 정보를 얻지 못한다.

 

# Controller에 AuthController 생성

 

 


 

# CORS란?

Cross-Origin Resource Sharing의 줄임말로 웹 브라우저에서 다른 출처의 리소스 공유에 대한 허용/비허용을 다룬 보안 정책이다.

 


BoardController에서 사용했던 @Secured("ROLE_USER") 이 스프링의 몇몇 버전에서 버그가 있다고 한다. 우리가 사용하는 스프링부트 2.7.18에서도 마찬가지..

따라서 @PreAuthorize("hasAuthority('ROLE_USER')") 을 사용하려고 한다.

@Secured는 권한만 줄 수 있고,

@PreAuthorize는 권한에 대한 표현식까지 사용할 수 있다.

따라서 이 표현식을 표현하는 하나의 객체가 필요하다.

 

# WebSecurityConfig.java 파일에서

// @PreAuthorize 어노테이션의 표현식을 해석하는 객체를 등록하는 코드
@Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
  DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
    handler.setRoleHierarchy(roleHierarchy());
    return handler;
}

추가한다.

해석 : 어떠한 메소드를 실행하기 전에(Pre) 권한이 있는지 체크해라!

 

# 회원 가입 및 로그인 처리  ( PPT 41p  )

 

# AuthController --> MemberController로 변경

// 회원가입 및 로그인 처리 -----------------------------------------------------------
@Autowired
private MemberService memberService;

@PostMapping("/join")
public Member join(@RequestBody Member member) {
    // 비밀번호 암호화 하기
    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    // member의 비밀번호 인코더를 이용하여 세팅하기.
    member.setMpassword(passwordEncoder.encode(member.getMpassword()));
    // 아이디 활성화 설정
    member.setMenabled(true);
    // 권한 설정
    member.setMrole("ROLE_USER");
    // 회원 등록하기
    memberService.join(member);
    // JSON형식으로 데이터를 보낼 때, 비밀번호는 보내지 않는다! // 비밀번호 제거
    member.setMpassword(null);
    return member;
}

raw 방식으로 보내보자. (JSON형식의 데이터)

 

이렇게 back-end-REST API를 만들어 보았다.

 


다시 Vue.js 시작....

front-end-vue PPT를 다시 보자.

119p 부터 !!

 

# Axios란 ?

-> node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트

 

Axios란? / Axios 사용 및 서버 통신 해보기!

Axios란? Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리입니다. # Axios란? # Axios사용법 # Axios를 사용해서 백엔드 없이 서버 통신 해보기!

velog.io

 

 

# Promise에 대해 알아보자.

 

1. views 폴더 - Ch08RestAPI 폴더 생성 - Exam01Promise 폴더 생성 - index.vue 생성

2. router 폴더 - Ch08RestAPI.js 파일 생성 후 코드 작성

3. router 폴더 - index.js 파일에 import 와 구조분해할당 방식으로 routes 등록

4. AppMenu 파일에 아코디언 및 리스트 추가.

 

# 비동기에 대해서 명확하게 이해하자!!

function handleBtn1() {
    console.log("handleBtn1() 시작");

    // 비동기 작업 처리 함수 호출
    asyncWork()
        .then((data) => { // promise가 resolve를 호출했을 때
            console.log("성공: ", data);
        })
        .catch((error) => { // promise가 reject를 호출했을 때
            console.log("실패: ", error);
        })
        .finally(() => { // 성공이든, 실패든 마지막 작업 처리 (항상 진행)
            console.log("마무리 작업 실행");
        })
    console.log("handleBtn1() 끝");
}

위의 코드에서 비동기적으로 실행이 된다면 어떻게 처리가 되겠는가?

첫째로 'handleBtn1() 시작' 이 콘솔에 출력되고, 아래의 asyncWokr() 함수의 내용이 비동기적으로 수행이 되고 있다면

'handleBtn1() 끝'을 먼저 콘솔에 출력하고, 그 동안 asyncWork() 함수는 동작을 하고 있으며 작업을 끝마치게 된다. 

 

==> 예를 들어, 우리가 파일 다운로드를 실행하고 다른 페이지를 보고 있을 때처럼 파일 다운로드는 따로 계속적으로 진행중이며 나는 다른 브라우저를 열람할 수 있다!!

 

* index.vue

<template>
    <div class="card">
        <div class="card-header">Exam01Promise</div>
        <div class="card-body">
            <div class="mb-3">
                <button class="btn btn-secondary btn-sm me-2" @click="handleBtn1">비동기 작업 1</button>
                <button class="btn btn-dark btn-sm" @click="handleBtn2">비동기 작업 2</button>
                <hr>
                <div class="mt-3" v-if="loading">
                    <div class="spinner-border text-success" role="status">
                        <span class="visually-hidden">Loading...</span>
                    </div>
                </div>
                <hr>
                <div>
                    <div class="mt-3" v-if="!loading">
                        {{ result }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';
// 상태 정의
const loading = ref(false); // spinner(로딩 UI)를 사용하기 위해 상태를 정의 (v-if와 같이 활용)
const result = ref("결과가 나오는 부분입니다.");  // 결과에 대한 내용을 컴포넌트에 출력해보기

// 비동기 작업 함수 정의
function asyncWork() {
    //3초 후에 응답이 오는 Promise 생성
    const promise = new Promise((resolve, reject) => {
        // 3초 후에, 첫번째 매개값인 람다식이 실행 
        setTimeout(() => {
            // 성공적으로 처리했을 경우 (resolve) -> 3초 후 성공적으로 처리 . 응답결과 반환 
            resolve("성공 응답 내용");
            // 실패 처리할 경우(reject) -> 3초 후 실패
            // reject("실패 응답 내용");
        }, 3000);
    });

    return promise;
}

function handleBtn1() {
    loading.value = true;
    console.log("handleBtn1() 시작");

    // 비동기 작업 처리 함수 호출
    asyncWork()
        .then((data) => { // promise가 resolve를 호출했을 때
            console.log("성공: ", data);
            result.value = data;
        })
        .catch((error) => { // promise가 reject를 호출했을 때
            console.log("실패: ", error);
            result.value = error;
        })
        .finally(() => { // 성공이든, 실패든 마지막 작업 처리 (항상 진행)
            console.log("마무리 작업 실행");
            loading.value = false;
        })
    console.log("handleBtn1() 끝");
}

// -----------------------------------------------------------------

function handleBtn2() {
    console.log("handleBtn2() 시작");
    useAsyncWokr();
    console.log("handleBtn2() 끝");
}

// 'await' 코드가 있는 경우에는 반드시 'async' 코드가 같이와야 한다!! **
async function useAsyncWokr() { // 함수의 앞에 async를 붙여주게 되면 비동기 방식으로 처리하게 된다.
    loading.value = true;
    try {
        const data = await asyncWork(); // 여기에선 Promise가 동기적으로 처리가 된다.
        console.log("성공: ", data);
        result.value = data;
    } catch (error) {
        console.log("실패: ", error);
        result.value = error;
    }
    console.log("마무리 작업 실행");
    loading.value = false;
}

</script>

<style scoped></style>

 

'await' 코드가 있는 경우에는 반드시 'async' 코드가 같이와야 한다!! **

v-if를 활용한 해당 태그와 자식들 안보이게 하기 활용

 

 

우리는 HttpSession과 Redis를 사용하지 않고 ( 권장하지 않음 )

==> 생각해보자. 굳이 세션을 유지해야할 경우가 어떤 경우일까 ?

===> 서버의 부하를 줄이기 위하여 세션과 Redis는 사용하지 않는 것이 좋을지?

 

토큰 기반의 인증 시스템을 사용한다고 하였다.

 

 

다시 앞으로 가서

이 부분을 자세히 살펴보자.

 

Axios는 Promise를 리턴한다.

PATCH도 똑같이 Promise를 리턴. (PATCH를 사용하면 여러가지 설정을 하는데 제약이 있다. PATCH는 데이터를 주거나 받는 것을 목적)

 

Axios는 Vue 내부에 들어가 있는 것이 아니라 별도로 다운받아야한다.

qs는 자바스크립트 객체를 쿼리 스트링으로 바꾸어주는 라이브러리.

 

터미널에서 다운로드를 실행하자.

이러한 글이 나오는데, 저 명령어를 사용하게 되면 어쩌다 버전이 맞지않아 프로젝트 실행이 안될 때가 있다.

따라서 무시하자.

 

src 폴더 아래에 새로운 폴더를 만들어보자. (apis)

axois 설정파일인 axiosConfig.js 생성.

우리가 백엔드에서 만들었던 컨트롤러와 연결지어

memberAPI.js , boardAPI.js 생성

 

*cf) 컨트롤러는 '기능'별로 생성해야한다. (게시판 하나하나를 컨트롤하려고 많이 만들면 안된다..)

 

* axiosConfig.js

import axios from "axios";

// 기본 경로를 설정해야한다.
axios.defaults.baseURL = "http://localhost";

// AccessToken을 받고나서 다음 요청시 전달할 수 있도록 요청 헤더에 추가
// 로그인이 성공했을 때 호출된다.
function addAuthHeader(accessToken) {
    // common 객체에 동적 속성으로 Authorization을 추가
    // accessToken은 헤더행의 속성이름 'Authorization'에 추가된다.
    axios.defaults.headers.common["Authorization"] = "Bearer " + accessToken;
}

// 공통 요청 헤더에서 Authorization 헤더 삭제
// 로그아웃시 호출된다.
function removeAuthHeader() {
    // common 객체의 Authorization 속성을 삭제 (속성을 삭제할 경우 delete를 사용한다.)
    delete axios.defaults.headers.common["Authorization"];
}

// AccessToken을 어디에 저장할지는 만드는 애플리케이션에 따라 다르다.

// 로컬 스토리지에 AccessToken을 저장하는 코드
function saveAccessToken(accessToken) {
    // sessionStorage // 요곤 세션 스토리지 사용할 때 쓰면 된다
    // "accessToken"는 저장하는 Key 이름이다.
    localStorage.setItem("accessToken", accessToken);
}

// 로컬 스토리지에 있는 AccessToken을 읽고 공통 헤더에 추가하는 코드
function readAccessToken() {
    // 저장된 Key이름 "accessToken"을 넣어준다.
    // "accessToken"의 값이 null이라면 뒤에 있는 값 ""이 변수 accessToken의 값으로 들어간다.
    const accessToken = localStorage.getItem("accessToken") || "";
    
    return accessToken; // 이렇게 사용해도 된다. 어차피 목적은 header에 AccessToken을 추가하는 것이기 때문이다.

    /*
    ppt의 내용
    if(accessToken != "") {
        addAuthHeader(accessToken);
     }
     */
}

export default {
    addAuthHeader,
    removeAuthHeader,
    saveAccessToken,
    readAccessToken
}

 

 


memberAPI.js , boardAPI.js 파일들은 다음 수업에 작성해보자.

+ Recent posts