ppt 150p

 

포스트맨을 열어보자.

 

이전에 SpringBoot에서 했던 @Secured 대신 @PreAuthorization 부분. list 빼고 모두 붙여준다.

따라서 로그인한 유저만 list 조회를 제외한 나머지 요청이 가능하게 된다.

 

1. 로그인을 한다

(발급한 토큰을 입력해주어 게시판 읽기를 이용) 

토큰을 발급다고 인증하여야 게시물 읽기가 가능하다.

 

# 게시판 읽기 구현하기

1. Ch08RestAPI - Exam04Board - BoardRead.vue 생성

2. 라우터 등록

3. boardAPI에 boardRead 생성하고 export

// 게시물 읽기
function boardRead(bno) {
    // PathVariable로 데이터 전송
    return axios.get("/board/read/" + bno);
}

export default {
    getBoardList,
    boardWrite,
    boardRead
};

 

* boardRead.vue

<template>
    <div class="card">
        <div class="card-header">BoardRead</div>
        <div class="card-body">
            <div class="row">
                <div class="col-md-6">
                    <p>bno: {{ board.bno }}</p>
                    <p>btitle: {{ board.btitle }}</p>
                    <p>bcontent: {{ board.bcontent }}</p>
                    <p>bwriter: {{ board.bwriter }}</p>
                    <p>bdate: {{ board.bdate }}</p>
                    <p>bhitcount: {{ board.bhitcount }}</p>
                </div>
                <div class="col-md-6"></div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import boardAPI from '@/apis/boardAPI';

// 상태 정의
const board = ref({});

// Query String으로 전달된 bno 얻기
const route = useRoute();
const bno = route.query.bno;

// 해당 bno의 게시물 얻는 함수 정의
async function getBoard(argBno) {
    try {
        const response = await boardAPI.boardRead(argBno); // promise를 리턴
        board.value = response.data;
    } catch (error) {
        console.log(error)
    }
}

// bno에 해당하는 게시물 가져오기
getBoard(bno);

</script>

<style scoped></style>

 

 

# 여기서 로그인을 안한 상태라면 값이 안나온다.

따라서 로그인 안한 유저라면 로그인 창으로 이동하게끔 해보자.

 

 

# 첨부 파일 다운로드 요청

ppt 154p

포스트맨으로 먼저 확인한 후 코드를 작성.

 

# 백엔드 수정

* JwtAuthenticationFilter

package com.mycompany.webapp.security;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter { // 이해 X 그냥 OncePerRequestFilter를 상속받아 정의해야함

//	@Override
//	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
//			throws IOException, ServletException {
//		// AccessToken 얻기
//		String accessToken = null;
//		
//		HttpServletRequest httpServletRequest = (HttpServletRequest)request; // 이 request를 가지고 GetHeader라는 메소드를 사용할 수 없다. 따라서 타입변환을 해주는 것이다.
//		String headerValue = httpServletRequest.getHeader("Authorization");
//		if(headerValue != null && headerValue.startsWith("Bearer")) {
//			accessToken = headerValue.substring(7);
//			log.info(accessToken);
//		}
//		
//		// AccessToken 유효성 검사
////		Jws<Claims> jws = 
//		
//		// 다음 필터를 실행
//		chain.doFilter(request, response);
//	}
	
	@Autowired
	private JwtProvider jwtProvider;
	
	@Autowired
	private AppUserDetailsService userDetailsService;

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
			// AccessToken 얻기
		String accessToken = null;
			
		// 요청 헤더에서 AccessToken 얻기
		HttpServletRequest httpServletRequest = (HttpServletRequest)request; // 이 request를 가지고 GetHeader라는 메소드를 사용할 수 없다. 따라서 타입변환을 해주는 것이다.
		String headerValue = httpServletRequest.getHeader("Authorization");
		if(headerValue != null && headerValue.startsWith("Bearer")) {
			accessToken = headerValue.substring(7);
			log.info(accessToken);
		}
		
		// 6.5
		// 쿼리스트링으로 전달되는 AccessToken 얻기
		// <img src="/board/battach/1?accessToken=xxx" ... > 여기서 xxx값을 얻으려면?
		if(accessToken == null) {
			if(request.getParameter("accessToken") != null) {  // getParameter() 메서드의 활용 방법..
				accessToken = request.getParameter("accessToken");
			}
		}
		
		// accessToken이 null일 경우에는 로그인하지 않았기 때문에 user의 id를 얻을 수 없다.
		if(accessToken != null) {
			// AccessToken 유효성 검사
			Jws<Claims> jws = jwtProvider.validateToken(accessToken);
			if(jws != null) {
				// 유효한 경우
				log.info("AccessToken이 유효함");
				String userId = jwtProvider.getUserId(jws);
				log.info("userId : " + userId);
				
				// userDetail 얻어야함 // @Autowired로 주입받기 // 사용자의 상세정보 얻기.
				UserDetails userDetails = (UserDetails) userDetailsService.loadUserByUsername(userId);
				// 인증 객체 얻기
				Authentication authentication = 
						new UsernamePasswordAuthenticationToken(userId, null, userDetails.getAuthorities());
				// 스프링 시큐리티에 인증 객체 설정
				SecurityContextHolder.getContext().setAuthentication(authentication);
			} else {
				// 유효하지 않은 경우
				log.info("AccessToken이 유효하지 않음");
			}
		}
		
		// 다음 필터를 실행
		filterChain.doFilter(request, response);
	}

}

 

* boardRead.vue 는 계속적으로 수정 // 백엔드 작업과 동시에

<template>
    <div class="card">
        <div class="card-header">BoardRead</div>
        <div class="card-body">
            <div class="row">
                <div class="col-md-6">
                    <p>bno: {{ board.bno }}</p>
                    <p>btitle: {{ board.btitle }}</p>
                    <p>bcontent: {{ board.bcontent }}</p>
                    <p>bwriter: {{ board.bwriter }}</p>
                    <p>bdate: {{ board.bdate }}</p>
                    <p>bhitcount: {{ board.bhitcount }}</p>
                </div>
                <div class="col-md-6">
                    <!-- 첨부 파일 다운로드 -->
                    <!-- 방법 1 (이미지가 하나일 때를 가정 )-->
                    <!-- <img v-if="battach != null" :src="battach" width="300"> -->

                    <!-- 방법 2 (이미지가 여러개일 경우를 가정) -->
                    <!-- <img v-if="battach != null" :src="`http://localhost/board/battach/${bno}`" width="300"> -->
                    <!-- axios.defaults.baseURL을 이용하여 경로가 바뀌었을 때 하나만 수정할 수 있도록 변수로 설정 -->
                    <!-- <img v-if="battach != null" :src="`${axios.defaults.baseURL}/board/battach/${board.bno}`"
                        width="300"> -->
                    <img v-if="battach != null"
                        :src="`${axios.defaults.baseURL}/board/battach/${board.bno}?accessToken=${$store.state.accessToken}`"
                        width="300">
                </div>
            </div>
            <div class="mt-3">
                <button class="btn btn-info btn-sm me-2" @click="goBoardList">목록</button>
                <span v-if="$store.state.userId === board.bwriter">
                    <button class="btn btn-warning btn-sm me-2" @click="goBoardUpdate">수정</button>
                    <button class="btn btn-danger btn-sm" @click="handleRemove">삭제</button>
                </span>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import boardAPI from '@/apis/boardAPI';
import { useStore } from 'vuex';
import axios from 'axios';

// 상태 정의
const board = ref({});

// Query String으로 전달된 bno 얻기
const route = useRoute();
const bno = route.query.bno;
const pageNo = route.query.pageNo;

// 첨부 파일 다운로드 구현하기
const battach = ref(null);

// 해당 bno의 게시물 얻는 함수 정의
async function getBoard(argBno) {
    try {
        const response = await boardAPI.boardRead(argBno); // promise를 리턴
        board.value = response.data;  // 해당 게시물의 첨부파일은 따로 가져오는 것이 좋다.

        // 게시물 첨부 파일 유무와 관련하여
        if (board.value.battachoname != null) {
            getAttach(argBno);  // 밑의 함수. 두 함수는 비동기 함수이며 각각 비동기적으로 작업을 수행하게 된다
        }
    } catch (error) {
        console.log(error)
    }
}

// 첨부 파일 화면에 다운로드
async function getAttach(argBno) {
    try {
        const response = await boardAPI.boardAttachDownload(argBno); // promise를 리턴
        const blob = response.data;
        // battach.value = blob; // 이렇게 하면 img태그에서 blob을 바로 처리하지 못한다.
        battach.value = URL.createObjectURL(blob);
        // 이렇게 변환하여 사용해야 한다. (참고 : https://developer.mozilla.org/ko/docs/Web/API/URL/createObjectURL_static )
    } catch (error) {
        console.log(error)
    }
}

// async function getAttach(argBno) {
//     try {
//         const response = await boardAPI.boardAttachDownload(argBno);
//         const blob = response.data;
//         battach.value = URL.createObjectURL(blob);
//     } catch (error) {
//         console.log(error);
//     }
// }



// bno에 해당하는 게시물 가져오기
getBoard(bno);

// store 객체 얻기
const store = useStore();

const router = useRouter();

if (store.state.userId !== '') {
    // bno에 해당하는 게시물 가져오기
    getBoard(bno);
} else {
    router.push("/Ch08RestAPI/Exam03Login");
}

function goBoardList() {
    router.push(`/Ch08RestAPI/Exam04Board/BoardList?pageNo=${pageNo}`);
}

function goBoardUpdate() {
    // router.push(`/Ch08RestAPI/Exam04Board/BoardUpdate?bno=${board.value.bno}`);
    router.push(`/Ch08RestAPI/Exam04Board/BoardUpdate?bno=${bno}&pageNo=${pageNo}`);
}

async function handleRemove() {
    try {
        await boardAPI.boardDelete(bno); // promise를 리턴
        router.back();  // 이전에 있던 라우트링크로 돌아간다 (게시물 목록)
    } catch (error) {
        console.log(error)
        // 다이얼로그 띄워주고 안된다고 알려줌

    }
}

</script>

<style scoped></style>

 

 

* BoardService.java

package com.mycompany.webapp.service;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.mycompany.webapp.dao.BoardDao;
import com.mycompany.webapp.dto.Board;
import com.mycompany.webapp.dto.Pager;

@Service
public class BoardService {
   @Autowired
   private BoardDao boardsDao;
   
   public int insert(Board board) {
      return boardsDao.insert(board);
   }

   public int getCount() {
      return boardsDao.count();
   }

   public List<Board> getList(Pager pager) {
      return boardsDao.selectByPage(pager);
   }
   
   public Board getBoard(int bno) {
	  // hitcount 올려주기
	   boardsDao.updateBhitcount(bno);
      return boardsDao.selectByBno(bno);
   }
   
   public void updateBhitcount(int bno) {
	   boardsDao.updateBhitcount(bno);
   }

   public int update(Board board) {
      return boardsDao.updateByBno(board);
   }

   public int delete(int bno) {
      return boardsDao.deleteByBno(bno);
   }
   
   public int addHitcount(int bno) {
      return boardsDao.updateBhitcount(bno);
   }
}

 

+ Recent posts