[Spring] 기존 프로젝트에 스프링 시큐리티 접목
이전 글
2021.10.04 - [Spring] - [Spring] 어노테이션을 이용하는 스프링 시큐리티 설정
2021.10.04 - [Spring] - [Spring] 스프링 시큐리티 Java 설정
2021.10.04 - [Spring] - [Spring] 자동 로그인(remember-me)
2021.10.04 - [Spring] - [Spring] 스프링 시큐리티를 JSP에서 사용하기
2021.10.03 - [Spring] - [Spring] 스프링 시큐리티, 커스텀 테이블, 커스텀 UserDetailsService 활용
2021.10.01 - [Spring] - [Spring] 스프링 시큐리티 로그인 (커스텀 데이터베이스 사용)
2021.09.30 - [Spring] - [Spring] JDBC를 이용하는 간편 인증/권한 처리
2021.09.27 - [Spring] - CSRF(Cross-site request forgery) 공격과 토큰 / 로그인 처리 / 로그아웃 처
2021.09.27 - [Spring] - [Spring] 스프링 시큐리티를 사용하는 커스텀 로그인 페이지
2021.09.25 - [Spring] - [Spring] 스프링 시큐리티 간단한 로그인과 로그아웃 처리
2021.09.24 - [Spring] - [Spring] Spring Web Security의 설정
2021.09.24 - [Spring] - [Spring] Spring Web Security
기존 프로젝트에 스프링 시큐리티를 접목하는 작업 순서
- 로그인과 회원 가입 페이지의 작성
- 기존 화면과 컨트롤러에 시큐리티 관련 내용 추가
- Ajax 부분의 변경
스프링 시큐리티 설정에 사용한 코드들 추가 (이전 글 참고)
- security-context.xml (or SecurityConfig), MemberVO, AuthVO 추가
- web.xml에 security-context.xml 설정 및 필터 추가 (or AbstractSecurityWebApplicationInitializer 를 상속받은 SecurityInitializer 추가)
- MemberMapper 인터페이스와 MemberMapper.xml 추가
- CommonController 추가
로그인 페이지 처리
로그인에 대한 처리는 앞에서 "/customLogin"으로 처리해 두었기 때문에 customLogin.jsp에 스프링 시큐리티 관련 항목들을 추가합니다.
customLogin.jsp의 일부
주의사항
- css, js 파일 링크는 절대 경로 사용
- <form> 태그 내 <input> 태그 name 속성 스프링 시큐리티에 맞게 수정 (중요!)
- CSRF 토큰 항목 추가
- JavaScript를 통한 로그인 전송
로그인 테스트
로그인 후 다음 페이지가 나타나는 것은 기존 소스코드 CustomLoginSuccessHandler를 이용했기 때문입니다.
스프링 시큐리티는 기본적으로 로그인 후 처리를 SavedRequestAwareAuthenticationSuccessHandler라는 클래스를 이용합니다. 해당 클래스는 이용자가 원래 보려고 했던 페이지의 정보를 유지해서 로그인 후에 다시 원했던 페이지로 이동하는 방식입니다.
SavedRequestAwareAuthenticationSuccessHandler를 이용하는 설정은 기존에 XML이나 Java 설정에서 'authentication-success-handler-ref' 속성이나 successHandler( ) 메서드를 삭제하고 관련 스프링 빈의 설정도 사용하지 않도록 합니다.
security-context.xml의 경우
SecurityConfig 클래스의 경우
게시물 작성 시 스프링 시큐리티 처리
System Flow 1
게시물 목록 -> 게시물 작성으로 이동 -> 로그인 창(로그인 하지 않은 경우) -> 로그인 -> 게시물 작성화면으로 이동
System Flow 2
게시물 목록 -> 게시물 작성으로 이동(로그인 사용자의 경우)
BoardController의 메서드의 일부는 스프링 시큐리티 어노테이션을 추가합니다.
BoardController 클래스의 일부
@PreAuthorize를 이용할 때의 표현식 isAuthenticated() 으로 어떠한 사용자든 로그인이 성공한 사용자만 해당 기능을 사용할 수 있도록 처리합니다.
게시물 작성 시 로그인한 사용자의 아이디 출력
게시물 작성은 로그인한 사용자들만 허락되므로, 작성자(writer) 항목에 현재 사용자 아이디가 출력될 수 있게 수정합니다.
register.jsp의 일부
register.jsp와 같이 스프링 시큐리티의 영향을 받는 JSP 파일에는 시큐리티 관련 태그 라이브러리를 설정합니다. 작성자(writer)에 현재 로그인한 사용자의 아이디를 출력합니다. 스프링 시큐리티에서는 username이 사용자의 아이디입니다.
CSRF 토큰 설정
스프링 시큐리티를 사용할 때 POST 방식의 전송은 반드시 CSRF 토큰을 사용하도록 추가해야만 합니다. <form> 태그 내에 CSRF 토큰의 값을 <input type="hidden"> 으로 추가합니다.
register.jsp의 일부
화면에서 개발자 도구로 소스 보기
브라우저에서 게시물이 정상적으로 등록되는지 확인합니다.
스프링 시큐리티 한글 처리
게시물 등록에서 스프링 시큐리티를 적용한 후 한글이 깨지는 문제가 발생할 수 있습니다. 한글 처리는 web.xml을 이용해서 스프링의 CharacterEncodingFilter를 이용해 처리하지만, 시큐리티를 필터로 적용할 때에는 필터의 순서를 주의해서 설정해야 합니다.
web.xml의 일부 - 인코딩 설정 먼저 적용하고, 스프링 시큐리티 적용
SecurityConfig 클래스의 일부
Java Config의 경우 다음 인코딩 적용 코드를 추가합니다. 참고 글 : https://coco-log.tistory.com/46
게시물 조회와 로그인 처리
게시물 조회 화면에서 로그인한 사용자만 수정/삭제 작업을 할 수 있는 기능이 활성화될 필요가 있습니다.
게시물 조회 화면에서 현재 게시물의 작성자와 현재 로그인한 사용자 정보를 비교해서 이를 처리하도록 수정합니다.
get.jsp의 상단에 태그 라이브러리 추가
get.jsp의 수정 버튼 부분 수정
<sec:authentication> 태그를 매번 이용하는 것은 불편하기 때문에 로그인과 관련된 정보인 principal은 아예 JSP 내에서 pinfo라는 이름의 변수로 사용하도록 합니다. <sec:authorize>는 인증받은 사용자만이 영향을 받기 위해서 지정하고, 내부에서는 username과 게시물의 writer가 일치하는지 확인해서 'Modify' 버튼을 추가합니다. 브라우저에는 자신이 작성한 게시물만 'Modify' 버튼이 보이게 됩니다.
조회 화면에서 댓글 추가 버튼
로그인한 사용자만이 조회 화면에서 댓글을 추가할 수 있으므로, <sec:authorize>를 이용해 댓글 버튼의 활성화/비활성화도 처리하도록 합니다.
get.jsp의 일부 : 댓글 추가 버튼 부분
게시물의 수정/삭제
게시물의 수정과 삭제는 화면과 POST 방식으로 처리되는 부분에서 CSRF 토큰과 스프링 시큐리티를 적용합니다.
게시물의 수정과 삭제는 현재 로그인한 사용자와 게시물의 작성자가 동일한 경우에만 할 수 있다는 것입니다. 이 처리를 과거에는 인터셉터로 처리했지만, @PreAuthorize의 경우에는 표현식으로 처리할 수 있습니다.
modify.jsp 상단에 시큐리티 태그 라이브러리를 추가합니다.
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
POST 방식으로 처리되는 부분에는 CSRF 토큰을 추가합니다.
modify.jsp 의 일부
조회와 마찬가지로 현재 로그인한 사용자가 게시물의 작성자인 경우에 수정과 삭제가 가능하도록 제어합니다.
BoardController에서의 제어
BoardController에서는 메서드를 실행하기 전에 로그인한 사용자와 현재 파라미터로 전달되는 작성자가 일치하는지 체크합니다. @PreAuthorize의 경우 문자열로 표현식을 지정할 수 있는데 이때 컨트롤러에 전달되는 파라미터를 같이 사용할 수 있으므로 유용합니다.
삭제의 경우 기존에는 파라미터로 게시물 번호 seq_bno만을 받았지만, 작성자를 의미하는 writer를 같이 추가해서 @PreAuthorize로 검사하도록 합니다.
BoardController 클래스의 일부
기존과 달라진 부분은 writer가 추가된 부분과 해당 파라미터를 @PreAuthorize에서 '#writer'를 이용해 체크한 부분입니다. 게시물의 수정은 Board 타입의 객체로 받도록 설계되어 있으므로 다음과 같이 변경합니다. (#board.writer)
BoardController 클래스의 일부
Ajax와 스프링 시큐리티 처리 (feat. REST & 첨부파일 관련)
스프링 시큐리티가 적용되면 POST, PUT, PATCH, DELETE와 같은 방식으로 데이터를 전송하는 경우에는 반드시 추가적으로 'X-CSRF-TOKEN'와 같은 헤더 정보를 추가해서 CSRF 토큰값을 전달하도록 수정해야만 합니다. Ajax는 JavaScript를 이용하기 때문에 브라우저서는 CSRF 토큰과 관련된 값을 변수로 선언하고, 전송 시 포함시켜주는 방식으로 수정합니다.
게시물 등록 시 첨부파일의 처리
스프링 시큐리티가 적용된 후 게시물 파일 첨부가 정상적으로 동작하지 않는 것을 알 수 있는데 게시물 등록이 POST 방식으로 전송되기 때문에 발생하는 문제입니다. 게시물 등록 페이지에서 javascript를 수정합니다.
register.jsp의 일부
기존 코드에 CSRF 토큰 header, tokenValue를 추가합니다. 웹 소스에선 다음과 같이 생성됩니다.
Ajax로 데이터를 전송할 때 beforeSend를 이용해 추가적인 헤더를 지정해서 전송합니다.
첨부파일의 제거
첨부파일의 등록과 마찬가지로 삭제하는 경우도 POST 방식으로 동작하기 때문에 마찬가지로 CSRF 토큰의 처리가 필요합니다.
register.jsp의 일부
UploadController의 수정
브라우저에서 로그인한 사용자만이 업로드가 가능하지만 필요하다면 서버 쪽에서도 어노테이션 등을 이용해서 업로드 시 보안을 확인할 수 있습니다.
UploadController 클래스의 일부
첨부파일의 등록과 삭제는 외부에서 로그인한 사용자만이 할 수 있도록 제한합니다.
게시물 수정/삭제에서 첨부파일의 처리
게시물의 수정 화면에서도 첨부파일은 추가되거나 삭제가 가능하므로 코드를 수정합니다.
댓글 기능에서의 Ajax
위 내용을 참고해서,
서버에서 처리할 것
1. 댓글 등록은 로그인한 유저만 댓글 추가 가능
2. 댓글 수정과 삭제는 '로그인 유저', '댓글 작성자 아이디'를 비교해서 같은 경우만 댓글 수정/삭제 가능
화면에서 처리할 것
1. CSRF 토큰 전송할 것.
2. 댓글 수정/삭제 : 댓글 번호 및 댓글 작성자를 같이 전송하도록 수정.
댓글 등록
댓글 등록은 로그인한 사용자가 댓글 작성자가 되어야 하므로, 댓글 작성자를 JavaScript의 변수로 설정합니다.
csrf 토큰에 대한 처리를 위해 관련 변수들도 작성합니다. (변수명 : csrfHeaderName, csrfTokenValue)
get.jsp의 일부
댓글을 보여주는 모달창에는 현재 로그인한 사용자의 이름으로 replyer 항목이 고정되도록 수정합니다.
jQuery를 이용해 Ajax로 CSRF 토큰을 전송하는 방식은 첨부파일의 경우 beforeSend를 이용했지만, 기본 설정으로 지정해서 사용하는 것이 더 편하기 때문에 아래 코드를 사용합니다.
get.jsp의 일부
ajaxSend( )를 이용한 코드는 모든 Ajax 전송 시 CSRF 토큰을 같이 전송하도록 세팅되기 때문에 매번 Ajax 사용 시 beforeSend를 호출하지 않아도 됩니다. ReplyController는 댓글 등록이 로그인한 사용자인지를 확인하도록 합니다.
ReplyController 클래스의 일부
브라우저에 새로운 댓글을 추가하려고 하면 댓글 작성자(replyer)는 고정된 형태로 보이고, 전송 시 CSRF 토큰이 같이 전송됩니다.
댓글 삭제
댓글 삭제는 모달창에서 댓글 작성자 정보와 현재 로그인한 사용자가 같은지를 비교해서 같은 경우에만 Ajax로 댓글을 삭제할 수 있도록 합니다. 만일 자신이 작성한 댓글이 아닌 경우나 로그인하지 않은 경우는 삭제할 수 없도록 제한해야 합니다.
get.jsp의 일부
originalReplyer가 추가된 후에는 resources 폴더 내의 js/reply.js에서 seq_rno와 replyer를 같이 전송하도록 수정해야 합니다.
/resources/js/reply.js 의 일부
reply.js의 remove는 replyer를 추가적으로 파라미터로 지정하고, 데이터 전송 시 JSON 타입으로 전송하도록 변경합니다. ReplyController는 JSON으로 전송되는 데이터를 처리하도록 아래와 같이 수정합니다.
ReplyController 클래스의 일부
기존 코드와 비교해보면 어노테이션이 추가되었고, 파라미터가 @RequestBody가 적용되어 JSON으로 된 데이터를 받도록 수정되었습니다.
브라우저를 통해 댓글이 정상적으로 삭제되는지 확인합니다.
댓글 수정
댓글 수정은 댓글 내용과 댓글 작성자가 같이 전송되도록 수정합니다.
get.jsp의 일부
ReplyController에서는 어노테이션 처리가 추가됩니다.
JSON 데이터가 전송되는지 확인하고, 서버에서 로그를 확인합니다.
프로젝트에 스프링 시큐리티를 접목하는 과정을 알아보았습니다.
'java, spring' 카테고리의 다른 글
List에서 stream()을 사용하여 특정 값을 가진 객체들만 제거하기 (2) | 2021.11.04 |
---|---|
[Spring] 스프링 시큐리티를 이용해 로그아웃 처리 (0) | 2021.10.07 |
[Spring] 어노테이션을 이용하는 스프링 시큐리티 설정 (0) | 2021.10.04 |
[Spring] 스프링 시큐리티 Java 설정 (0) | 2021.10.04 |
[Spring] 자동 로그인(remember-me) (0) | 2021.10.04 |