//현제 오류 수정 때문에 이거 그대로 하면 스프링 시큐리티 오류로 게시글 작성,수정이 불가능해집니다 권한 오류 해결하고 살 더붙여서 수정할겁니다.
ADMIN 계정만 접근이 가능하고 일반 유저는 접근을 할 수 없도록 설정을 추가해야 합니다.
우선 컨트롤러에서 경로부터 수정할 생각입니다.
boardController
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequiredArgsConstructor
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService;
//메인페이지
@GetMapping("/index")
public String index() {
return "index";
}
//글 작성 페이지
@GetMapping(value = "/admin/save")//admin 추가
public String writeForm() {
return "board/save";
}
// 글작성 컨트롤러
@PostMapping(value = "/admin/save")//admin 추가
public String save(@ModelAttribute BoardDTO boardDTO) {
System.out.println("boardDTO = " + boardDTO);
boardService.save(boardDTO);
return "index"; //index 로 변경
}
// 게시글 목록
// @GetMapping("/")
// public String findAll(Model model) {
// // DB에서 전체 게시글 데이터를 가져와서 list.html에 보여준다.
// List<BoardDTO> boardDTOList = boardService.findAll();
//
// model.addAttribute("boardList", boardDTOList);
// return "list";
// }
//게시글 조회
@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model,
@PageableDefault(page =1) Pageable pageable){
boardService.updateHits(id);
BoardDTO boardDTO =boardService.findById(id);
model.addAttribute("board",boardDTO);
model.addAttribute("page",pageable.getPageNumber());
return "board/detail";
}
//게시글 수정
@GetMapping(value = "/admin/update/{id}")//admin 추가
public String updateForm(@PathVariable Long id,Model model) {
BoardDTO boardDTO = boardService.findById(id);
model.addAttribute("boardUpdate",boardDTO);
return "board/update";
} //게시글 수정
@PostMapping(value = "/admin/update")//admin 추가
public String update(@ModelAttribute BoardDTO boardDTO, Model model){
BoardDTO board = boardService.update(boardDTO);
model.addAttribute("board", board);
return "board/detail";
// return "redirect:/board/"+ boardDTO.getId(); // 이것도 가능
}
//게시글 삭제
@GetMapping(value = "/admin/delete/{id}")//admin 추가
public String delete(@PathVariable Long id){
boardService.delete(id);
return "redirect:/board/paging";
}
//게시글 페이징
// /board/paging?page=1
@GetMapping("/paging")
public String paging(@PageableDefault(page = 1) Pageable pageable, Model model) {
//pageable.getPageNumber();
Page<BoardDTO> boardList = boardService.paging(pageable);
List<BoardDTO> boardDTOList = boardService.findAll(); //추가
int blockLimit = 3;
int startPage = (((int)(Math.ceil((double)pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1; // 1 4 7 10 ~~
int endPage = ((startPage + blockLimit - 1) < boardList.getTotalPages()) ? startPage + blockLimit - 1 : boardList.getTotalPages();
// page 갯수 20개
// 현재 사용자가 3페이지
// 1 2 3
// 현재 사용자가 7페이지
// 7 8 9
// 보여지는 페이지 갯수 는 3
// 총 페이지 갯수 8개
model.addAttribute("boardList", boardDTOList); //추가
model.addAttribute("boardList", boardList);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
return "board/paging";
}
}
게시글 조회나, 리스트, 페이징은 권한 없이도 접근할 수 있게 오픈했고 삭제, 수정, 글쓰기는 권한 있는 사람만 접근 가능하도록 일단 설정했습니다.
ajax의 경우 http request header에 XMLHttpRequest라는 값이 세팅되어 요청이 오는데, 인증되지 않은 사용자가 ajax로 리소스를 요청할 경우 "Unauthorized" 에러를 발생시키고 나머지 경우는 로그인 페이지로 리다이렉트 시켜주는 콘픽을 설정합니다.
기존에 회원가입, 로그인 구현 때 작성했던 CustomAuthenticationEntryPoint의 조금 더 수정을 할 겁니다.
CustomAuthenticationEntryPoint
package cho.boardplus.confing;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
if ("XMLHttpRequest".equals(request.getHeader("x-requested-with"))){
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Unauthorized");
}else {
response.sendRedirect("/members/login");
}
}
}
/*
해당 코드는 스프링 시큐리티에서 인증되지 않은 사용자가 보호된 리소스에 접근할 때 호출되는 commence() 메서드입니다.
먼저 HttpServletResponse.SC_UNAUTHORIZED를 사용하여 인증되지 않은 오류를 응답합니다. 그런 다음,
XMLHttpRequest 헤더가 있는 경우 AJAX 요청을 처리하기 위해 동일한 HttpServletResponse.SC_UNAUTHORIZED 응답을 다시 보내고,
그렇지 않은 경우 /members/login으로 리디렉션합니다.
즉, 이 코드는 인증되지 않은 사용자가 보호된 리소스에 접근하려고 할 때 적절한 오류 응답을 보내거나 로그인 페이지로 리디렉션하여 인증을 요구합니다.
*/
ajax의 경우 http request header에XMLHttpRequest 라는 값이 세팅되어 요청이 오는데, 인증되지않은 사용자가ajax로 리소스를 요청할 경우 "Unauthorized" 에러를 발생시키고 나머지 경우는 로그인 페이지로 리다이렉트 시켜줍니다.
그리고 SecurityConfig에도 추가 수정이 필요합니다.
SecurityConfig
package cho.boardplus.confing;
import cho.boardplus.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MemberService memberService;
@Override
protected void configure(HttpSecurity http) throws Exception{
http.formLogin()
.loginPage("/members/login")
.defaultSuccessUrl("/")
.usernameParameter("email")
.failureUrl("/members/login/error")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/members/logout"))
.logoutSuccessUrl("/");
http.authorizeRequests().mvcMatchers("/","members/**","detail/**","/images/**").permitAll()//추가1
.mvcMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated();
http.exceptionHandling()//추가2
.authenticationEntryPoint
(new CustomAuthenticationEntryPoint());
}
@Override
public void configure(WebSecurity web) throws Exception{ //추가3
web.ignoring().antMatchers("/css/**","/js/**","/img/**");
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(memberService).passwordEncoder(passwordEncoder());
}
}
- 시큐리티 처리에 HttpServletRequest를 이용한다는 것을 의미합니다.
- permitAll()을 통해 모든 사용자가 인증(로그인) 없이 해당 경로에 접근할 수 있도록 설정합니다. 메인 페이지. 회원 관련 URL, 상세 페이지, 이미지를 불러오는 경로가 이에 해당합니다.
- /admin으로 시작하는 경로는 해당 계정이 ADMIN Role일 경우에만 접근 가능하도록 설정합니다.
- 2,3에서 설정해 준 경로를 제외한 나머지 경로들은 모두 인증을 요구하도록 설정합니다.
- 인증되지 않은 사용자가 리소스에 접근하였을 때 수행되는 핸들러를 등록합니다.
- static 디렉터리의 하위 파일은 인증을 무시하도록 설정합니다.
이제 기존에 html 들 다 경로들을 수정해줘야 합니다.
save.html
<form action="/board/admin/save" method="post" enctype="multipart/form-data">
writer: <input type="text" name="boardWriter"> <br>
title: <input type="text" name="boardTitle"> <br>
contents: <textarea name="boardContents" cols="30" rows="10"></textarea> <br>
file: <input type="file" name="boardFile" multiple> <br>
<input type="submit" value="글작성">
</form>
admin 추가
datail.html
<script th:inline="javascript">
const listReq = () => {
console.log("목록 요청");
const page = [[${page}]];
location.href = "/board/paging?page="+page;
}
const updateReq = () => {
console.log("수정 요청");
const id = [[${board.id}]];
location.href = "/board/admin/update/" + id;
}
const deleteReq = () => {
console.log("삭제 요청");
const id = [[${board.id}]];
location.href = "/board/admin/delete/" + id;
}
수정해 주시면 됩니다. 또
update.html
<form action="/board/admin/update" method="post" name="updateForm">
<input type="hidden" name="id" th:value="${boardUpdate.id}">
writer: <input type="text" name="boardWriter" th:value="${boardUpdate.boardWriter}" readonly> <br>
title: <input type="text" name="boardTitle" th:value="${boardUpdate.boardTitle}"> <br>
contents: <textarea name="boardContents" cols="30" rows="10" th:text="${boardUpdate.boardContents}"></textarea> <br>
<input type="hidden" name="boardHits" th:value="${boardUpdate.boardHits}">
<input type="button" value="글수정" onclick="boardUpdate()">
</form>
form action에 admin을 추가해줘야 합니다. 그리고 header.html에서도 수정을 해줍시다.
header.html
<li class="nav-board" sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
<a class="nav-link" href="/board/admin/save">게시글등록</a>
</li>
<li class="nav-board" sec:authorize="isAuthenticated()">
<a class="nav-link" href="board/paging">게시판목록</a>
</li>
<li class="nav-board" sec:authorize="isAnonymous()">
<a class="nav-link" href="/members/login">로그인</a>
</li>
<li class="nav-board" sec:authorize="isAuthenticated()">
<a class="nav-link" href="/members/logout">로그아웃</a>
위 보시면 게시글 등록에 admin을 추가했고 로그인이 필요 없는 게시판 목록이나 로그인 화면 로그아웃은 어드민 권한이 필요 없으니 그대로 두시면 됩니다.
보시면 admin 이 보이면서 권한이 있을때 접속했고
로그아웃 해서 권한없이 게시글 작성 주소로 가면 해당 에러다 발생합니다.
html에 ajax를 추가로 작성했을시 403오류가 뜨고 없을시 500오류가 뜹니다. 위 에 설명이 있으니 다시 보시면 좋습니다.
다음은 게시글을 작성할 때 회원 db랑 게시판 db 랑 연결해서 게시글을 작성했을때 어떤 아이디가 작성했는지 맵핑 해서 해봅시다.
정석방법 은 이렇습니다 만약 오류 없이 잘 실행이 된다면 이 밑은 따라오실 필요가 없습니다.
방법은 이렇고 저자는 현제 csrf 토큰 오류가 발생 했습니다. https://cbh2031.tistory.com/100
저는 개인적으로 다시 수정을 가할겁니다.
SecurityConfig
package cho.boardplus.confing;
import cho.boardplus.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MemberService memberService;
@Override
protected void configure(HttpSecurity http) throws Exception{
http.formLogin()
.loginPage("/members/login")
.defaultSuccessUrl("/")
.usernameParameter("email")
.failureUrl("/members/login/error")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/members/logout"))
.logoutSuccessUrl("/")
;
http.authorizeRequests()//페이지 권한 설정
.mvcMatchers("/css/**", "/js/**", "/img/**").permitAll()
.mvcMatchers("/", "/members/**", "/board/paging","/board/{id}","/board/save", "/images/**").permitAll()//
.mvcMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
;
http.csrf().disable();//csrf 토큰 방어 중지
http.exceptionHandling()//페이지 권한
.authenticationEntryPoint
(new CustomAuthenticationEntryPoint())
;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(memberService)
.passwordEncoder(passwordEncoder());
}
}
csrf 토큰 방어를 중지 시켯습니다.
그리고 다시 admin을 다 뺏고
BoardController
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
@RequiredArgsConstructor
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService;
//메인페이지
@GetMapping("/index")
public String index() {
return "index";
}
//글작성 페이지
@GetMapping("/save")
public String saveForm() {
return "board/save";
}
// 글작성
@PostMapping("/save")
public String save(@ModelAttribute BoardDTO boardDTO) throws IOException {
System.out.println("boardDTO = " + boardDTO);
boardService.save(boardDTO);
return "index";
}
// 게시글 목록
// @GetMapping("/")
// public String findAll(Model model) {
// // DB에서 전체 게시글 데이터를 가져와서 list.html에 보여준다.
// List<BoardDTO> boardDTOList = boardService.findAll();
//
// model.addAttribute("boardList", boardDTOList);
// return "list";
// }
//게시글 조회
@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model,
@PageableDefault(page =1) Pageable pageable){
boardService.updateHits(id);
BoardDTO boardDTO =boardService.findById(id);
model.addAttribute("board",boardDTO);
model.addAttribute("page",pageable.getPageNumber());
return "board/detail";
}
//게시글 수정
@GetMapping(value = "/update/{id}")
public String updateForm(@PathVariable Long id,Model model) {
BoardDTO boardDTO = boardService.findById(id);
model.addAttribute("boardUpdate",boardDTO);
return "board/update";
} //게시글 수정
@PostMapping(value = "/update")//admin 추가
public String update(@ModelAttribute BoardDTO boardDTO, Model model){
BoardDTO board = boardService.update(boardDTO);
model.addAttribute("board", board);
return "board/detail";
// return "redirect:/board/"+ boardDTO.getId(); // 이것도 가능
}
//게시글 삭제
@GetMapping(value = "/delete/{id}")
public String delete(@PathVariable Long id){
boardService.delete(id);
return "redirect:/board/paging";
}
//게시글 페이징
// /board/paging?page=1
@GetMapping("/paging")
public String paging(@PageableDefault(page = 1) Pageable pageable, Model model) {
//pageable.getPageNumber();
Page<BoardDTO> boardList = boardService.paging(pageable);
List<BoardDTO> boardDTOList = boardService.findAll(); //추가
int blockLimit = 3;
int startPage = (((int)(Math.ceil((double)pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1; // 1 4 7 10 ~~
int endPage = ((startPage + blockLimit - 1) < boardList.getTotalPages()) ? startPage + blockLimit - 1 : boardList.getTotalPages();
// page 갯수 20개
// 현재 사용자가 3페이지
// 1 2 3
// 현재 사용자가 7페이지
// 7 8 9
// 보여지는 페이지 갯수 는 3
// 총 페이지 갯수 8개
model.addAttribute("boardList", boardDTOList);
model.addAttribute("boardList", boardList);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
return "board/paging";
}
}
html 단에서 csrf 쪽 태그들을 다 주석 처리했습니다.
<!-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">-->
hearder.html 도 다시 admin을 빼시고 나머지 admin을 추가했던 주소값들도 다시 원상복귀 시켜서 로그인 id 값으로 수정,이나 삭제 버튼이 보이게 권한을 변경 할겁니다.
'Spring > 게시판 CRUD + 추가기능들' 카테고리의 다른 글
게시글 작성시 회원 정보 게시글에 자동 포함시키기 (0) | 2023.04.13 |
---|---|
로그인 구현 (0) | 2023.04.03 |
회원가입 기능 구현 (0) | 2023.04.01 |
게시글 페이징 (0) | 2023.03.30 |
게시판 삭제 (0) | 2023.03.29 |