본문 바로가기

SpringBoot

[연재] Spring Boot diary : 접근보안 (feat. Postman)

728x90
반응형

원문은 Spring Boot: study.diary 접근보안 (feat. Postman) 을 참고하세요.

 

Spring Boot 를 이용해서 학습차원에서 개발해보고 있는 diary 프로그램이 프로그램으로서의 기본 골격은 갖추었다고 생각했어.

그런데, A 라는 사용자가 로그인해서 B 사용자가 작성한 일기를 볼 수 있다거나, 편집할 수 있는 허점이 있어. 그리고 일단 사용자로 등록한 후에 비밀번호를 변경하는 작업 역시 불가능한 상태야.

이런 보안상 허점이라거나 미진한 기능들을 한꺼번에 모아서 처리해볼께.

일기 작성자 확인하기

사용자를 한 명 더 추가해봤어. 추가된 사용자의 이메일주소는 klist02@naver.com 이야.


이 사용자로 로그인해서 작성한 일기 데이터는 고유값 id 가 19 번 하나야.


기억날지 모르지만 Rest API 로 /diary/{id} 를 사용하면 고유값 {id} 에 해당하는 일기 데이터를 불러올 수 있도록 작성했었지.


klist02@naver.com 사용자가 작성한 것이 아닌 id 고유값 18 번의 데이터를 가지고와볼께. klist02@naver.com 으로 로그인한 상태인 것을 잊지마.

로그아웃된 상태에서 웹브라우저에 http://localhost:8080/diary/18 를 입력하면 로그인되지 않은 상태이기 때문에 로그인화면이 표시가 돼.

여기에 로그인하면 다시 http://localhost:8080/diary/18 주소값으로 이동해서 데이터가 보이게 되지.


큰일이네. klist02@naver.com 이 로그인해서 주소를 직접 입력하는 방식으로 다른 사람의 일기를 훔쳐볼 수 있다니.

그래서 일기데이터를 읽고, update 하고, delete 할 때마다 해당 일기 데이터에 저장된 이메일 주소와 현재 로그인한 사용자의 이메일 주소를 비교해서 일치하는 경우에만 action 이 가능해지도록 수정하려고 해.

기존에 로그인한 정보를 가져오는 로직을 작성한 곳이 DiaryService 였으니까 이번에도 여기를 수정해볼께.

우선 일기 데이터를 읽어와서 현재 로그인한 사용자의 email 주소와 일치하는지를 확인할거야.

DiaryService.java
@Service
public class DiaryService {
    ...
    public Diary GetDiary(Integer id) {
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        Diary diary = diaryMapper.GetDiary(id);
        if (!diary.getEmail().equals(email)) {
            throw new DiaryException("작성자가 아닙니다.");
        }
        return diary;
    }
    ...
}

새로 등장한 DiaryException 을 아래와 같이 정의해주면 돼.

DiaryException.java
package com.woohahaapps.study.diary.exception;

public class DiaryException extends RuntimeException {
    public DiaryException() {}
    public DiaryException(String message) {
        super(message);
    }
}

이제 앞에서처럼 http://localhost:8080/diary/18 로 이동해서 klist02@naver.com 으로 로그인하게 되면 아래처럼 예외가 표시될거야.


대신 klist02@naver.com 이 작성한 id 고유값 19 번은 정상적으로 읽어와지지.


DiaryService 의 GetDiary 에 작성한 로직을 UpdateDiary 와 DeleteDiary 에도 작성해주면 되겠네.

@Service
public class DiaryService {
    ...
    public void UpdateDiary(Integer id, String diary_date, String diary_content) {
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        Diary diary = diaryMapper.GetDiary(id);
        if (!diary.getEmail().equals(email)) {
            throw new DiaryException("작성자가 아닙니다.");
        }
        diaryMapper.UpdateDiary(id, diary_date, diary_content);
    }

    public void DeleteDiary(Integer id) {
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        Diary diary = diaryMapper.GetDiary(id);
        if (!diary.getEmail().equals(email)) {
            throw new DiaryException("작성자가 아닙니다.");
        }
        diaryMapper.DeleteDiary(id);
    }
    ...
}

이제 수정한 로직을 테스트해볼건데, Postman 이라는 앱을 이용해보려고 해. PUT 이나 DELETE 메소드는 웹브라우저만으로는 테스트하기가 어렵기 때문이야. Postman 앱은 https://www.postman.com/downloads/ 에서 다운로드받을 수 있어.

Postman

우선 로그인을 해야만 diary 프로그램을 사용할 수 있기 때문에 Postman 에서 로그인폼을 호출해볼께.

로그인폼 URL 은 http://localhost:8080/login 인데, GET 방식으로 호출하면 돼.

"Send" 버튼을 클릭하면 아래쪽에 결과가 표시가 되지.


결과는 로그인폼의 소스인데, 조금 스크롤해보면 form 태그 안에 name="_csrf" 가 보여. 그 값을 일단 복사해둘께. 이 값은 로그인처리를 하는 URL 에 그대로 전송시켜야 하거든.

이번에는 실제 로그인을 처리해보자. URL 에 http://localhost:8080/process_login 을 입력하고 method 를 POST 로 변경시켜보자.


전달할 파라미터를 입력하기 위해서 Body 탭을 선택하고 email, password, _csrf 를 추가해줘. _csrf 키의 값으로 조금 전에 복사해둔 값을 입력한 다음에 "Send" 버튼을 눌러보자.


아래쪽 결과를 실제 웹페이지처럼 보고 싶으면 Preview 탭을 선택해주면 돼.

이제 woohaha@gmail.com 으로 로그인한 상황이 되었어.

이 상태에서 klist02@naver.com 이 작성한 일기 고유번호 19 번을 Update 하는 시도를 해볼께. 일단 일기수정 폼을 호출해보자.

GET 방식으로 http://localhost:8080/diary/edit/19 URL 을 호출하면 아래와 같이 작성자가 아닙니다 라는 오류 메시지가 리턴되는걸 확인할 수 있어.


일기데이터 수정은 edit 폼에서 PUT method 로 http://localhost:8080/diary/19 를 호출해야 하는데, 여기에서부터 막혔으니까 잠시 가능하도록 풀어줄께.

@Service
public class DiaryService {
    ...
    public Diary GetDiary(Integer id) {
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        Diary diary = diaryMapper.GetDiary(id);
        if (!diary.getEmail().equals(email)) {
            //throw new DiaryException("작성자가 아닙니다.");
        }
        return diary;
    }
    ...
}

코드를 수정했으니, 프로그램을 재실행해서 로그인 과정부터 다시 수행해야 해. 그리고 일기수정 폼까지 무리없이 진행되는걸 확인할 수 있을거야.

PUT method 를 테스트하기 전에 일기수정 폼에 들어있는 _csrf 의 속성값을 사용해야 해.


리턴된 결과에서 DiaryService 의 UpdateDiary 에서 Exception 이 발생했다는걸 확인할 수가 있어.

이어서 DELETE method 도 테스트해보자.

일기데이터 수정 폼을 호출해서 _csrf 속성값을 가져와서

DELETE method 의 Header 에 X-CSRF-TOKEN 키를 추가하고 값으로 설정하면 돼.

그리고나서 "Send" 버튼을 클릭하면 Exception 내용을 확인할 수가 있어.

반응형