๐Ÿ’ป ์ฝ”๋”ฉ ์ผ๊ธฐ : [Spring Security] '๊ฒŒ์‹œํŒ ํšŒ์›๊ถŒํ•œ ์„ค์ •' ํŽธ

ybkยท2024๋…„ 5์›” 6์ผ

spring

๋ชฉ๋ก ๋ณด๊ธฐ
28/55
post-thumbnail

๐Ÿ”” '๊ฒŒ์‹œํŒ ํšŒ์›๊ถŒํ•œ ์„ค์ •'์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž!


๋กœ๊ทธ์ธ :

  • ๊ธ€ ์ž‘์„ฑ ์‹œ ์ž‘์„ฑ์ž๋Š” ๋กœ๊ทธ์ธํ•œ ์‚ฌ๋žŒ์˜ ์ด๋ฉ”์ผ
  • ๋กœ๊ทธ์ธํ•œ ์‚ฌ๋žŒ๋งŒ ๊ธ€์“ฐ๊ธฐ ๊ฐ€๋Šฅ
  • ์ž‘์„ฑ์ž ์ž์‹  ๊ธ€๋งŒ ์ˆ˜์ •/์‚ญ์ œ ๊ฐ€๋Šฅ
  • ์ž์‹ ์˜ ํšŒ์›์ •๋ณด๋งŒ ์ˆ˜์ •/์‚ญ์ œ ๊ฐ€๋Šฅ
  • ํšŒ์› ํƒˆํ‡ด ์‹œ ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ

๐Ÿ’Ÿ ๊ธ€ ์ž‘์„ฑ ์‹œ ์ž‘์„ฑ์ž๋Š” ๋กœ๊ทธ์ธํ•œ ์‚ฌ๋žŒ์˜ ์ด๋ฉ”์ผ๋กœ ์กฐํšŒ

  • board ํ…Œ์ด๋ธ”์— member ํ…Œ์ด๋ธ”์˜ ์•„์ด๋””๋ฅผ ์ฐธ์กฐํ•˜๋Š” member_id ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
ALTER TABLE board ADD COLUMN member_id INT REFERENCES member (id);
  • BoardMappe์—์„œ board ํ…Œ์ด๋ธ”๊ณผ member ํ…Œ์ด๋ธ” ์กฐ์ธํ•˜๊ธฐ
@Select("""
        SELECT b.id, 
               b.title,
               m.nick_name writer
        FROM board b JOIN member m ON b.member_id = m.id
        ORDER BY id DESC
        LIMIT #{offset}, 10
        """)
List<Board> selectAllByPage(int offset);
  • ์ž‘์„ฑ์ž๋ฅผ ๋กœ๊ทธ์ธํ•œ ํšŒ์›์˜ ์ด๋ฉ”์ผ๋กœ ๋ฐ›์•„์•ผ ํ•˜๋Š”๋ฐ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ Security๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์ •๋ณด๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด Authentication์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • Authentication ๊ฐ์ฒด์—๋Š” form์—์„œ์˜ username๊ณผ password๋ž‘ UserDetails(CustomUser)์˜ username๊ณผ password๋ฅผ ๋น„๊ตํ•  ๋•Œ ์‚ฌ์šฉํ•œ UserDetails ๊ฐ์ฒด๊ฐ€ ๋กœ๊ทธ์ธ ์„ฑ๊ณตํ–ˆ์„ ๋•Œ Authentication์— ๋“ค์–ด์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ์ฒด๋ฅผ ๊บผ๋‚ด๋Š” ๋ฐฉ๋ฒ•์€ getPrincipal()๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • member์˜ id๋ฅผ ๊บผ๋‚ด์„œ board์˜ member_id์— ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
public void add(Board board, Authentication authentication) {
    Object principal = authentication.getPrincipal();
    if (principal instanceof CustomUser user) {
        Member member = user.getMember();
        board.setMemberId(member.getId());
        mapper.insert(board);
    }
}
  • CustomeUser์—์„œ getMember()๋ฅผ ๋ฐ›์•„์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— CustomUser์— Member ํ•„๋“œ๋ฅผ ๋งŒ๋“ค๊ณ  CustomeUser()์— Member๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ  Getter ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.
@Getter
public class CustomUser extends User {
    private Member member;

    public CustomUser(Member member) {
        super(member.getEmail(), member.getPassword(), List.of());
        this.member = member;
    }
}
  • board๊ฐ€ member์˜ ์•„์ด๋””๋ฅผ ๋ฐ›์•„์„œ board์˜ memberId์— ๋„ฃ์–ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Board ๊ฐ์ฒด์— memberId๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.
@Data
public class Board {
    private Integer id;
    private String title;
    private String content;

    // ์ž‘์„ฑ์ž nickName ์šฉ์œผ๋กœ ์‚ฌ์šฉ๋จ
    private String writer;
    private LocalDateTime inserted;
    private Integer memberId;
}
  • BoardMapper์—์„œ insert ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
@Insert("""
        INSERT INTO board (title, content, member_id)
        VALUES (#{title}, #{content}, #{memberId})
        """)
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Board board);
  • ์กฐํšŒ์‹œ ์กฐํšŒ๋œ ํ™”๋ฉด์— ์ž‘์„ฑ์ž์— memberId๊ฐ€ ๋“ค์–ด๊ฐ€์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— JOIN์„ ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.
@Select("""
        SELECT b.id,
               b.title,
               b.content,
               b.inserted,
               m.nick_name writer,
               m.id member_id
        FROM board b JOIN member m ON b.member_id = m.id
        WHERE b.id = #{id}
        """)
Board selectById(Integer id);

๐Ÿ’Ÿ ์ˆ˜์ •ํ•  ๋•Œ ์ž์‹ ์˜ ๊ธ€๋งŒ ์‚ญ์ œ

  • ๊ถŒํ•œ์ด ์žˆ๋‹ค๋ฉด(hasAccess()) ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
@PostMapping("/delete")
public String delete(Integer id, Authentication authentication) {
    if (service.hasAccess(id, authentication)) {
        service.remove(id);
    }

    return "redirect:/";
}
  • BoardService์—์„œ ๊ถŒํ•œ์˜ ์œ ๋ฌด์— ๋”ฐ๋ผ ์ˆ˜์ • ์—ฌ๋ถ€๋ฅผ ๊ฐ€๋ฆฝ๋‹ˆ๋‹ค.
public boolean hasAccess(Integer id, Authentication authentication) {
    // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ๋ชปํ–ˆ๋‹ค๋ฉด false
	if (authentication == null) {
        return false;
    }
	
    // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ
    Board board = mapper.selectById(id);
    Object principal = authentication.getPrincipal();
    if (principal instanceof CustomUser user) {
        Member member = user.getMember();
        return board.getMemberId().equals(member.getId());
    }
    return false;
}
  • ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด ๊ฒŒ์‹œ๊ธ€(board)์˜ ์žˆ๋Š” Member์˜ id์™€ ๋กœ๊ทธ์ธํ•œ id์™€ ๊ฐ™์€์ง€ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
@Select("""
        SELECT b.id,
               b.title,
               b.content,
               b.inserted,
               m.nick_name writer,
               m.id member_id
        FROM board b JOIN member m ON b.member_id = m.id
        WHERE b.id = #{id}
        """)
Board selectById(Integer id);

๐Ÿ’Ÿ ์ˆ˜์ •ํ•  ๋•Œ ์ž์‹ ์˜ ๊ธ€๋งŒ ์ˆ˜์ •

  • ๋กœ๊ทธ์ธํ•œ ์‚ฌ๋žŒ์˜ ์ •๋ณด(id) ๋ฅผ ๋ฐ›์•„ ๊ถŒํ•œ์ด ์žˆ๋‹ค๋ฉด ์ž‘์„ฑ์ž ๊ฒŒ์‹œ๊ธ€๋งŒ ์ˆ˜์ •๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
@PostMapping("/modify")
public String modifyPost(Board board, Authentication authentication, RedirectAttributes rttr) {

    if (service.hasAccess(board.getId(), authentication)) {
        service.modify(board);
    }

    rttr.addAttribute("id", board.getId());
    return "redirect:/board";
}
  • ์ž‘์„ฑ์ž ์ž์‹ ์˜ ๊ธ€์ผ ๋•Œ๋งŒ ์‚ญ์ œ, ์ˆ˜์ •๋ฒ„ํŠผ ๋ณด์ด๊ณ  ๋‹ค๋ฅธ ํšŒ์›์˜ ๊ฒŒ์‹œ๊ธ€์€ ์•ˆ๋ณด์ด๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
<sec:authorize access="isAuthenticated()"> <%-- ๋กœ๊ทธ์ธ์ด ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ--%>
  <%-- 1. ๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž์˜ id์™€ 2.๊ฒŒ์‹œ๋ฌผ์˜ memberId๊ฐ€ ๊ฐ™์œผ๋ฉด ๋ฒ„ํŠผ ๋ณด์—ฌ์คŒ--%>
    <sec:authentication property="principal.member" var="member"/>
    <c:if test="${member.id eq board.memberId}">
        <div class="mb-3">
            <button form="formDelete" class="btn btn-danger">์‚ญ์ œ</button>
            <a href="/modify?id=${board.id}" class="btn btn-secondary">์ˆ˜์ •</a>
        </div>
    </c:if>
</sec:authorize>
  • ์‚ฌ์šฉ์ž์˜ id๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด์„œ property ์†์„ฑ์—์„œ principal์•ˆ์— member ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์คฌ๊ธฐ ๋•Œ๋ฌธ์— principal.member๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ id์™€ ๊ฒŒ์‹œ๋ฌผ์˜ memberId๊ฐ€ ๊ฐ™์•„๋ฉด ์ˆ˜์ •, ์‚ญ์ œ ๋ฒ„ํŠผ์ด ๋ณด์ด๊ฒŒํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’Ÿ ์ž์‹ ์˜ ํšŒ์›์ •๋ณด๋งŒ ์‚ญ์ œ ๊ฐ€๋Šฅ

  • ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•  ๋•Œ๋งŒ ์‚ญ์ œ ํ•ฉ๋‹ˆ๋‹ค.
@PostMapping("remove")
public String remove(Integer id, Authentication authentication) {
    if (service.hasAccess(id, authentication)) {
        service.remove(id);
    }

    return "redirect:/logout";
}
  • MemberService์—์„œ ๊ถŒํ•œ์˜ ์œ ๋ฌด์— ๋”ฐ๋ผ ์ˆ˜์ • ์—ฌ๋ถ€๋ฅผ ๊ฐ€๋ฆฝ๋‹ˆ๋‹ค.
public boolean hasAccess(Integer id, Authentication authentication) {
    if (authentication == null) {
        return false;
    }

    Object principal = authentication.getPrincipal();
    if (principal instanceof CustomUser user) {
        Member member = user.getMember();
        return member.getId().equals(id);
    }
    return false;
}

๐Ÿ’Ÿ ์ž์‹ ์˜ ํšŒ์›์ •๋ณด๋งŒ ์ˆ˜์ • ๊ฐ€๋Šฅ

ํŒจ์Šค์›Œ๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•ด์„œ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

public void modify(Member member) {
    if (member.getPassword() != null && member.getPassword().length() > 0) {
        // ์•”ํ˜ธ๋ฅผ ์ž…๋ ฅํ–ˆ์„ ๋•Œ๋งŒ ๋ณ€๊ฒฝ
        member.setPassword(encoder.encode(member.getPassword()));
    } else {
        // ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ธฐ์กด ์•”ํ˜ธ ์œ ์ง€
        Member old = mapper.selectById(member.getId());
        member.setPassword(old.getPassword());
    }

    mapper.update(member);
}

๐Ÿ’Ÿ ํšŒ์› ํƒˆํ‡ด ์‹œ ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ

  • ํšŒ์› ํƒˆํ‡ดํ•  ๋•Œ board ํ…Œ์ด๋ธ”์˜ ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ํ›„ ํƒˆํ‡ด๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” board ํ…Œ์ด๋ธ”์ด member ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž‘์„ฑํ•œ ๊ธ€์„ ๋จผ์ € ์‚ญ์ œํ•˜๊ณ  ๊ทธ ๋‹ค์Œ ํšŒ์› ํƒˆํ‡ด๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
public void remove(Integer id) {
    // board ํ…Œ์ด๋ธ”์—์„œ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ
    boardMapper.deleteBoardByMemberId(id);

    // member ํ…Œ์ด๋ธ”์—์„œ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ
    mapper.deleteById(id);
}
profile
๊ฐœ๋ฐœ์ž ์ค€๋น„์ƒ~

0๊ฐœ์˜ ๋Œ“๊ธ€