본문 바로가기

Server Programming/Spring Boot Backend Programming

8장-2. 로그아웃과 자동 로그인 처리 (+ 인증된 사용자 처리, currentUser, AccessDeniedHandler)

반응형

요구사항

  • 로그아웃
  • 자동 로그인
  • 컨트롤러와 화면에서 인증처리
    • 로그인 유무와 권한에 따른 제어
    • 컨트롤러 인증된 정보 활용하는 경우의 처리

로그아웃

스프링 시큐리티는 HttpSession을 이용해 로그인을 수행하기 때문에 쿠키를 삭제하게 된다면 세션이 종료되고, 자동으로 로그아웃

 

로그인부터 로그아웃까지 동작 과정

  1. 입력한 아이디로 해당 아이디의 사용자 정보를 가져온다.
  2. 가져온 사용자의 비밀번호와 입력한 비밀번호가 일치하면 세션을 성립하고, 인증(로그인) 처리한다.
  3. 쿠키를 삭제하면 성립한 세션이 종료되고 자동으로 로그아웃처리가 된다.


CSRF 토큰을 사용하지 않는 경우 GET방식의 로그아웃 처리가 가능

 

(1) GET방식의 로그인 메서드에서 로그아웃 여부 확인

- '/logout' 경로 호출시 -> 'login?logout' 경로로 이동

-이를 이용해 로그아웃 여부 확인

@GetMapping("/login")
//로그인 과정에서 문제 발생 시, 로그아웃 시 사용하기 위한 파라미터
public void loginGET(String error, String logout){
    log.info("login get...");
    log.info("logout : "+logout);
    
    if(logout!=null){
        log.info("user logout...");
    }

 

(2) login.html에서 '/member/login/?logout'인 경우 로그아웃 메시지 출력

-logout이 null이 아니면, 로그아웃 페이지

-logout이 null이면, 로그인하도록 로그인 페이지

<div class="card-body">
            <!-- 문자열 로그아웃이 null이 아니면 로그아웃 메시지 출력-->
            <th:block th:if="${param.logout != null}">
                <h1>Logout........</h1>
            </th:block>

            <!-- 문자열 로그아웃이 null이면 로그인 화면 출력-->
            <form id="registerForm" action="/member/login" method="post" th:if="${param.logout==null}">
                <div class="input-group mb-3">
                    <span class="input-group-text">아이디</span>
                    <input type="text" name="username" class="form-control" placeholder="USER ID">
                </div>
                <div class="input-group mb-3">
                    <span class="input-group-text">패스워드</span>
                    <input type="text" name="password" class="form-control" placeholder="PASSWORD">
                </div>
<!--                <div class="input-group mb-3 ">-->
<!--                    <input class="form-check-input" type="checkbox" name="remember-me">-->
<!--                    <label class="form-check-label">-->
<!--                        자동 로그인-->
<!--                    </label>-->
<!--                </div>-->
                <div class="my-4">
                    <div class="float-end">
                        <button type="submit" class="btn btn-primary submitBtn">LOGIN</button>
                    </div>
                </div>
            </form>
<!--            <a href="/oauth2/authorization/kakao">KAKAO</a>-->
        </div><!--end card body-->

-> <th:block> : 블록 처리를 담당하는 태그로 태그안에 자체 태그를 추가하는 태그


자동 로그인

 

스프링 시큐리티의 'remember-me' 기능

: 쿠키를 이용해 브라우저에 로그인한 정보를 기억해 로그인 상태를 유지하는 기능

 

기존 방법 스프링 시큐리티
HttpSession 쿠키
세션이 성립하는 기간동안 유지 쿠키에 유효기간을 지정해 브라우저가 쿠키를 저장
(로그인 관련 정보를 유지)

 

1. 커스텀 로그인 페이지를 이용하는 경우에 필요한 설정

-쿠키를 저장하는 테이블 생성 (추후에 토큰을 이용한 JWT로 전환)

-persistent_logins 테이블 생성

create table persistent_logins
(
    username  varchar(64) not null,
    series    varchar(64) primary key,
    token     varchar(64) not null,
    last_used timestamp   not null
)

 

2. 자동 로그인 설정 변경

 

(1) CustomSecurityConfig에서 쿠키를 발행하도록 내용을 수정

-쿠키 관련 정보를 테이블로 보관하도록 지정

-DataSource, UserDetailsService 객체를 이용해 쿠키 정보를 저장

  • 메서드 명 : CustomSecurityConfig
  • 의존성 주입 객체 : DataSource, CustomUserDetailsService
  • 빈으로 등록 : SecurityFilterChain, WebSecurityCustomizer, PersistentTokenRepository

 

  • 쿠키 생성하기 위한 요소
    • 쿠키 값을 인코딩하기 위한 키 값
    • 필요한 정보를 저장하는 tokenRepository 지정
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    log.info("---configure---");

    //별도의 로그인 페이지를 이용해 로그인 처리하도록 변경
    http.formLogin().loginPage("/member/login");
    
    //remember-me 기능을 위한 쿠키 생성
    http.rememberMe()
            .key("12345678")
            .tokenRepository(persistentTokenRepository())
            .userDetailsService(userDetailsService)
            .tokenValiditySeconds(60*60*24*30);

    return http.build();
}
  • rememberMe()
    • key
    • tokenRepository
    • userDetailsService
    • tokenValiditySeconds

 

//쿠키 저장을 위한 메서드
@Bean
public PersistentTokenRepository persistentTokenRepository(){
    JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
    repo.setDataSource(dataSource);
    return repo;
}

 

(2) remember-me 기능 활성화를 위해 로그인 화면에서 'remember-me' 이름의 값을 전달하기 위한 체크박스 추가

<!-- 문자열 로그아웃이 null이면 로그인 화면 출력-->
<form id="registerForm" action="/member/login" method="post" th:if="${param.logout==null}">
    <div class="input-group mb-3">
        <span class="input-group-text">아이디</span>
        <input type="text" name="username" class="form-control" placeholder="USER ID">
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">패스워드</span>
        <input type="text" name="password" class="form-control" placeholder="PASSWORD">
    </div>
    <div class="input-group mb-3 ">
        <input class="form-check-input" type="checkbox" name="remember-me">
        <label class="form-check-label">
            자동 로그인
        </label>
    </div>
    <div class="my-4">
        <div class="float-end">
            <button type="submit" class="btn btn-primary submitBtn">LOGIN</button>
        </div>
    </div>
</form>

 


컨트롤러와 화면에서 인증 처리

  • 로그인 유무와 권한에 따른 제어
  • 컨트롤러 인증된 정보 활용하는 경우의 처리
화면 컨트롤러
게시물 작성자만 수정/삭제 버튼 노출
게시물 작성시, 사용자의 정보 세팅
현재 로그인한 사용자 저어보와 게시물 작성자가 같을 때만 삭제 처리
URL을 통해 권한이 없거나 인가가 없는 사용자의 부적절한 접근 차단

타임리프에서  인증 정보 활용

  1. 게시물 CRUD
  2. AccessDeniedHandler

타임리프 스프링 시큐리티 라이브러리 의존성 추가

-타임리프 의존성 추가되어있는지 확인

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
//타임리프 스프링 시큐리티
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'

 

게시물 CRUD

1. 게시물 등록 작업

 

(1) 자바스크립트에서 게시물 등록 인증 정보 생성

//현재 사용자 정보 생성
const auth =[[${#authentication.principal}]]
const errors = [[${errors}]]

console.log(errors)

auth 정보 : password, username, authorities, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled

[Username=user1, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_USER]]

 

(2) 사용자 정보를 가져오기 위해 타임리프 스프링 시큐리티 네임스페이스 추가

 

<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
      layout:decorate="~{layout/basic.html}">

 

(3) 게시물 작성자 부분을 현재 로그인한 사용자 아이디로 처리

 

변경 전,

<!--작성자-->
<div class="input-group mb-3">
  <div class="input-group-text">Writer</div>
  <input type="text" name="writer" class="form-control" placeholder="Writer">
</div>

 

변경 후,

 <div class="input-group mb-3">
  <div class="input-group-text">Writer</div>
  <input type="text" name="writer" class="form-control" placeholder="Writer" sec:value="${#authentication.principal.username}" readonly>
</div>

2. 게시물 조회 작업

 

(1) BoardController에서 로그인한 사용자만 조회할 수 있도록 수정

-@PreAuthorize isAuthenticated() 표현식으로 로그인한 사용자만 제한

@PreAuthorize("isAuthenticated()")
@GetMapping({"/read", "/modify"})
public void read(Long bno, PageRequestDTO pageRequestDTO, Model model){
    BoardDTO boardDTO=boardService.readOne(bno);
    log.info(boardDTO);

    model.addAttribute("dto", boardDTO);
}

 

 

게시물 조회시 인증 유무에 따른 처리

  • 로그인한 사용자
    • 조회 페이지
  • 로그인하지 않는 사용자
    • 로그인 페이지

 

(2) read.html 처리

-현재 로그인한 사용자와 게시물 작성자가 같은 경우만 Modify 버튼 활성화

-현재 로그인한 사용자와 댓글 작성자가 같은 경우만 Modify/Remove 버튼 활성화

 

  1. 글 작성자만 수정 버튼 활성화
  2. 댓글 작성자는 로그인한 사용자로 설정
  3. 댓글 수정/삭제는 댓글 작성자와 로그인한 사용자가 동일한 사용자 일경우

 

스프링 시큐리티 네임스페이스 추가

<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
      xmlns:th="http://www.thymeleaf.org"
      layout:decorate="~{layout/basic.html}">

 

1. 글 작성자만 수정 버튼 활성화

 

변경 전, 수정 버튼

<!--목록 / 수정 버튼-->
 <div class="my-4">
       <div class="float-end" th:with="link = ${pageRequestDTO.getLink()}">
           <a th:href="|@{/board/list}?${link}|" class="text-decoration-none">
               <button type="button" class="btn btn-primary">List</button>
           </a>
      <a th:href="|@{/board/modify(bno=${dto.bno})}&${link}|" class="text-decoration-none">
               <button type="button" class="btn btn-secondary">Modify</button>
           </a>
       </div>
   </div>

 

변경 후, 수정 버튼

<!--목록 / 수정 버튼-->
 <div class="my-4">
       <div class="float-end" th:with="link = ${pageRequestDTO.getLink()}">
           <a th:href="|@{/board/list}?${link}|" class="text-decoration-none">
               <button type="button" class="btn btn-primary">List</button>
           </a>
      <a th:if="${user!=null && user.username==dto.writer}" th:href="|@{/board/modify(bno=${dto.bno})}&${link}|" class="text-decoration-none">
               <button type="button" class="btn btn-secondary">Modify</button>
           </a>
       </div>
   </div>

 

2. 댓글 작성자는 로그인한 사용자로 설정

 

변경 전, 댓글 작성자

<!-- 댓글 등록을 위한 모달창-->
  <div class="modal registerModal" tabindex="-1">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">Register Reply</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <div class="input-group mb-3">
          <span class="input-group-text">Reply Text</span>
<!--          <p>Modal body text goes here.</p>-->
          <input type="text" class="form-control replyText">
        </div>
          <div class="input-group mb-3">
          <span class="input-group-text">Replyer</span>
<!--          <p>Modal body text goes here.</p>-->
          <input type="text" class="form-control replyer">
        </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-primary registerBtn">Register</button>
          <button type="button" class="btn btn-online-dark closeRegisterBtn">Close</button>
        </div>
      </div>
    </div>
  </div>

 

변경 후, 댓글 작성자를 현재 로그인 사용자로 변경

<!-- 댓글 등록을 위한 모달창-->
  <div class="modal registerModal" tabindex="-1">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">Register Reply</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <div class="input-group mb-3">
          <span class="input-group-text">Reply Text</span>
<!--          <p>Modal body text goes here.</p>-->
          <input type="text" class="form-control replyText">
        </div>
          <div class="input-group mb-3" th:with="user=${#authentication.principal}">
          <span class="input-group-text">Replyer</span>
<!--          <p>Modal body text goes here.</p>-->
          <input type="text" class="form-control replyer" th:value="${user.username}" readonly>
        </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-primary registerBtn">Register</button>
          <button type="button" class="btn btn-online-dark closeRegisterBtn">Close</button>
        </div>
      </div>
    </div>
  </div>

 

3. 댓글 수정/삭제는 댓글 작성자와 로그인한 사용자가 동일한 사용자 일경우에만 활성화

  • 자바스크립트를 이용한 댓글 수정/삭제 처리
    1. 현재 로그인한 사용자의 아이디를 변수로 지정
    2. 댓글 작성자와 현재 로그인한 사용자(currentUser)가 동일한 유저인지 확인해 수정/삭제 제어
      • hasAuth의 참거짓으로 수정/삭제 버튼 클릭시 이벤트 처리 
  • 자바스크립트에서 '==', '==='
    • '==' :  두 변수의 [값] 비교
    • '===' : 두 변수의 [값 & 자료형] 비교

(1) 현재 로그인한 사용자의 아이디를 변수로 지정

//+) 로그인 유무와 특정 권한별 제어를 위한 현재 로그인한 사용자 변수 설정
const currentUser = [[${#authentication.principal.username}]]
// 댓글 작성자와 현재 로그인한 유저 일치여부를 기본값으로 false로 지정
let hasAuth = false

 

+) 댓글 내용을 띄울 때, 참거짓 설정

  //댓글 내용을 모달창에 띄우기
  //+) 댓글 작성자와 현재 로그인한 사용자의 일치 여부 판단
  getReply(rno).then(reply =>{
    console.log(reply)
    //댓글을 구별하기 위한 변수
    replyHeader.innerHTML=reply.rno
    //String값은 value로 접근
    modifyText.value=reply.replyText
    //댓글 내용을 채운 모달창 띄우기
    modifyModal.show()

    //현재 사용자와 댓글 작성자의 값&유형 비교
    //같으면 true, 다르면 false
    hasAuth=currentUser===reply.replyer
  }).catch(e=>alert('error'))
},false)

 

(2) 댓글 작성자와 현재 로그인한 사용자(currentUser)가 동일한 유저인지 확인해 수정/삭제 제어

 

수정

//수정 버튼 이벤트 처리
modifyBtn.addEventListener("click",function (e){
  if(!hasAuth){
    alert("댓글 작성자만 수정이 가능합니다.")
    modifyModal.hide()
    return
  }
  //입력받은 내용을 필요한 파라미터를 채워서 객체로 전달
  //: rno를 replyHeader의 주소로 지정한다.
  const replyObj={
    bno:bno,
    rno:replyHeader.innerHTML,
    replyText:modifyText.value
  }
  //수정한 댓글 출력하고, 댓글을 수정하던 페이지 정보를 이용해 이전 페이지로 이동
  modifyReply(replyObj).then(result =>{
    alert(result.rno+' 번 댓글이 수정되었습니다.')
    replyText.value=''
    modifyModal.hide()
    printReplies(page,size)
  }).catch(e=> {
    console.log(e)
  })

},false)

 

삭제

//삭제 버튼 클릭시 모달창 이벤트 처리
removeBtn.addEventListener("click", function (e){
  if(!hasAuth){
    alert("댓글 작성자만 삭제가 가능합니다.")
    modifyModal.hide()
    return
  }
  //rno
  removeReply(replyHeader.innerHTML).then(result=>{
    alert(result.rno+' 번 댓글이 삭제되었습니다.')
    //댓글 삭제 후, 변수 초기화
    replyText.value=''
    modifyModal.hide()

    //1페이지로 이동하기 위한 변수
    page=1

    //댓글 목록 출력
    printReplies(page, size)
  })
},false)

 

사용자에 따른 제어를 적용한 댓글 모달창

//댓글 조회/수정/삭제 모달창 띄우기
//댓글 조회/수정/삭제 클래스 모달창/속성값 변수 설정

const modifyModal = new bootstrap.Modal(document.querySelector(".modifyModal"))
const replyHeader = document.querySelector(".replyHeader")
const modifyText = document.querySelector(".modifyText")
const modifyBtn = document.querySelector(".modifyBtn")
const removeBtn = document.querySelector(".removeBtn")
const closeModifyBtn = document.querySelector(".closeModifyBtn")
//+) 로그인 유무와 특정 권한별 제어를 위한 현재 로그인한 사용자 변수 설정
const currentUser = [[${#authentication.principal.username}]]
// 댓글 작성자와 현재 로그인한 유저 일치여부를 기본값으로 false로 지정
let hasAuth = false

//댓글 클릭시 모달창 띄우기
replyList.addEventListener("click", function (e){
  e.preventDefault()
  e.stopPropagation()

  const target=e.target
  if(!target || target.tagName!='SPAN'){
    return
  }
  const rno = target.getAttribute("data-rno")

  if(!rno){
    return
  }

  //댓글 내용을 모달창에 띄우기
  //+) 댓글 작성자와 현재 로그인한 사용자의 일치 여부 판단
  getReply(rno).then(reply =>{
    console.log(reply)
    //댓글을 구별하기 위한 변수
    replyHeader.innerHTML=reply.rno
    //String값은 value로 접근
    modifyText.value=reply.replyText
    //댓글 내용을 채운 모달창 띄우기
    modifyModal.show()

    //현재 사용자와 댓글 작성자의 값&유형 비교
    //같으면 true, 다르면 false
    hasAuth=currentUser===reply.replyer
  }).catch(e=>alert('error'))
},false)

//수정 버튼 이벤트 처리
modifyBtn.addEventListener("click",function (e){
  if(!hasAuth){
    alert("댓글 작성자만 수정이 가능합니다.")
    modifyModal.hide()
    return
  }
  //입력받은 내용을 필요한 파라미터를 채워서 객체로 전달
  //: rno를 replyHeader의 주소로 지정한다.
  const replyObj={
    bno:bno,
    rno:replyHeader.innerHTML,
    replyText:modifyText.value
  }
  //수정한 댓글 출력하고, 댓글을 수정하던 페이지 정보를 이용해 이전 페이지로 이동
  modifyReply(replyObj).then(result =>{
    alert(result.rno+' 번 댓글이 수정되었습니다.')
    replyText.value=''
    modifyModal.hide()
    printReplies(page,size)
  }).catch(e=> {
    console.log(e)
  })

},false)

//닫기 버튼 클릭시 모달창 이벤트 처리
closeModifyBtn.addEventListener("click", function (e){
  modifyModal.hide()
},false)

//삭제 버튼 클릭시 모달창 이벤트 처리
removeBtn.addEventListener("click", function (e){
  if(!hasAuth){
    alert("댓글 작성자만 삭제가 가능합니다.")
    modifyModal.hide()
    return
  }
  //rno
  removeReply(replyHeader.innerHTML).then(result=>{
    alert(result.rno+' 번 댓글이 삭제되었습니다.')
    //댓글 삭제 후, 변수 초기화
    replyText.value=''
    modifyModal.hide()

    //1페이지로 이동하기 위한 변수
    page=1

    //댓글 목록 출력
    printReplies(page, size)
  })
},false)

3. 게시물 수정 작업

현재 로그인한 사용자와 게시물의 작성자가 일치할 때만 수정/삭제 가능하도록

BoardController의 @PreAuthorize을 이용한다.

 

modify 메서드에 @PreAuthorize의 표현식을 로그인한 사용자가 게시물의 작성자일 때로 작성해 어노테이션 설정

- '#boardDTO' : 현재 파라미터가 수집된 boardDTO 객체를 뜻한다.

@PreAuthorize("principal.username==#boardDTO.writer")
@PostMapping("/modify")
public String modify(PageRequestDTO pageRequestDTO, @Valid BoardDTO boardDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes){
    log.info("board modify post..."+boardDTO);

    //검증 실패시 - 에러 메시지 전송 및 이전 페이지로 이동
    if(bindingResult.hasErrors()) {
        log.info("modify post has errors.......");

        String link = pageRequestDTO.getLink();

        redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors() );

        redirectAttributes.addAttribute("bno", boardDTO.getBno());

        return "redirect:/board/modify?"+link;
    }
    //검증 성공시 - 수정 수행
    boardService.modify(boardDTO);
    //수정 완료 메시지 전송
    redirectAttributes.addFlashAttribute("result", "modified");
    //수정한 게시물 번호 전송
    redirectAttributes.addAttribute("bno", boardDTO.getBno());

    //수정 완료시 조건없이 read 페이지로 이동
    return "redirect:/board/read";
}

-> @PathAuthorize 어노테이션은 URL로 다른 사용자의 게시물의 수정페이지로 이동을 하려고 할 때 에러를 발생한다.

 

 

4. 접근거부핸들러 AccessDeniedHandler

-403에러와 같이 서버에서 사용자의 요청의 거부에 대한 에러로, 원인이 다양하다.

403 에러 원인

  • 사용자가 권한이 없을 경우
  • 특정 조건이 맞지 않는 경우

AccessDeniedHandler : 에러페이지를 대체하고, 403 에러 원인에 따라 처리하기 위한 인터페이스

  • <form>태그 요청이 403일 경우
    • 로그인 페이지 이동시 'ACCESS_DENIED'값을 파라미터로 전달
  • Ajax인 경우
    • JSON 데이터를 만들어서 전송

 

 

(1) /security/handler/Custom403Handler 클래스 작성

-"Content-Type"이 "application/json"으로 시작하는지 확인해 JSON 데이터 유무 파악

-JSON 데이터가 아니면 로그인 페이지로 리다이렉트

-추후에 JSON 데이터의 경우 처리

package org.zerock.b01.security;

import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Log4j2
public class Custom403Handler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.info("---ACCESS DENIED---");
        
        response.setStatus(HttpStatus.FORBIDDEN.value());
        
        //JSON 요청인지 확인
        String contentType = request.getHeader("Content-Type");
        
        boolean jsonRequest = contentType.startsWith("application/json");
        
        log.info("isJSON : "+jsonRequest);
        
        //일반 request
        if(!jsonRequest){
            response.sendRedirect("/member/login?error=ACCESS_DENIED");
        }
        
    }
}

 

(2) 403핸들러를 동작하기 위한 CustomSecurityConfig에 필요한 시큐리티 설정

-빈 처리

-예외 처리

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    log.info("---configure---");

    //별도의 로그인 페이지를 이용해 로그인 처리하도록 변경
    http.formLogin().loginPage("/member/login");

    //remember-me 기능을 위한 쿠키 생성
    http.rememberMe()
            .key("12345678")
            .tokenRepository(persistentTokenRepository())
            .userDetailsService(userDetailsService)
            .tokenValiditySeconds(60*60*24*30);

    //csrf 토큰 비활성화
    http.csrf().disable();
    
    //403 에러를 커스텀 핸들러로 예외처리
    http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());

    return http.build();
}

//403 에러의 커스텀 핸들러
@Bean
public AccessDeniedHandler accessDeniedHandler(){
    return new Custom403Handler();
}

-> 403 핸들러에 지정한 작업인 로그인 페이지로 이동을 수행한다.

 

 

4. 게시물 삭제 처리

게시물 번호를 이용한 삭제처리 -> 작성자를 추가로 설정해 @PreAuthorize로 권한 확인

//이미지 포함된 게시물 삭제하도록 파라미터를 bno에서 BoardDTO로 변경
//: 게시물 삭제 후, 첨부파일 삭제 처리
//+) 권한을 가진 사용자만 삭제하도록 @PreAuthorize 표현식 추가
@PreAuthorize("principal.username==#boardDTO.writer")
@PostMapping("/remove")
public String remove(BoardDTO boardDTO, RedirectAttributes redirectAttributes){

    Long bno=boardDTO.getBno();
    log.info("remove post..."+bno);

    boardService.remove(bno);

    //게시물이 DB상에 삭제되었다면 첨부파일 삭제하도록 처리
    log.info(boardDTO.getFileNames());
    List<String> fileNames=boardDTO.getFileNames();
    if(fileNames !=null && fileNames.size()>0){
        //파일을 삭제하는 메서드 호출
        removeFiles(fileNames);
    }


    redirectAttributes.addFlashAttribute("result", "removed");

    return "redirect:/board/list";
}

 

반응형