๐Ÿ’ป ์ฝ”๋”ฉ ์ผ๊ธฐ : [Spring Security] '๊ด€๋ฆฌ์ž ์ ‘๊ทผ' ํŽธ

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

spring

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

๐Ÿ”” '๊ด€๋ฆฌ์ž ๊ถŒํ•œ ์ ‘๊ทผ'์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž!

๋กœ๊ทธ์ธ :

  • ํšŒ์›์ •๋ณด๋Š” ๋ณธ์ธ๊ณผ ๊ด€๋ฆฌ์ž๋งŒ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’Ÿ ํšŒ์›์ •๋ณด๋Š” ๋ณธ์ธ๊ณผ ๊ด€๋ฆฌ์ž๋งŒ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ์ ‘๊ทผ

1. ๊ถŒํ•œ(authority) ํ…Œ์ด๋ธ” ๋งŒ๋“ค๊ธฐ

CREATE TABLE authority
(
    id        INT PRIMARY KEY AUTO_INCREMENT,
    member_id INT         NOT NULL REFERENCES member (id),
    name      VARCHAR(20) NOT NULL
);
  • ์‚ฌ์šฉ์ž ID์— ๋”ฐ๋ฅธ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

2. MemberMapper์—์„œ ๋ฉค๋ฒ„ ์•„์ด๋””์— ์˜ํ•ด ์ €์žฅ๋œ ๊ถŒํ•œ ์ •๋ณด ์กฐํšŒํ•˜๊ธฐ

@Select("""
        SELECT name FROM authority WHERE member_id = #{memberId}
        """)
List<String> selectAuthorityByMemberId(Integer memberId);

3. UseDetails์— ๊ถŒํ•œ ์ฃผ์ž…ํ•˜๊ธฐ

  • ์‚ฌ์šฉ์ž DB ์กฐํšŒํ•˜๊ณ  ํ•ด๋‹น ์•„์ด๋””์˜ ๊ถŒํ•œ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
@Component
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final MemberMapper mapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Member member = mapper.selectByEmail(username);
        List<String> authority = mapper.selectAuthorityByMemberId(member.getId());
        member.setAuthority(authority);
        return new CustomUser(member);
    }
}
  • ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ID์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๊ณ  ์ด๋ฅผ CustomUser ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

4. ์‚ฌ์šฉ์ž ๊ถŒํ•œ ์ €์žฅ ๋ฐ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ๊ถŒํ•œ ์ „๋‹ฌ ๋ฐ›๊ธฐ

@Getter
public class CustomUser extends User {
    private Member member;

    public CustomUser(Member member) {
        super(member.getEmail(), member.getPassword(), member.getAuthority().stream().map(SimpleGrantedAuthority::new).toList());
        this.member = member;
    }
}
  • CustomUser(Member member) : User ํด๋ž˜์Šค์˜ ์ƒ์„ฑ์ž๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํšŒ์›์˜ ์ด๋ฉ”์ผ, ๋น„๋ฐ€๋ฒˆํ˜ธ, ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋ฐ›์•„์„œ CustomUser ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์ •๋ณด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ GrantedAuthority ๊ฐ์ฒด์˜ ๋ฆฌ์ŠคํŠธ(Collection)์œผ๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Stream์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋ฆฌ์ŠคํŠธ ํ˜•์‹์œผ๋กœ ๊ฐ€์ ธ์™€ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ ํ›„ map() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SimpleGrantedAuthority ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด ๋‹ค์‹œ ๋ฆฌ์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

5. ํšŒ์› ์ •๋ณด ํŽ˜์ด์ง€๋กœ ๋งํฌ ํƒ€๊ณ  ๋“ค์–ด๊ฐˆ ๋•Œ admin์ผ ๋•Œ๋งŒ ๋“ค์–ด๊ฐ€๋„๋ก ์„ค์ •ํ•˜๊ธฐ

@GetMapping("list")
@PreAuthorize("hasAnyAuthority('admin')")
public String list(Member member, Model model) {
    model.addAttribute("memberList", service.list());
    return "member/list";
}

6. ํšŒ์› ์ •๋ณด ๊ด€๋ฆฌ์ž๋งŒ ์ ‘๊ทผํ•˜๊ธฐ

@GetMapping("")
public String view(Integer id, Authentication authentication, Model model) {
    if (service.hasAccess(id, authentication) || service.isAdmin(authentication)) {
        model.addAttribute("member", service.get(id));
        return "member/info";
    }
    return "redirect:/";
}
  • ์ ‘๊ทผ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž์ธ์ง€ ๊ด€๋ฆฌ์ž์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ํ™•์ธ ํ›„ ํ•ด๋‹น ํšŒ์› ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
public boolean isAdmin(Authentication authentication) {
    Object principal = authentication.getPrincipal();
    if (principal instanceof CustomUser user) {
        return user.getAuthorities().stream().map(GrantedAuthority::getAuthority).anyMatch(authority -> authority.equals("admin"));
    }
    return false;
}
- ์‚ฌ์šฉ์ž๊ฐ€ ์ž๊ธด ๊ถŒํ•œ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์ง„ ๊ถŒํ•œ ์ค‘์— admin ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์—ฌ true์ด๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๊ด€๋ฆฌ์ž์ž…๋‹ˆ๋‹ค.

7. ํšŒ์› ์ •๋ณด ์ž์‹  ์ •๋ณด ์ ‘๊ทผ ๊ฐ€๋Šฅ

<sec:authorize access="isAuthenticated()">
    <sec:authorize access="hasAuthority('admin')">
        <li class="nav-item">
            <a href="/member/list" class="nav-link">
                ํšŒ์›๋ชฉ๋ก
            </a>
        </li>
    </sec:authorize>
</sec:authorize>

<sec:authorize access="isAuthenticated()">
    <sec:authentication property="principal.member" var="authMember"></sec:authentication>
    <li class="nav-item">
        <a href="/member?id=${authMember.id}" class="nav-link">๋‚ด์ •๋ณด</a>
    </li>
</sec:authorize>
  • navbar์— ๋‚ด ์ •๋ณด๋ฅผ ๋งŒ๋“ค์–ด id๋กœ ํƒ€๊ณ  ๋“ค์–ด๊ฐ€ ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ ๋‚ด ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
profile
๊ฐœ๋ฐœ์ž ์ค€๋น„์ƒ~

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