Spring 22 Spring Security 게시판 [업데이트 중]

Kang.__.Mingu·2024년 9월 26일

Spring

목록 보기
21/21

SQL

create table security_board(
	num number primary key, 
    writer varchar2(100), 
    subject varchar2(500), 
    content varchar2(4000), 
    regdate date
);
create sequence security_board_seq;

SecurityBoard(DTO)

@Data
public class SecurityBoard {
	private int num;
	private String writer;//작성자(아이디)
	private String subject;
	private String content;
	private String regdate;
	
	private String name;//작성자(이름)
}

SecurityBoardMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.itwill.mapper.SecurityBoardMapper">
    <!-- 삽입 -->
    <insert id="insertSecurityBoard">
        <selectKey resultType="int" keyProperty="num" order="BEFORE">
            select security_board_seq.nextval from dual
        </selectKey>
        insert into security_board values(#{num}, #{writer}, #{subject}, #{content}, sysdate)
    </insert>

    <!-- num이라는 조건에 맞게 업데이트 처리 -->
    <update id="updateSecurityBoard">
        update security_board set subject=#{subject}, content=#{content} where num=#{num}
    </update>

    <!-- 해당 번호로 삭제 -->
    <delete id="deleteSecurityBoard">
        delete from security_board where num=#{num}
    </delete>

    <!-- 검색한 컬럼을 resultType를 사용해서 해당 객체로 만들어줌
     security_board 테이블과 security_user에 있는 컬럼을 join => inner join(해당 컬럼에 같은 값을 가진 필드만 join 처리)-->
    <select id="selectSecurityBoardByNum" resultType="SecurityBoard">
        select num, writer, subject, content, regdate, name from security_board
        join security_user on writer=userid where num=#{num}
    </select>

    <!-- 리스트의 전체 갯수를 가져오는 sql -->
    <!-- 조건을 걸어 검색필드에 값이 있다면 해당 조건과 키워드에 맞게 갯수를 반환 -->
    <select id="selectSecurityBoardCount" resultType="int">
        select count(*) from security_board join security_user on writer=userid
        <if test="keyword != null and keyword != ''">
            <bind name="word" value="'%'+keyword+'%'"/>
            where ${column} like #{word}
        </if>
    </select>

    <!-- 페이징 처리된 게시판 리스트를 가져오는 sql -->
    <!-- 키워드가 있다면 검색된 키워드의 게시판 리스트를 반환 -->
    <select id="selectSecurityBoardList" resultType="SecurityBoard">
        select * from (select rownum rn, board.* from (select num, writer, subject, content
        , regdate, name from security_board join security_user on writer=userid
        <if test="keyword != null and keyword != ''">
            <bind name="word" value="'%'+keyword+'%'"/>
            where ${column} like #{word}
        </if>
        order by num desc) board) where rn between #{startRow} and #{endRow}
    </select>
</mapper>

SecurityBoardMapper.java

public interface SecurityBoardMapper {
	int insertSecurityBoard(SecurityBoard board);
	int updateSecurityBoard(SecurityBoard board);
	int deleteSecurityBoard(int num);
	SecurityBoard selectSecurityBoardByNum(int num);
	int selectSecurityBoardCount(Map<String, Object> map);
	List<SecurityBoard> selectSecurityBoardList(Map<String, Object> map);
}

selectSecurityBoardList 메소드의 매개변수는 전달값이 2개 이상이면 DTO 객체나 Map 객체를 사용해야하는데 DTO객체가 없다면 무조건 Map 객체 사용
Map 객체는 <String, Object>가 국롤

SecurityBoardDAO

public interface SecurityBoardDAO {
	int insertSecurityBoard(SecurityBoard board);
	int updateSecurityBoard(SecurityBoard board);
	int deleteSecurityBoard(int num);
	SecurityBoard selectSecurityBoardByNum(int num);
	int selectSecurityBoardCount(Map<String, Object> map);
	List<SecurityBoard> selectSecurityBoardList(Map<String, Object> map);
}

SecurityBoardDAOImpl

@Repository
@RequiredArgsConstructor
public class SecurityBoardDAOImpl implements SecurityBoardDAO {
	private final SqlSession sqlSession;

	@Override
	public int insertSecurityBoard(SecurityBoard board) {
		return sqlSession.getMapper(SecurityBoardMapper.class).insertSecurityBoard(board);
	}

	@Override
	public int updateSecurityBoard(SecurityBoard board) {
		return sqlSession.getMapper(SecurityBoardMapper.class).updateSecurityBoard(board);
	}

	@Override
	public int deleteSecurityBoard(int num) {
		return sqlSession.getMapper(SecurityBoardMapper.class).deleteSecurityBoard(num);
	}

	@Override
	public SecurityBoard selectSecurityBoardByNum(int num) {
		return sqlSession.getMapper(SecurityBoardMapper.class).selectSecurityBoardByNum(num);
	}

	@Override
	public int selectSecurityBoardCount(Map<String, Object> map) {
		return sqlSession.getMapper(SecurityBoardMapper.class).selectSecurityBoardCount(map);
	}

	@Override
	public List<SecurityBoard> selectSecurityBoardList(Map<String, Object> map) {
		return sqlSession.getMapper(SecurityBoardMapper.class).selectSecurityBoardList(map);
	}
}

SecurityBoardService.java

public interface SecurityBoardService {
	void addSecurityBoard(SecurityBoard board);
	void modifySecurityBoard(SecurityBoard board);
	void removeSecurityBoard(int num);
	SecurityBoard getSecurityBoardByNum(int num);
	Map<String, Object> getSecurityBoardList(Map<String, Object> map);
}

SecurityBoardServiceImpl.java

@Service
@RequiredArgsConstructor
public class SecurityBoardServiceImpl implements SecurityBoardService {
	private final SecurityUserDAO securityUserDAO;
	private final SecurityBoardDAO securityBoardDAO;

	@Transactional
	@Override
	public void addSecurityBoard(SecurityBoard board) {
		if(securityUserDAO.selectSecurityUserByUserid(board.getWriter()) == null) {
			throw new RuntimeException("게시글 작성자를 찾을 수 없습니다.");
		}
		securityBoardDAO.insertSecurityBoard(board);
	}

	@Transactional
	@Override
	public void modifySecurityBoard(SecurityBoard board) {
		if(securityUserDAO.selectSecurityUserByUserid(board.getWriter()) == null) {
			throw new RuntimeException("게시글 작성자를 찾을 수 없습니다.");
		}
		if(securityBoardDAO.selectSecurityBoardByNum(board.getNum()) == null) {
			throw new RuntimeException("변경하고자 하는 게시글을 찾을 수 없습니다.");
		}
		securityBoardDAO.updateSecurityBoard(board);
	}

	@Transactional
	@Override
	public void removeSecurityBoard(int num) {
		if(securityBoardDAO.selectSecurityBoardByNum(num) == null) {
			throw new RuntimeException("삭제 하고자 하는 게시글을 찾을 수 없습니다.");
		}
		securityBoardDAO.deleteSecurityBoard(num);
	}

	@Override
	public SecurityBoard getSecurityBoardByNum(int num) {
		SecurityBoard board=securityBoardDAO.selectSecurityBoardByNum(num);
		if(board == null) {
			throw new RuntimeException("게시글을 찾을 수 없습니다.");
		}
		return board;
	}

	@Override
	public Map<String, Object> getSecurityBoardList(Map<String, Object> map) {
		int pageNum=1;
		if(map.get("pageNum") != null && !map.get("pageNUm").equals("")) {
			pageNum=Integer.parseInt((String)map.get("pageNum"));
		}
		
		int pageSize=5;
		if(map.get("pageSize") != null && !map.get("pageSize").equals("")) {
			pageSize=Integer.parseInt((String)map.get("pageSize"));
		}
		
		int totalBoard=securityBoardDAO.selectSecurityBoardCount(map);
		
		int blockSize=5;
		
		Pager pager=new Pager(pageNum, pageSize, totalBoard, blockSize);
		
		map.put("startRow", pager.getStartRow());
		map.put("endRow", pager.getEndRow());
		List<SecurityBoard> boardList=securityBoardDAO.selectSecurityBoardList(map);
		
		Map<String, Object> result=new HashMap<String, Object>();
		result.put("pager", pager);
		result.put("securityBoardList", boardList);
		
		return result;
	}
}

SecurityBoardController.java

@Controller
@RequestMapping("/board")
@RequiredArgsConstructor
public class SecurityBoardController {
	private final SecurityBoardService securityBoardService;
	
	//페이지 요청시 모든 전달값을 Map 객체로 제공받아 사용
	// ex) Map 객체로 전달받는 값: /board/list?pageNum=2&pageSize=5&column=subject&keyword=Spring
	@RequestMapping("/list")
	public String list(@RequestParam Map<String, Object> map, Model model) {
		model.addAttribute("resultMap", securityBoardService.getSecurityBoardList(map));
		model.addAttribute("searchMap", map);
		return "board/board_list";
	}
	
	@RequestMapping(value= "/register", method = RequestMethod.GET)
	public String register() {
		return "board/board_register";
	}
	
	@RequestMapping(value= "/register", method = RequestMethod.POST)
	public String register(@ModelAttribute SecurityBoard board) {
		board.setSubject(HtmlUtils.htmlEscape(board.getSubject()));
		board.setContent(HtmlUtils.htmlEscape(board.getContent()));
		securityBoardService.addSecurityBoard(board);
		return "redirect:/board/list";
	}
}

board_list.jsp(게시글, 페이징, 검색)

<body>
	<h1>게시글 목록</h1>
	<hr>
	<div id="container">
		<%-- 로그인된 사용자만 글쓰기 가능 --%>
		<sec:authorize access="isAuthenticated()">
			<div style="text-align: right; margin-bottom: 10px;">
				<button type="button" onclick="location.href='<c:url value="/board/register"/>';">글쓰기</button>
			</div>
		</sec:authorize>
		<table>
			<tr>
				<th class="num">글번호</th>
				<th class="writer">작성자</th>
				<th class="subject">제목</th>
				<th class="regdate">작성일</th>
			</tr>
			<c:choose>
				<c:when test="${empty resultMap.securityBoardList }">
					<tr>
						<td colspan="4">검색된 게시글이 없습니다.</td>
					</tr>
				</c:when>
				<c:otherwise>
					<c:forEach var="securityBoard" items="${resultMap.securityBoardList }">
						<tr>
							 <td>${securityBoard.num }</td>
							 <td>${securityBoard.name }</td>
							 <td style="text-align: left;">
							 	${securityBoard.subject }
							 </td>
							 <td>${securityBoard.regdate }</td>
						</tr>
					</c:forEach>
				</c:otherwise>
			</c:choose>
		</table>
		
		<div style="text-align: center;">
			<c:choose>
				<c:when test="${resultMap.pager.startPage > resultMap.pager.blockSize }">
					<a href="<c:url value="/board/list"/>?pageNum=${resultMap.pager.prevPage}&pageSize=5&column=${searchMap.column}&keyword=${searchMap.keyword}">
						[이전]
					</a>
				</c:when>
				<c:otherwise>
					[이전]
				</c:otherwise>
			</c:choose>
				
			<c:forEach var="i" begin="${resultMap.pager.startPage }" end="${resultMap.pager.endPage }" step="1">
				<c:choose>
					<c:when test="${resultMap.pager.pageNum != i }">
						<a href="<c:url value="/board/list"/>?pageNum=${i}&pageSize=5&column=${searchMap.column}&keyword=${searchMap.keyword}">
							[${i }]
						</a>
					</c:when>
					<c:otherwise>
						[${i }]
					</c:otherwise>
				</c:choose>				
			</c:forEach>
			
			<c:choose>
				<c:when test="${resultMap.pager.endPage != resultMap.pager.totalPage }">
					<a href="<c:url value="/board/list"/>?pageNum=${resultMap.pager.nextPage}&pageSize=5&column=${searchMap.column}&keyword=${searchMap.keyword}">
						[다음]
					</a>
				</c:when>
				<c:otherwise>
					[다음]
				</c:otherwise>
			</c:choose>
		</div>
		
		<div style="text-align: center;">
			<form action="<c:url value="/board/list"/>" method="post">
				<select name="column">
					<option value="name">작성자</option>
					<option value="subject">제목</option>
					<option value="content">내용</option>
				</select>			
				<input type="text" name="keyword">
				<sec:csrfInput/>
				<button type="submit">검색</button>
			</form>
		</div>
	</div>
</body>

로그인 사용자만 접근할 수 있게 사용하는 방법(어노테이션 사용해서)

servlet-context.xml

  • global-method-security: Controller 클래스의 요청 처리 메소드에 권한 관련 어노테이션을 제공하기 위한 엘리먼트

  • security 네임스페이스를 추가하여 spring-securit.xsd 파일의 엘리먼트를 사용할 수 있도록 설정

  • pre-post-annotations 속성: disabled(기본값) 또는 enabled 중 하나를 속성값으로 설정
    => 속성값을 [enabled]로 설정하면 @PreAuthorize 어노테이션 또는 @PostAuthorize 어노테이션을 사용할 수 제공

  • secured-annotations 속성: disabled(기본값) 또는 enabled 중 하나를 속성값으로 설정
    => 속성값을 [enabled]로 설정하면 @Secured 어노테이션을 사용할 수 있도록 제공

<security:global-method-security pre-post-annotations="disabled" secured-annotations="disabled"/>

=> 설정 해주는거

SecurityBoardController.java(/board_register)

  • 로그인 사용자만 요청 처리 메소드를 호출할 수 있도록 권한 설정

  • @PreAuthorize: 요청 처리 메소드가 실행되기 전에 권한을 설정하기 위한 어노테이션(권한에 따라 요청 메서드 실행 여부 결정)

  • value 속성: 권한(ROLE)을 속성값으로 설정 - SpEL 사용 가능
    => value 속성외에 다른 속성이 없는 경우 속성값만 설정 가능

  • @PostAuthorize: 요청 처리 메소드가 실행된 후에 권한을 설정하기 위한 어노테이션

  • @Secured: 권한(ROLE)을 속성값으로 설정 - SpEL 사용 불가능

// 로그인 된 사용자만 해당 요청 메서드 이용 가능
@PreAuthorize("isAuthenticated()")
@RequestMapping(value= "/register", method = RequestMethod.GET)
public String register() {
	return "board/board_register";
}

@PreAuthorize("isAuthenticated()")
@RequestMapping(value= "/register", method = RequestMethod.POST)
public String register(@ModelAttribute SecurityBoard board) {
	board.setSubject(HtmlUtils.htmlEscape(board.getSubject()));
	board.setContent(HtmlUtils.htmlEscape(board.getContent()));
	securityBoardService.addSecurityBoard(board);
	return "redirect:/board/list";
}

board_detail.jsp(로그인한 사용자만 글쓰기 버튼 활성화)

<sec:authorize access="isAuthenticated()">
	<div style="text-align: right; margin-bottom: 10px;">
		<button type="button" onclick="location.href='<c:url value="/board/register"/>';">글쓰기</button>
	</div>
</sec:authorize>

SecurityBoardController.java(/board_detail)

  • 중요한 건 input hidden으로 설정해주고 name과 value를 적용해놔야 글변경, 글삭제 버튼을 눌렀을 때 name과 value가 Map 객체로 받아다가 처리할 수 있다.
    => 결론은 권한 설정 때문에 사용하는 것
<div style="margin-top: 10px;">
	<form method="get" id="linkForm">
		<input type="hidden" name="num" value="${securityBoard.num }">
		<%-- 권한 설정을 위해 게시글 작성자 전달 --%>
		<input type="hidden" name="writer" value="${securityBoard.writer }">
		<input type="hidden" name="pageNum" value="${searchMap.pageNum }">
		<input type="hidden" name="pageSize" value="${searchMap.pageSize }">
		<input type="hidden" name="column" value="${searchMap.column }">
		<input type="hidden" name="keyword" value="${searchMap.keyword }">
			
		<button type="button" id="listBtn">글목록</button>
		<sec:authorize access="isAuthenticated()">
			<%-- authorize 태그의 access 속성값으로 설정된 권한이 있는 경우 var 속성값으로 
			작성된 Scope 속성명에 [true] 저장 --%>
			<sec:authorize access="hasRole('ROLE_ADMIN')" var="adminRole"/>
			<%-- authentication 태그로 인증된 사용자 정보를 제공받아 var 속성값으로 작성된
			Scope 속성명에 인증된 사용자 정보 저장 --%>
			<sec:authentication property="principal" var="pinfo"/>
			
            <%-- 해당 조건을 만족하면 보여줌 --%>
			<c:if test="${adminRole || pinfo.userid eq securityBoard.writer }">
				<button type="button" id="modifyBtn">글변경</button>
				<button type="button" id="removeBtn">글삭제</button>
			</c:if>
		</sec:authorize>
	</form>
</div>

<script type="text/javascript">
$("#listBtn").click(function() {
	$("#linkForm").attr("action","<c:url value="/board/list"/>").submit();
});
	
$("#modifyBtn").click(function() {
$("#linkForm").attr("action","<c:url value="/board/modify"/>").submit();
});
		
$("#removeBtn").click(function() {
	$("#linkForm").attr("action","<c:url value="/board/remove"/>").submit();
});

SecurityBoardController.java(modify , remove)

  • 게시글 수정 => 로그인 사용자 중 관리자 또는 게시글 작성자인 경우에만 요청 처리 메소드를 호출할 수 있도록 권한 설정

  • SpEL를 사용해 권한 설정할 경우 EL 연산자 사용 가능
    => # 표현식을 사용하여 요청 처리 메소드의 전달값이 저장된 매개변수 사용 가능

  • /modify를 GET 방식으로 요청할 때 map.get("num")으로 가져오는데 board_detail.jsp에서 hidden으로 name과 value 설정해놓은 값이 map객체에 저장되어 url로 넘어갈 때 원하는 값을 꺼내서 사용할 수있다.

@PreAuthorize("hasRole('ROLE_ADMIN') or principal.userid eq #map['writer']")
@RequestMapping(value= "/modify", method = RequestMethod.GET)
public String modify(@RequestParam Map<String, Object> map, Model model) {
	int num=Integer.parseInt((String)map.get("num"));
	model.addAttribute("securityBoard", securityBoardService.getSecurityBoardByNum(num));
	model.addAttribute("searchMap", map);
	return "board/board_modify";
}

@PreAuthorize("hasRole('ROLE_ADMIN') or principal.userid eq #map['writer'] ")
@RequestMapping(value= "/modify", method = RequestMethod.POST)
public String modify(@ModelAttribute SecurityBoard board, 
		@RequestParam Map<String, Object> map, Model model) throws UnsupportedEncodingException {
	board.setSubject(HtmlUtils.htmlEscape(board.getSubject()));
	board.setContent(HtmlUtils.htmlEscape(board.getContent()));
	securityBoardService.modifySecurityBoard(board);
	
	String pageNum=(String)map.get("pageNum");
	String pageSize=(String)map.get("pageSize");
	String column=(String)map.get("column");
    // URLEncoder로 감싸줘야하는 이유: url은 한글을 못 받아서 utf-8이라고 명시를 해줘야 받을 수 있음
	String keyword=URLEncoder.encode((String)map.get("keyword"), "utf-8");
	
    // 새로운 페이지를 요청할 때 이전 페이지로 돌아가기 위해
    // => 쉽게 말해 2페이지에서 상품의 상세페이지를 눌렀을 때 상품이 보여지고
    // 글목록 버튼을 누르면 2페이지로 돌아가기 위해 필요(키워드로 검색했다면 키워드 목록까지)
	return "redirect:/board/detail?num="+board.getNum()+"&pageNum="+pageNum
		+"&pageSize="+pageSize+"&column="+column+"&keyword="+keyword;
}

삭제

  • 로그인 사용자 중 관리자 또는 게시글 작성자인 경우에만 요청 처리 메소드를 호출할 수 있도록 권한 설정

  • 관리자, 작성자만 삭제 가능 => 번호로 삭제

@PreAuthorize("hasRole('ROLE_ADMIN') or principal.userid eq #writer ")
@RequestMapping("/remove")
public String remove(@RequestParam int num, @RequestParam String writer) {
	securityBoardService.removeSecurityBoard(num);
	return "redirect:/board/list";
}

댓글 기능

SQL

create table security_reply(
	num number primary key, 
    writer varchar2(100), 
    content varchar2(1000), 
    regdate date, 
    board_num number, 
    constraint reply_board_num_fk foreign key(board_num) 
	references security_board(num) on delete cascade);
	    
create sequence security_reply_seq;   

SecurityReply(DTO)

@Data
public class SecurityReply {
	private int num;
	private String writer;//작성자(아이디)
	@NotEmpty(message = "내용을 입력해 주세요.")
	private String content;
	private String regdate;
	private int boardNum;
	private String name;//작성자(이름)
}

SecurityReplyMapper.xml

<mapper namespace="xyz.itwill.mapper.SecurityReplyMapper">
	<insert id="insertSecurityReply">
		<selectKey resultType="int" keyProperty="num" order="BEFORE">
			select security_reply_seq.nextval from dual
		</selectKey>
		insert into security_reply values(#{num}, #{writer}, #{content}, sysdate, #{boardNum})
	</insert>
	
	<select id="selectSecurityReplyList" resultType="SecurityReply">
		select num, writer, content,regdate, board_num, name from security_reply join 
			security_user on writer=userid where board_num=#{boardNum} order by num desc
	</select>
</mapper>

SecurityReplyMapper.java

public interface SecurityReplyMapper {
	int insertSecurityReply(SecurityReply reply);
	List<SecurityReply> selectSecurityReplyList(int boardNum);
}

SecurityReplyDAO.java

public interface SecurityReplyDAO {
	int insertSecurityReply(SecurityReply reply);
	List<SecurityReply> selectSecurityReplyList(int boardNum);
}

SecurityReplyDAOImpl.java

@Repository
@RequiredArgsConstructor
public class SecurityReplyDAOImpl implements SecurityReplyDAO {
	private final SqlSession sqlSession;

	@Override
	public int insertSecurityReply(SecurityReply reply) {
		return sqlSession.getMapper(SecurityReplyMapper.class).insertSecurityReply(reply);
	}

	@Override
	public List<SecurityReply> selectSecurityReplyList(int boardNum) {
		return sqlSession.getMapper(SecurityReplyMapper.class).selectSecurityReplyList(boardNum);
	}
}

SecurityReplyService

public interface SecurityReplyService {
	void addSecurityReply(SecurityReply reply);
	List<SecurityReply> getSecurityReplyList(int boardNum);
}

SecurityReplyServiceImpl.java

@Service
@RequiredArgsConstructor
public class SecurityReplyServiceImpl implements SecurityReplyService {
	private final SecurityReplyDAO securityReplyDAO;
	private final SecurityUserDAO securityUserDAO;
	private final SecurityBoardDAO securityBoardDAO;

	@Override
	public void addSecurityReply(SecurityReply reply) {
		if(securityUserDAO.selectSecurityUserByUserid(reply.getWriter()) == null) {
			throw new RuntimeException("작성자를 찾을 수 없습니다.");
		}
		securityReplyDAO.insertSecurityReply(reply);
	}

	@Override
	public List<SecurityReply> getSecurityReplyList(int boardNum) {
		if(securityBoardDAO.selectSecurityBoardByNum(boardNum) == null) {
			throw new RuntimeException("게시글을 찾을 수 없습니다.");
		}
		return securityReplyDAO.selectSecurityReplyList(boardNum);
	}
}

SecurityReplyController.java

// Spring MVC에서 RESTful 웹 서비스를 구현하는 컨트롤러를 정의할 때 사용한다.
// JSON 또는 XML 형식으로 데이터를 반환하는 컨트롤러임을 나타내고, 
// 모든 메서드의 반환값이 HTTP 응답 본문으로 직접 쓰여진다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/reply")
public class SecurityReplyController {
	private final SecurityReplyService securityReplyService;

	//BindingResult 객체 : @Valid 어노테이션을 사용해 객체 필드값에 대한 검증시 문제가 발생될
	//경우 관련 에러를 저장하기 위한 객체 - Errors 객체의 자식 객체
	@PreAuthorize("isAuthenticated()")
	// PostMapping: 주로 데이터를 서버로 전송하거나 등록할 때 사용. 여기서는 새로운 댓글을 등록하기 위해 사용
	@PostMapping("/register")
	public String register(@RequestBody @Valid SecurityReply reply, BindingResult bindingResult) 
			throws BindException {
		if(bindingResult.hasErrors()) {
			throw new BindException(bindingResult);
		}
		securityReplyService.addSecurityReply(reply);
		return "success";
	}
	
	@GetMapping("/list/{boardNum}")
	public List<SecurityReply> list(@PathVariable int boardNum) {
		return securityReplyService.getSecurityReplyList(boardNum);
	}
}

댓글 입력, 댓글 목록 출력(AJAX 사용)

board_detail.jsp

<%-- 댓글을 입력받거나 댓글 목록을 출력하는 태그 - 로그인 사용자에게만 제공 --%>
<sec:authorize access="isAuthenticated()">
	<input type="hidden" id="writer" value="<sec:authentication property="principal.userid"/>">
	<div>
		<textarea rows="3" cols="60" id="content"></textarea>
		<button type="button" id="addBtn">댓글쓰기</button>
	</div>
</sec:authorize>
<%-- 댓글 --%>
<div id="replyList"></div>

// 화면에 출력하는 함수(처음 페이지가 로드 되었을 때 무조건 띄워질 수 있게)
function replyListDisplay() {
	$.ajax({
		type: "get",
		url: "<c:url value="/reply/list"/>/"+${securityBoard.num},
		dataType: "json",
		success: function(result) {
			if(result.length == 0) {
				var html="<div style='width: 600px; border-bottom: 1px solid black;'>";
				html+="댓글이 하나도 없습니다.";
				html+="</div>";
				$("#replyList").html(html);
				return;
			}
				
			var html="";
			$(result).each(function() {
				html+="<div style='width: 600px; border-bottom: 1px solid black;'>";
				html+="["+this.num+"]"+this.name+"<br>";
				html+="<pre>"+this.content+"</pre>("+this.regdate+")";
				html+="</div>";
			});
			$("#replyList").html(html);
		},
		error: function(xhr) {
			alert("에러코드 = "+xhr.status);
		}
	});
}
	
replyListDisplay();

// 글쓰기 버튼을 눌렀을 때 hidden으로 처리되어있는 작성자랑 내용 작성 부분 가져옴
  • ajax로 post 방식으로 가져올 때 CSRF 토큰을 전달해야 한다.
  • 1번 방법(ajax 안에 beforeSend 속성 넣기)
//beforeSend 속성 : 페이지 요청전에 실행될 명령이 작성된 함수를 속성값으로 설정
// => XMLHttpRequest 객체를 함수의 매개변수로 제공받아 사용 가능  

beforeSend: function(xhr) {
	xhr.setRequestHeader("${_csrf.headerName}","${_csrf.token}");
},
  • 2번 방법(ajaxSend() 메서드를 호출하여 페이지를 Ajax 기능으로 요청할 경우 무조건 CSRF 토큰 전달) => Ajax를 post 방식으로 요청하는 페이지 마다 설정해줘야됨
//ajaxSend() 메소드를 호출하여 페이지를 Ajax 기능으로 요청할 경우 무조건 CSRF 토큰 전달$(document).ajaxSend(function(event, xhr) {
	xhr.setRequestHeader("${_csrf.headerName}","${_csrf.token}");
});
profile
최선을 다해 꾸준히 노력하는 개발자 망고입니당 :D

0개의 댓글