Spring๐ŸŒธ

Spring Security - ๊ธฐ๋ณธ

Jeein0313 2023. 5. 20. 19:59

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ณต๊ฒฉํ•˜๋Š” ์„ธ์…˜ ๊ณ ์ • ๊ณต๊ฒฉ, ํด๋ฆญ์žฌํ‚น ๊ณต๊ฒฉ, CSRF ๋“ฑ์˜ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ๊ณ ๋ คํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Spring Security๋ผ๋Š” ๋ณด์•ˆ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

*์„ธ์…˜ ๊ณ ์ • ๊ณต๊ฒฉ(Session Fixation) : ๊ณต๊ฒฉ์ž๊ฐ€ ์œ ํšจํ•œ ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์„ ๊ฐ€๋กœ์ฑ„๋„๋ก ํ—ˆ์šฉํ•˜๋Š”  ๊ณต๊ฒฉ. ์ด ๊ณต๊ฒฉ์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด session ID, ํŠนํžˆ ์ทจ์•ฝํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์˜ ํ•œ๊ณ„๋ฅผ ํƒ์ƒ‰ํ•œ๋‹ค. ์‚ฌ์šฉ์ž ์ธ์ฆ ์‹œ ์ƒˆ๋กœ์šด session ID๋ฅผ ํ• ๋‹นํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ(๋กœ๊ทธ์ธ ์‹œ ๋ฐœ๊ธ‰๋ฐ›์€ ์„ธ์…˜ ID๊ฐ€ ๋กœ๊ทธ์ธ ์ „/ํ›„ ๋ชจ๋‘ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉ๋จ) ๊ธฐ์กด session ID๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ณต๊ฒฉ์€ ์œ ํšจํ•œ session ID๋ฅผ ์–ป์€ ํ›„ ํ•ด๋‹น ์„ธ์…˜ ID๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๋„๋ก ์œ ๋„ํ•˜๊ณ , ์‚ฌ์šฉ๋œ session ID ๋ฅผ ์•Œ์•„๋‚ด์–ด ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆํ•œ ์„ธ์…˜์„ ํƒˆ์ทจํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.

์„ธ์…˜ ๊ณ ์ • ๊ณต๊ฒฉ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ํ•œ ํ›„์— ํด๋ผ์ด์–ธํŠธ์™€ ์›น ์„œ๋ฒ„ ๊ฐ„์— ์ˆ˜๋ฆฝ๋œ ์„ธ์…˜์„ ๋„์ฒญํ•˜๋Š” ์„ธ์…˜ ํ•˜์ด์žฌํ‚น์˜ ์ผ์ข…์ด ์•„๋‹ˆ๋ฉฐ, ํ”ผํ•ด์ž์˜ ๋ธŒ๋ผ์šฐ์ €์— established๋œ ์„ธ์…˜์„ fix ์‹œํ‚ค๋ฏ€๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜๊ธฐ ์ „์— ๊ณต๊ฒฉ์ด ์‹œ์ž‘๋œ๋‹ค.

 

*ํด๋ฆญ์žฌํ‚น ๊ณต๊ฒฉ : ์›น ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์ด ํด๋ฆญํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ์ธ์ง€ํ•˜๋Š” ๊ฒƒ๊ณผ ๋‹ค๋ฅธ ์–ด๋–ค ๊ฒƒ์„ ํด๋ฆญํ•˜๊ฒŒ ์†์ด๋Š” ์•…์˜์ ์ธ ๊ธฐ๋ฒ•์œผ๋กœ์จ ์ž ์žฌ์ ์œผ๋กœ ๊ณต๊ฒฉ์ž๋Š” ๋น„๋ฐ€์ •๋ณด๋ฅผ ์œ ์ถœ์‹œํ‚ค๊ฑฐ๋‚˜ ๊ทธ๋“ค์˜ ์ปดํ“จํ„ฐ์— ๋Œ€ํ•œ ์ œ์–ด๋ฅผ ํš๋“ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.(์‚ฌ์šฉ์ž๋ฅผ ์†์—ฌ ๋ณด์ด์ง€ ์•Š๋Š” ์š”์†Œ๋ฅผ ํด๋ฆญํ•˜๋„๋ก ํ•  ๋•Œ ๋ฐœ์ƒํ•จ)

ํ•ดํ‚น๋œ ์‚ฌ์ดํŠธ๋ฅผ ํด๋ฆญํ•˜๋ฉด ๋‹ค๋ฅธ ๋ถˆ๋Ÿ‰ iframe ์›น ์‚ฌ์ดํŠธ์—์„œ ์ž‘์—…์ด ์‹คํ–‰๋˜๊ณ , ์ด๋Ÿฌํ•œ ํด๋ฆญ์œผ๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž ์‚ญ์ œ, ๊ถŒํ•œ ์—…๋ฐ์ดํŠธ ๋˜๋Š” ๊ธฐํƒ€ ์ž‘์—…์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์–ด๋–ค ๋น„๋””์˜ค์— ๋งํฌ๋œ ์ด๋ฉ”์ผ์„ ๋ฐ›์•˜์„ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ ํ”Œ๋ ˆ์ด ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ ์ด๋Š” ์•„๋งˆ์กด์˜ ์ƒํ’ˆ ํŽ˜์ด์ง€์ด ์ƒํ’ˆ ๊ตฌ๋งค ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ๊ฒƒ์ผ ์ˆ˜ ์žˆ๋‹ค. 

 

*CSRF๋Š” https://nothing-is-on-my-way.tistory.com/83 ์ฐธ๊ณ .

 

 

 

Spring Security๋ž€ Spring MVC ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์ฆ๊ณผ ์ธ๊ฐ€ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๋Š” ๋ณด์•ˆ ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.

Spring MVC ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณด์•ˆ์„ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ์‹ค์ƒ์˜ ํ‘œ์ค€์ด๋‹ค.

 

Spring์—์„œ ์ง€์›ํ•˜๋Š” Interceptor๋‚˜ Servlet Filter๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ณด์•ˆ ๊ธฐ๋Šฅ์„ ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณด์•ˆ์˜ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์„ Spring Security์—์„œ ์•ˆ์ •์ ์œผ๋กœ ์ง€์›ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ๊ตฌ์กฐ์ ์œผ๋กœ ์ž˜ ๋งŒ๋“ค์–ด์ง„ ๊ฒ€์ฆ๋œ Spring Security๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•œ ์„ ํƒ์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

 

Spring Security๋กœ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ณด์•ˆ ๊ฐ•ํ™” ๊ธฐ๋Šฅ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ๋‹ค์–‘ํ•œ ์œ ํ˜•(ํผ ๋กœ๊ทธ์ธ ์ธ์ฆ, ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ, OAuth 2 ๊ธฐ๋ฐ˜ ์ธ์ฆ, LADP ์ธ์ฆ)์˜ ์‚ฌ์šฉ์ž ์ธ์ฆ ๊ธฐ๋Šฅ ์ ์šฉ
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‚ฌ์šฉ์ž์˜ ์—ญํ• (Role)์— ๋”ฐ๋ฅธ ๊ถŒํ•œ ๋ ˆ๋ฒจ ์ ์šฉ
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ ‘๊ทผ ์ œ์–ด
  • ๋ฏผ๊ฐํ•œ ์ •๋ณด์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™”
  • SSL ์ ์šฉ
  • ์ผ๋ฐ˜์ ์œผ๋กœ ์•Œ๋ ค์ง„ ์›น ๋ณด์•ˆ ๊ณต๊ฒฉ ์ฐจ๋‹จ

์ด ์™ธ์—๋„ SSO, ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„œ ๊ธฐ๋ฐ˜ ์ธ์ฆ, ๋ฉ”์„œ๋“œ ๋ณด์•ˆ, ์ ‘๊ทผ ์ œ์–ด ๋ชฉ๋ก ๊ฐ™์€ ๋ณด์•ˆ์„ ์œ„ํ•œ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•œ๋‹ค.

 

 

 

Spring Security์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์šฉ์–ด ์ •๋ฆฌ

  • Principal(์ฃผ์ฒด) 
    • Spring Security์—์„œ ์‚ฌ์šฉ๋˜๋Š” Principal ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž, ๋””๋ฐ”์ด์Šค ๋˜๋Š” ์‹œ์Šคํ…œ ๋“ฑ์ด ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ์ธ์ฆ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜ํ–‰๋œ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ • ์ •๋ณด๋ฅผ ์˜๋ฏธ

 

  • Authentication(์ธ์ฆ)
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ณธ์ธ์ด ๋งž์Œ์„ ์ฆ๋ช…ํ•˜๋Š” ์ ˆ์ฐจ๋ฅผ ์˜๋ฏธ.
    • Authentication (์ธ์ฆ)์„ ์œ„ํ•ด์„œ๋Š” Credential(์‹œ์› ์ฆ๋ช… ์ •๋ณด)๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
    • ํŠน์ • ์‚ฌ์ดํŠธ์˜ ๋กœ๊ทธ์ธ์„ ์œ„ํ•ด ์ž…๋ ฅํ•˜๋Š” ํŒจ์Šค์›Œ๋“œ ์—ญ์‹œ Credential

 

  • Authorization(์ธ๊ฐ€, ๊ถŒํ•œ ๋ถ€์—ฌ)
    • Authentication์ด ์ •์ƒ์ ์œผ๋กœ ์ˆ˜ํ–‰๋œ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ•˜๋‚˜ ์ด์ƒ์˜ ๊ถŒํ•œ(authority)์„ ๋ถ€์—ฌํ•˜์—ฌ ํŠน์ • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํŠน์ • ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ—ˆ๊ฐ€ํ•˜๋Š” ๊ณผ์ •์„ ์˜๋ฏธ.
    • Authorization์€ Authentication ๊ณผ์ • ์ดํ›„ ์ˆ˜ํ–‰๋˜์–ด์•ผ ํ•˜๋ฉฐ ๊ถŒํ•œ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์—ญํ• (Role) ํ˜•ํƒœ๋กœ ๋ถ€์—ฌ๋œ๋‹ค.

 

  • Access Control 
    • ์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜๋Š” ํ–‰์œ„๋ฅผ ์ œ์–ดํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

 

 

Hello, Spring Security ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ Spring Security์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ์•Œ์•„๋ณด์ž.

 

Security์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ์™€ ๋™์ž‘ ๋ฐฉ์‹์„ ๊ฐ€์žฅ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด SSR ๋ฐฉ์‹์œผ๋กœ ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์ž.

 

Hello Spring Security ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ Home ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

 

ํšŒ์› ๊ฐ€์ž… ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layouts/common-layout">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Hello Spring Security Coffee Shop</title>
</head>
<body>
    <hr />
    <div class="container" layout:fragment="content">
        <!-- (1) ํšŒ์› ๊ฐ€์ž… ํผ -->
        <form action="/members/register" method="post">
            <div class="row">
                <div class="col-xs-2">
                    <input type="text" name="fullName" class="form-control" placeholder="User Name"/>
                </div>
            </div>
            <div class="row" style="margin-top: 20px">
                <div class="col-xs-2">
                    <input type="email" name="email" class="form-control" placeholder="Email"/>
                </div>
            </div>
            <div class="row" style="margin-top: 20px">
                <div class="col-xs-2">
                    <input type="password" name="password" class="form-control" placeholder="Password"/>
                </div>
            </div>

            <button class="btn btn-outline-secondary" style="margin-top: 20px">ํšŒ์› ๊ฐ€์ž…</button>
        </form>
    </div>
</body>
</html>

(1)์˜ HTML form ํƒœ๊ทธ๋ฅผ ์ด์šฉํ•ด์„œ ํšŒ์› ๊ฐ€์ž… ํผ์„ ๊ตฌ์„ฑํ•˜๋ฉฐ ํ•ด๋‹น ํผ์—์„œ ์ž…๋ ฅํ•œ ์ •๋ณด๋Š” ์•„๋ž˜์˜ ์ฝ”๋“œ์—์„œ MemberController์˜ registerMember() ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ๋‹ค.

 

 

package com.codestates.member;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Controller
@RequestMapping("/members")
public class MemberController {
    private final MemberService memberService;
    private final MemberMapper mapper;

    public MemberController(MemberService memberService, MemberMapper mapper) {
        this.memberService = memberService;
        this.mapper = mapper;
    }

    @GetMapping("/register")
    public String registerMemberForm() {
        return "member-register";
    }

    @PostMapping("/register")
    public String registerMember(@Valid MemberDto.Post requestBody) {
        Member member = mapper.memberPostToMember(requestBody);
        memberService.createMember(member);

        System.out.println("Member Registration Successfully");
        return "login";
    }

    @GetMapping("/my-page")
    public String myPage() {
        return "my-page";
    }
}

 

๋กœ๊ทธ์ธ ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 


์ปคํ”ผ ๋ณด๊ธฐ ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

 

์ „์ฒด ์ฃผ๋ฌธ ๋ชฉ๋ก ๋ณด๊ธฐ ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

 

Spring Security๋ฅผ ์ ์šฉํ–ˆ์„ ๋•Œ, ๊ด€๋ฆฌ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

 

๋งˆ์ดํŽ˜์ด์ง€ ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

 

๋งˆ์ดํŽ˜์ด์ง€ ํ™”๋ฉด์€ Spring Security๋ฅผ ์ ์šฉํ–ˆ์„ ๋•Œ, ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€์ด์–ด์•ผ ํ•œ๋‹ค.

 

 

 

์ด์ œ ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— Spring Security ์˜์กด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

build.gradle์˜ dependencies์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด spring-boot-starter-security ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

dependencies {
	...
  ...

	implementation 'org.springframework.boot:spring-boot-starter-security'   // (1)
  
  ...
  ...
}

 

 

๊ทธ๋ฆฌ๊ณ  ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜์—ฌ localhost:8080/login์— ์ ‘์†ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚œ๋‹ค.

์ด ํ™”๋ฉด์€ ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ๋งŒ๋“  ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ๋ฐ”๋กœ Spring Security์—์„œ ์ž๋™ ๊ตฌ์„ฑ์„ ํ†ตํ•ด ๋‚ด๋ถ€์ ์œผ๋กœ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋””ํดํŠธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  Username๊ณผ password ์—ญ์‹œ ๋””ํดํŠธ ๊ฐ’์ด  ์•„๋ž˜์™€ ๊ฐ™์ด ์ •ํ•ด์ ธ ์žˆ๋‹ค.

 

 

โœ”๏ธ Spring Security์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋””ํดํŠธ ๋กœ๊ทธ์ธ ์ •๋ณด

  • Username : 'user'
  • Password : ์•„๋ž˜์™€ ๊ฐ™์ด ์„œ๋ฒ„์˜ ๋กœ๊ทธ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ

 

ํ•˜์ง€๋งŒ ์ด ๋ฐฉ์‹์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ํŒจ์Šค์›Œ๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉฐ, Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” ๋””ํดํŠธ ์ธ์ฆ ์ •๋ณด๋งŒ์œผ๋กœ๋Š” ํšŒ์› ๊ฐ์ž์˜ ์ธ์ฆ ์ •๋ณด๋กœ ๋กœ๊ทธ์ธ ํ•  ์ˆ˜ ์—†๋‹ค.

 

๋”ฐ๋ผ์„œ Spring Security์˜ Configuration์„ ํ†ตํ•ด ์šฐ๋ฆฌ์—๊ฒŒ ๋งž๋Š” ์ธ์ฆ ๋ฐฉ์‹์„ ์„ค์ •ํ•ด๋ณด์ž.

 

 

Spring Security Configuration์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

package com.codestates.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class SecurityConfiguration {
    // ์—ฌ๊ธฐ์— Spring Security์˜ ์„ค์ •์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
}

 

 

์—ฌ๊ธฐ์— InMemory User๋กœ ์ธ์ฆํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ • ์ •๋ณด๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

 

package com.codestates.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;

@Configuration
public class SecurityConfiguration {

    @Bean
    public UserDetailsManager userDetailsService(){
        UserDetails userDetails =
                User.withDefaultPasswordEncoder()
                        .username("jeein@naver.com")
                        .password("1111")
                        .roles("USER")
                        .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

}

์œ„ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž์˜ ๊ณ„์ • ์ •๋ณด๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์ƒ์— ์ง€์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ์‚ฌ์šฉ์ž ๊ณ„์ • ์ •๋ณด๊ฐ€ ๋ฐ”๋€” ์ผ์€ ์—†๋‹ค.

UserDetails ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ํ•ต์‹ฌ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์œผ๋ฉฐ, UserDetails ๊ตฌํ˜„์ฒด์ธ User ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ๋‹ค.

  • withDefaultPasswordEncoder()๋Š” ๋””ํดํŠธ ํŒจ์Šค์›Œ๋“œ ์ธ์ฝ”๋”๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž์˜ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•œ๋‹ค.
  • username() ๋ฉ”์„œ๋“œ๋Š” ์‚ฌ์šฉ์ž์˜ username์„ ์„ค์ •ํ•œ๋‹ค. (์—ฌ๊ธฐ์„œ username์€ ํšŒ์›๋ช…์ด ์•„๋‹ˆ๋ผ, ํšŒ์›์„ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž ์•„์ด๋”” ๊ฐ™์€ ๊ฐ’)
  • password() ๋ฉ”์„œ๋“œ๋Š” ์‚ฌ์šฉ์ž์˜ password๋ฅผ ์„ค์ •ํ•œ๋‹ค. 
  • roles() ๋ฉ”์„œ๋“œ๋Š” ์‚ฌ์šฉ์ž์˜ Role, ์ฆ‰ ์—ญํ• ์„ ์ง€์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค. 

 

Spring Security์—์„œ๋Š” ์‚ฌ์šฉ์ž์˜ ํ•ต์‹ฌ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ UserDetails๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” UserDetailsManager๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์šฐ๋ฆฌ๋Š” ๋ฉ”๋ชจ๋ฆฌ์ƒ์—์„œ UserDetails๋ฅผ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ InMemoryUserDetailsManager๋ผ๋Š” ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

 

*UseDetails ์ธํ„ฐํŽ˜์ด์Šค

UserDetails ์ธํ„ฐํŽ˜์ด์Šค๋Š” ํ•ต์‹ฌ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์ด๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋Š” Spring Security์—์„œ ์ง์ ‘์ ์œผ๋กœ ๋ณด์•ˆ์„ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜์ง€๋Š” ์•Š๋Š”๋‹ค. ๊ตฌํ˜„ ํด๋ž˜์Šค๋Š” ๋‹จ์ˆœํžˆ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด๊ณ , ์ด ์ •๋ณด๋Š” ๋‚˜์ค‘์— ์ธ์ฆ(Authentication) ๊ฐ์ฒด๋กœ ์บก์Аํ™”๋œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ณด์•ˆ๊ณผ ๊ด€๋ จ์ด ์—†๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํŽธ๋ฆฌํ•œ ์œ„์น˜์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

*withDefaultPasswordEncoder : withDefaultPasswordEncoder() ๋ฉ”์„œ๋“œ์˜ Deprecated๋Š” ํŠน์ดํ•˜๊ฒŒ๋„ ํ–ฅํ›„ ๋ฒ„์ „์—์„œ ์ œ๊ฑฐ๋จ์„ ์˜๋ฏธํ•˜๊ธฐ ๋ณด๋‹ค๋Š” Production ํ™˜๊ฒฝ์—์„œ ์ธ์ฆ์„ ์œ„ํ•œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ณ ์ •ํ•ด์„œ ์‚ฌ์šฉํ•˜์ง€ ๋ง๋ผ๋Š” ๊ฒฝ๊ณ ์˜ ์˜๋ฏธ๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ  ์žˆ๋Š” ๊ฒƒ์ด๋‹ˆ ๋ฐ˜๋“œ์‹œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์ด๋‚˜ ๋ฐ๋ชจ ํ™˜๊ฒฝ์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.

์œ„์— ๋‚˜์™€ ์žˆ๋“ฏ์ด UserDetails ์ƒ์„ฑํ•  ๋•Œ์— User.withDefaultPasswordEncoder()๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์†Œ์Šค์ฝ”๋“œ์— "password"๊ฐ€ ์ปดํŒŒ์ผ๋˜๊ณ  ์ƒ์„ฑ ์‹œ์ ์— ๋ฉ”๋ชจ๋ฆฌ์— ํฌํ•จ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์•”ํ˜ธ๊ฐ€ plain text๋กœ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ์–ด์„œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฏธ๋ฆฌ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ•ด์‹œํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด 

 PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
 // outputs {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
 // remember the password that is printed out and use in the next step
 System.out.println(encoder.encode("password"));
  
 
 UserDetails user = User.withUsername("user")
     .password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG")
     .roles("USER")
     .build();

 

 

์ด๋ ‡๊ฒŒ ๊ฐ™์ด new InMemoryUserDetailsManager(userDetails)๋ฅผ ํ†ตํ•ด UserDetailsManager ๊ฐ์ฒด๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•˜๋ฉด Spring ์—์„œ๋Š” ํ•ด๋‹น Bean์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๊ฐ€ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ๋„˜์–ด์˜ฌ ๊ฒฝ์šฐ, ์ •์ƒ์ ์ธ ์ธ์ฆ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

 

๋‹ค์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰์‹œํ‚ค๊ณ  ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

 

 

ํ•˜์ง€๋งŒ ์‹ค์ œ ์„œ๋น„์Šค์—์„œ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉ์ž ๊ณ„์ • ์ •๋ณด๋ฅผ ๊ณ ์ •ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜๋Š” ์—†๋‹ค. ์‹ค์ œ๋กœ๋Š” ์‚ฌ์šฉ์ž ๊ณ„์ • ์ •๋ณด๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜์–ด์•ผ ํ•œ๋‹ค.

 

๋‹ค์Œ์œผ๋กœ Spring Security์—์„œ HTTP ๋ณด์•ˆ์„ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด ๋ณด์ž.

์ด๋•Œ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  custom ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

package com.codestates.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        //HttpSecurity๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์— ๋Œ€ํ•œ ๋ณด์•ˆ ์„ค์ • ๊ตฌ์„ฑ
        http
                .csrf().disable() //(1)
                .formLogin() //(2)
                .loginPage("/auths/login-form") //(3)
                .loginProcessingUrl("/process_login") //(4)
                .failureUrl("/auths/login-form?error") //(5)
                .and() //(6)
                .authorizeHttpRequests() //(7)
                .anyRequest() //(8)
                .permitAll(); //(9)
        
        return http.build();

    }

    @Bean
    public UserDetailsManager userDetailsService(){
        UserDetails userDetails =
                User.withDefaultPasswordEncoder()
                        .username("jeein@naver.com")
                        .password("1111")
                        .roles("USER")
                        .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

}
  • (1)์—์„œ๋Š” CSRF(Cross-Site Reqeust Forgery) ๊ณต๊ฒฉ์— ๋Œ€ํ•œ Spring Security ์„ค์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  ์žˆ๋‹ค. Spring Security๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์•„๋ฌด ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด csrf() ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ CSRF Token์„ ์ˆ˜์‹  ํ›„ ๊ฒ€์ฆํ•œ๋‹ค. 
  • (2)์—์„œ๋Š” formLogin()์„ ํ†ตํ•ด ๊ธฐ๋ณธ์ ์ธ ์ธ์ฆ ๋ฐฉ๋ฒ•์„ ํผ ๋กœ๊ทธ์ธ ๋ฐฉ์‹์œผ๋กœ ์ง€์ •ํ•œ๋‹ค.
  • (3)์˜ loginPage()์—์„œ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค์–ด ๋‘” custom ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค.
  • (4)์˜ loginProcessingUrl("/process_login") ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์ธ์ฆ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•  ์š”์ฒญ URL์„ ์ง€์ •ํ•œ๋‹ค. loginProcessing()์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์ธ "/process_login"์€ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค์–ด ๋‘” login.html์˜ form ํƒœ๊ทธ์˜ action ์†์„ฑ์— ์ง€์ •ํ•œ URL๊ณผ ๋™์ผํ•˜๋‹ค. ์•„์ง ์šฐ๋ฆฌ๋Š” AuthController์— ๋ณ„๋„์˜ ์ธ์ฆ ํ”„๋กœ์„ธ์Šค ๋กœ์ง์„ ์ง€์ •ํ•˜์ง€ ์•Š์•˜๋‹ค. ๊ฒฐ๊ตญ /process_login URL๋กœ ์š”์ฒญ์„ ์ „์†กํ•˜๋ฉด ์—ฌ์ „ํžˆ Spring Security์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ์ธ์ฆ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • (5)์˜ failureUrl("/auth/login-form?error") ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์ธ์ฆ์ด ์‹คํŒจํ•  ๊ฒฝ์šฐ ์–ด๋–ค ํ™”๋ฉด์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•  ๊ฒƒ์ธ๊ฐ€๋ฅผ ์ง€์ •ํ•œ๋‹ค.

 

๋กœ๊ทธ์ธ์— ์‹คํŒจํ•  ๊ฒฝ์šฐ, ๋กœ๊ทธ์ธ ํ™”๋ฉด์„ ํ‘œ์‹œํ•˜๊ณ  ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์‹คํŒจํ–ˆ๋‹ค๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ด ์ฃผ๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ž์—ฐ์Šค๋Ÿฌ์›€์œผ๋กœ failureUrl()์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์˜ URL์ธ "/auths/login-form?error"๋ฅผ ์ง€์ •ํ•œ๋‹ค.

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layouts/common-layout">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Hello Spring Security Coffee Shop</title>
    </head>
    <body>
        <div class="container" layout:fragment="content">
            <form action="/process_login" method="post">
                <!-- (1) ๋กœ๊ทธ์ธ ์‹คํŒจ์— ๋Œ€ํ•œ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ -->
                <div class="row alert alert-danger center" role="alert" th:if="${param.error != null}">
                    <div>๋กœ๊ทธ์ธ ์ธ์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.</div>
                </div>
                <div class="row">
                    <div class="col-xs-2">
                        <input type="email" name="username"  class="form-control" placeholder="Email" />
                    </div>
                </div>
                <div class="row" style="margin-top: 20px">
                    <div class="col-xs-2">
                        <input type="password" name="password"  class="form-control" placeholder="Password" />
                    </div>
                </div>

                <button class="btn btn-outline-secondary" style="margin-top: 20px">๋กœ๊ทธ์ธ</button>
            </form>
            <div style="margin-top: 20px">
                <a href="/members/register">ํšŒ์›๊ฐ€์ž…</a>
            </div>
        </div>
    </body>
</html>

 

(1)์„ ๋ณด๋ฉด ${param.error} ์˜ ๊ฐ’์„ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์ธ์ฆ ์‹คํŒจ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๊ณ  ์žˆ๋‹ค.

${param.error} ๋Š” Spring Security Configuration์—์„œ failureUrl(”/auths/login-form?error”)์˜ ?error ๋ถ€๋ถ„์— ํ•ด๋‹นํ•˜๋Š” ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

  • (7)์˜ authorizeHttpRequests() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์ ‘๊ทผ ๊ถŒํ•œ์„ ํ™•์ธํ•˜๊ฒ ๋‹ค๊ณ  ์ •์˜ํ•œ๋‹ค.
  • (8), (9)์˜ anyRequest().permitAll() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์˜ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•œ๋‹ค.

 

๊ทธ๋Ÿผ ๋‹ค์Œ์œผ๋กœ request URI์— ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•ด๋ณด์ž.

 

 

์‚ฌ์šฉ์ž์—๊ฒŒ ๋ถ€์—ฌ๋œ Role์„ ์ด์šฉํ•ด์„œ ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ request URI์— ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•ด๋ณด๋„๋ก ํ•˜์ž.

package com.codestates.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        //HttpSecurity๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์— ๋Œ€ํ•œ ๋ณด์•ˆ ์„ค์ • ๊ตฌ์„ฑ
        http
                .csrf().disable()
                .formLogin()
                .loginPage("/auths/login-form")
                .loginProcessingUrl("/process_login")
                .failureUrl("/auths/login-form?error")
                .and()
                .exceptionHandling().accessDeniedPage("/auth/access-denied") //(1)
                .and()
                .authorizeHttpRequests(authorize -> authorize //(2)
                        .antMatchers("/orders/**").hasRole("ADMIN") //(2-1)
                        .antMatchers("/members/my-page").hasRole("USER") //(2-2)
                        .antMatchers("/**").permitAll() //(2-3)
                );

        return http.build();

    }

    @Bean
    public UserDetailsManager userDetailsService(){
        UserDetails userDetails =
                User.withDefaultPasswordEncoder()
                        .username("jeein@naver.com")
                        .password("1111")
                        .roles("USER")
                        .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

}

 

(1)์—์„œ๋Š” exceptionHandling().accessDeniedPage("auth/access-denied")๋ฅผ ํ†ตํ•ด ๊ถŒํ•œ์ด ์—†๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • request URI์— ์ ‘๊ทผํ•  ๊ฒฝ์šฐ, ๋ฐœ์ƒํ•˜๋Š” 403 ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํŽ˜์ด์ง€๋ฅผ ์„ค์ •ํ•จ.

 

exceptionHandling() ๋ฉ”์„œ๋“œ๋Š” ๋ฉ”์„œ๋“œ์˜ ์ด๋ฆ„ ๊ทธ๋Œ€๋กœ Exception์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ•˜๋ฉฐ, ๋ฆฌํ„ดํ•˜๋Š” ExceptionHandlingConfigurer  ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๊ตฌ์ฒด์ ์ธ Exception ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

accessDeniedPage() ๋ฉ”์„œ๋“œ๋Š” 403 ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ, ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ง€์ •ํ•œ URL๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋˜๋„๋ก ํ•ด์ค€๋‹ค.

 

authorizeHttpRequest() ๋ฉ”์„œ๋“œ๋Š” (2)์™€ ๊ฐ™์ด ๋žŒ๋”” ํ‘œํ˜„์‹์„ ํ†ตํ•ด request URI ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌ ๊ฐ€๋Šฅํ•˜๋‹ค.

antMatchers() ๋ฉ”์„œ๋“œ๋Š” ์ด๋ฆ„ ๊ทธ๋Œ€๋กœ ant ๋ผ๋Š” ๋นŒ๋“œ ํˆด์—์„œ ์‚ฌ์šฉ๋˜๋Š” Path Pattern์„ ์ด์šฉํ•˜์—ฌ ๋งค์น˜๋˜๋Š” URL์„ ํ‘œํ˜„ํ•œ๋‹ค.

(2-1)์˜ .antMatchers(”/orders/**”).hasRole(”ADMIN”)์€ ADMIN Role์„ ๋ถ€์—ฌ๋ฐ›์€ ์‚ฌ์šฉ์ž๋งŒ /orders๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  URL์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ.

/orders/**์—์„œ **์€ /orders๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  ํ•˜์œ„ URL์„ ํฌํ•จํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, /orders/1, /orders/1/coffees, /orders/1/coffee/1 ๊ณผ ๊ฐ™์€ ๋ชจ๋“  ํ•˜์œ„ URL์„ ํฌํ•จํ•œ๋‹ค.

(2-2)์˜ antMatchers("/members/my-page").hasRole("USER")์€ USER Role์„ ๋ถ€์—ฌ๋ฐ›์€ ์‚ฌ์šฉ์ž๋งŒ /members/my-page URL์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

(2-3)์˜ .anyMatchers("/**").permitAll()์€ ์•ž์—์„œ ์ง€์ •ํ•œ URL ์™ธ์˜ ๋‚˜๋จธ์ง€ ๋ชจ๋“  URL์€ Role ์ƒ๊ด€์—†์ด ์ ‘๊ทผ ๊ฐ€๋Šฅํ•จ์„ ๋‚˜ํƒ€๋ƒ„.

 

antMatchers()๋ฅผ ์ด์šฉํ•œ ์ ‘๊ทผ ๊ถŒํ•œ ๋ถ€์—ฌ ์‹œ์—๋Š” ๋‹ค์Œ์„ ์ฃผ์˜ํ•˜์—ฌ์•ผ ํ•œ๋‹ค.

.authorizeHttpRequests(authorize -> authorize
                    .antMatchers("⁄**").permitAll() // ์ด ํ‘œํ˜„์‹์ด ์ œ์ผ ์•ž์— ์˜ค๋ฉด?
                    .antMatchers("/orders/**").hasRole("ADMIN")
                    .antMatchers("/members/my-page").hasRole("USER")
            );

 

์ด์ฒ˜๋Ÿผ .antMatchers("/**").permitAll()์ด ๋จผ์ € ์˜ค๋ฉด Spring Security์—์„œ๋Š” Role์— ์ƒ๊ด€์—†์ด ๋ชจ๋“  request URL์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌด์— ๋‹ค์Œ์— ์˜ค๋Š” .antMatchers("/orders/**").hasRole("ADMIN")๊ณผ .antMatchers("/members/my-page").hasRole("USER")๋Š” ์ œ ๊ธฐ๋Šฅ์„ ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋˜๊ณ , ๊ฒฐ๊ณผ์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ Role๊ณผ๋Š” ๋ฌด๊ด€ํ•˜๊ฒŒ ๋ชจ๋“  request URL์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

 

ํ˜„์žฌ ์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•œ SecurityConfiguration ํด๋ž˜์Šค์—๋Š” USER Role์„ ๊ฐ€์ง€๋Š” ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž๋งŒ InMemory User๋กœ ๋“ฑ๋ก๋œ ์ƒํƒœ์ด๋‹ค.

์ด์ œ ADMIN Role์„ ๊ฐ€์ง€๋Š” ์‚ฌ์šฉ์ž๋ฅผ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•ด์„œ .antMatchers("/orders/**").hasRole("ADMIN")๋กœ ์„ค์ •ํ•œ ํ™”๋ฉด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธ์„ ํ•ด๋ณด๋„๋ก ํ•˜์ž.

 

	
    @Bean
    public UserDetailsManager userDetailsService(){
        UserDetails user =
                User.withDefaultPasswordEncoder()
                        .username("jeein@naver.com")
                        .password("1111")
                        .roles("USER")
                        .build();

        //(1)
        UserDetails admin =
                User.withDefaultPasswordEncoder()
                        .username("admin@naver.com")
                        .password("2222")
                        .roles("ADMIN")
                        .build();
        

        return new InMemoryUserDetailsManager(user,admin);
    }

(1)๊ณผ ๊ฐ™์ด admin@naver.com์ด๋ผ๋Š” InMemory User๋ฅผ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•˜์˜€์œผ๋ฉฐ, admin@naver.com ์—๊ฒŒ๋Š” ADMIN Role์ด ๋ถ€๊ฐ€๋˜์—ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ณ  ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ”์ธ ํ™”๋ฉด์—์„œ [์ „์ฒด ์ฃผ๋ฌธ ๋ชฉ๋ก ๋ณด๊ธฐ] ๋ฉ”๋‰ด๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ˜„์žฌ ํ™”๋ฉด์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•œ ํ›„์— ์–ด๋–ค ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค. ๋˜ํ•œ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์•„์›ƒ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ๋„ ์—†๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งˆ์ดํŽ˜์ด์ง€ ๋งํฌ ์—ญ์‹œ ๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ ๋ณด์ด๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <!-- (1) -->
    <body>
        <div align="right" th:fragment="header">
            <a href="/members/register" class="text-decoration-none">ํšŒ์›๊ฐ€์ž…</a> |
            <span sec:authorize="isAuthenticated()"> <!-- (2) -->
                <span sec:authorize="hasRole('USER')">  <!-- (3) -->
                    <a href="/members/my-page" class="text-decoration-none">๋งˆ์ดํŽ˜์ด์ง€</a> |
                </span>
                <a href="/logout" class="text-decoration-none">๋กœ๊ทธ์•„์›ƒ</a>  <!-- (4) -->
                <span th:text="${#authentication.name}">ํ™๊ธธ๋™</span>๋‹˜  <!-- (5) -->
            </span>
            
            <span sec:authorize="!isAuthenticated()"> <!-- (6) -->
                <a href="/auths/login-form" class="text-decoration-none">๋กœ๊ทธ์ธ</a>
            </span>
        </div>
    </body>
</html>

๋กœ๊ทธ์•„์›ƒ ๋ฐ ๊ถŒํ•œ๋ณ„๋กœ ๋ฉ”๋‰ด ํ‘œ์‹œ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜์ •ํ•œ header.html

ํƒ€์ž„๋ฆฌํ”„ ๊ธฐ๋ฐ˜์˜ HTML ํ…œํ”Œ๋ฆฟ์—์„œ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋‚˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ์–ด๋–ค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋จผ์ € (1)๊ณผ ๊ฐ™์ด sec ํƒœ๊ทธ๋ฅผ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ XML ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ง€์ •ํ•œ๋‹ค.

๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์•„๋„ ๋™์ž‘ํ•˜์ง€๋งŒ, IDE์—์„œ ๋นจ๊ฐ›๊ฒŒ ํ‘œ์‹œ๋œ๋‹ค.

 

(2)์™€ ๊ฐ™์ด ํƒœ๊ทธ ๋‚ด๋ถ€์—์„œ sec:authorize="isAuthenticated()"์„ ์ง€์ •ํ•˜๋ฉด ํ˜„์žฌ ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ์— ์„ฑ๊ณตํ•œ ์‚ฌ์šฉ์ž์ธ์ง€๋ฅผ ์ฒดํฌํ•œ๋‹ค. ์ฆ‰, isAuthenticated()์˜ ๊ฐ’์ด true์ด๋ฉด ํƒœ๊ทธ ํ•˜์œ„์— ํฌํ•จ๋œ ์ฝ˜ํ…์ธ ๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•œ๋‹ค.

 

๋งˆ์ดํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ, ADMIN Role์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋Š” ํ•„์š” ์—†๋Š” ๊ธฐ๋Šฅ์ด๋ฏ€๋กœ (3)๊ณผ ๊ฐ™์ด sec:authorize="hasRole('USER')"์„ ์ง€์ •ํ•ด์„œ User Role์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ ํ‘œ์‹œ๋˜๋„๋ก ํ•œ๋‹ค.

 

(2)์—์„œ isAuthenticated()์˜ ๊ฐ’์ด true๋ผ๋Š” ์˜๋ฏธ๋Š” ์ด๋ฏธ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ผ๋Š” ์˜๋ฏธ๋กœ [๋กœ๊ทธ์ธ] ๋ฉ”๋‰ด ๋Œ€์‹ ์— (4)์™€ ๊ฐ™์ด [๋กœ๊ทธ์•„์›ƒ] ๋ฉ”๋‰ด๋ฅผ ํ‘œ์‹œํ•œ๋‹ค. (4)์˜ href="/logout"์—์„œ "/logout" URL์€ SecurityConfiguration ํด๋ž˜์Šค์—์„œ ์„ค์ •ํ•œ ๊ฐ’๊ณผ ๊ฐ™์•„์•ผ ํ•œ๋‹ค.

 

(5)์—์„œ๋Š” th:text="${#authentication.name}"๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž์˜ username์„ ํ‘œ์‹œํ•˜๊ณ  ์žˆ๋‹ค. ์ด๊ณณ์—๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋กœ๊ทธ์ธํ•  ๋•Œ ์‚ฌ์š”ํ•œ username์ด ํ‘œ์‹œ๋œ๋‹ค.

 

(6)์—์„œ๋Š” sec:authorize-"!isAuthenticated()"๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด [๋กœ๊ทธ์ธ] ๋ฒ„ํŠผ์ด ํ‘œ์‹œ๋˜๋„๋ก ํ•œ๋‹ค.

 

*sec ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” build.gradle์˜ dependencies()์— ๋‹ค์Œ์˜ ์˜์กด๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5’

 

 

๋งˆ์ง€๋ง‰์œผ๋กœ Spring Security์—์„œ ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •์„ ํ•ด๋ณด์ž.

 

	
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        //HttpSecurity๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์— ๋Œ€ํ•œ ๋ณด์•ˆ ์„ค์ • ๊ตฌ์„ฑ
        http
                .csrf().disable()
                .formLogin()
                .loginPage("/auths/login-form")
                .loginProcessingUrl("/process_login")
                .failureUrl("/auths/login-form?error")
                .and()
                .logout() //(1)
                .logoutUrl("/logout") //(2) 
                .logoutSuccessUrl("/") //(3)
                .and()
                .exceptionHandling().accessDeniedPage("/auth/access-denied")
                .and()
                .authorizeHttpRequests(authorize -> authorize
                        .antMatchers("/orders/**").hasRole("ADMIN")
                        .antMatchers("/members/my-page").hasRole("USER")
                        .antMatchers("/**").permitAll()
                );

        return http.build();

    }

๋กœ๊ทธ์•„์›ƒ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์„ค์ •์„ ์œ„ํ•ด์„œ๋Š” (1)๊ณผ ๊ฐ™์ด logout()์„ ๋จผ์ € ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค. logout() ๋ฉ”์„œ๋“œ๋Š” ๋กœ๊ทธ์•„์›ƒ ์„ค์ •์„ ์œ„ํ•œ LogoutConfigurer ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

(2)์—์„œ๋Š” logoutUrl("/logout")์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์•„์›ƒ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ request URL์„ ์ง€์ •ํ•œ๋‹ค.

(3)์—์„œ๋Š” ๋กœ๊ทธ์•„์›ƒ์„ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•œ ์ดํ›„ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•  URL์„ ์ง€์ •ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ๋กœ๊ทธ์•„์›ƒ ์ดํ›„ ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ”์ธ ํ™”๋ฉด์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ•˜๋„๋ก ์ง€์ •ํ–ˆ๋‹ค.

 

 

 

์—ฌ๊ธฐ๊นŒ์ง€ InMemory User๋ฅผ ์ด์šฉํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹œ, ๋ฉ”๋ชจ๋ฆฌ์— ๋‘ ๊ฐœ์˜ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฏธ๋ฆฌ ๋“ฑ๋กํ•˜์—ฌ ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๋ณ„๋กœ request URL์˜ ์ ‘๊ทผ์ด ์ œํ•œ๋˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์‚ดํŽด๋ณด์•˜๋‹ค.

 

ํ•˜์ง€๋งŒ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๊ธฐ๋Šฅ์ด ์žˆ๋Š”๋ฐ, ๋ฐ”๋กœ ํšŒ์›๊ฐ€์ž…์ด๋‹ค.

์šฐ๋ฆฌ๋Š” ์•„์ง ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํšŒ์› ๊ฐ€์ž… ์š”์ฒญ์œผ๋กœ ์ „๋‹ฌ๋ฐ›์€ ํšŒ์› ์ •๋ณด๋ฅผ Spring Security๊ฐ€ ์•Œ ์ˆ˜ ์žˆ๋Š” ์–ด๋–ค ์ฒ˜๋ฆฌ๋„ ํ•˜์ง€ ์•Š์•˜๋‹ค.

 

ํšŒ์› ๊ฐ€์ž… ํผ์„ ํ†ตํ•œ InMemory User ๋ฅผ ๋“ฑ๋กํ•˜๊ธฐ ์œ„ํ•œ ์ž‘์—… ์ˆœ์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • PasswordEncoder Bean ๋“ฑ๋ก
  • MemberService Bean ๋“ฑ๋ก์„ ์œ„ํ•œ JavaConfiguration ์ž‘์„ฑ
  • InMemoryMemberService ํด๋ž˜์Šค ๊ตฌํ˜„

๋จผ์ € PasswordEncoder๋ฅผ Bean ๋“ฑ๋กํ•ด๋ณด์ž.

PasswordEncoder๋Š” Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” ํŒจ์Šค์›Œ๋“œ ์•”ํ˜ธํ™” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

์šฐ๋ฆฌ๊ฐ€ ํšŒ์› ๊ฐ€์ž… ํผ์„ ํ†ตํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ „๋‹ฌํ•˜๋Š” ํŒจ์Šค์›Œ๋“œ๋Š” ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์€ plain text์ด๋‹ค.

๋”ฐ๋ผ์„œ ํšŒ์› ๊ฐ€์ž… ํผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ํŒจ์Šค์›Œ๋“œ๋Š” InMemory User๋กœ ๋“ฑ๋ก๋˜๊ธฐ ์ „์— ์•”ํ˜ธํ™”๋˜์–ด์•ผ ํ•œ๋‹ค.

 

PasswordEncoder๋Š” ๋‹ค์–‘ํ•œ ์•”ํ˜ธํ™” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉฐ, Spring Security์—์„œ ์ง€์›ํ•˜๋Š” PasswordEncoder์˜ ๋””ํดํŠธ ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ bcrypt ์ด๋‹ค.

package com.codestates.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        //HttpSecurity๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์— ๋Œ€ํ•œ ๋ณด์•ˆ ์„ค์ • ๊ตฌ์„ฑ
        http
                .csrf().disable()
                .formLogin()
                .loginPage("/auths/login-form")
                .loginProcessingUrl("/process_login")
                .failureUrl("/auths/login-form?error")
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .and()
                .exceptionHandling().accessDeniedPage("/auth/access-denied")
                .and()
                .authorizeHttpRequests(authorize -> authorize
                        .antMatchers("/orders/**").hasRole("ADMIN")
                        .antMatchers("/members/my-page").hasRole("USER")
                        .antMatchers("/**").permitAll()
                );

        return http.build();

    }

    @Bean
    public UserDetailsManager userDetailsService(){
        UserDetails user =
                User.withDefaultPasswordEncoder()
                        .username("jeein@naver.com")
                        .password("1111")
                        .roles("USER")
                        .build();

        //(1)
        UserDetails admin =
                User.withDefaultPasswordEncoder()
                        .username("admin@naver.com")
                        .password("2222")
                        .roles("ADMIN")
                        .build();


        return new InMemoryUserDetailsManager(user,admin);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

}

SecurityConfiguration ํด๋ž˜์Šค์—์„œ PasswordEncoder๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ  ์žˆ๋‹ค.

PasswordEncoderFactories.createDelegatingPasswordEncoder(); ๋ฅผ ํ†ตํ•ด์„œ DelegatingPasswordEncoder๋ฅผ ๋จผ์ € ์ƒ์„ฑํ•˜๋Š”๋ฐ, ์ด DelegatingPasswordEncoder๊ฐ€ ์‹ค์งˆ์ ์œผ๋กœ PasswordEncoder ๊ตฌํ˜„ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์ค€๋‹ค.

 

์šฐ๋ฆฌ๊ฐ€ userDetailsService() ๋ฉ”์„œ๋“œ์—์„œ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•˜๋Š” InMemoryUser ํŒจ์Šค์›Œ๋“œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๋””ํดํŠธ PasswordEncoder๋ฅผ ํ†ตํ•ด ์•”ํ˜ธํ™”๋œ๋‹ค.

 

MemberServcie Bean ๋“ฑ๋ก์„ ์œ„ํ•œ JavaConfiguration ์ž‘์„ฑ

package com.codestates.member;

public interface MemberService {
    Member createMember(Member member);
}

Hello Spring Security ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํšŒ์› ๊ฐ€์ž… ํผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ธฐ๋Šฅ๋งŒ ์žˆ์œผ๋ฉด ๋˜๋ฏ€๋กœ createMember() ํ•˜๋‚˜๋งŒ ๊ตฌํ˜„ํ•˜๋Š” ๊ตฌํ˜„์ฒด๊ฐ€ ์žˆ์œผ๋ฉด ๋œ๋‹ค.

 

 

InMemory User ๋“ฑ๋ก์„ ์œ„ํ•œ InMemoryMemberService ํด๋ž˜์Šค

package com.codestates.member;

public class InMemoryMemberService implements MemberService {
    public Member createMember(Member member) {

        return null;
    }
}

 

 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— User๋ฅผ ๋“ฑ๋กํ•˜๊ธฐ ์œ„ํ•œ DBMemberService ํด๋ž˜์Šค

package com.codestates.member;

import org.springframework.transaction.annotation.Transactional;

@Transactional
public class DBMemberService implements MemberService {
    public Member createMember(Member member) {
         return null;
    }
}

 

 

JavaConfiguration ๊ตฌ์„ฑ

package com.codestates.config;

import com.codestates.member.InMemoryMemberService;
import com.codestates.member.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.UserDetailsManager;

@Configuration
public class JavaConfiguration {
    // (1)
    @Bean
    public MemberService inMemoryMemberService(UserDetailsManager userDetailsManager, 
                                               PasswordEncoder passwordEncoder) {
        return new InMemoryMemberService(userDetailsManager, passwordEncoder);
    }
}

JavaConfiguration ํด๋ž˜์Šค๋Š” MemberService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„ ํด๋ž˜์Šค์ธ InMemoryMemberService๋ฅผ Spring Bean์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.

 

(1)์—์„œ๋Š” MemberService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์ธ InMemoryMemberService ํด๋ž˜์Šค์˜ Bean ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

InMemoryMemberService ํด๋ž˜์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ ์—†์ด ๋ฉ”๋ชจ๋ฆฌ์— Spring Security์˜ User๋ฅผ ๋“ฑ๋กํ•ด์•ผ ํ•˜๋ฏ€๋กœ UserDetailsManager๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋˜ํ•œ User ๋“ฑ๋ก ์‹œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•œ ํ›„์— ๋“ฑ๋กํ•ด์•ผ ํ•˜๋ฏ€๋กœ Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” PasswordEncoder ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

 

์ด์ œ InMemoryMemberService๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž.

package com.codestates.member;

import com.codestates.auth.utils.AuthorityUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.UserDetailsManager;

import java.util.List;

public class InMemoryMemberService implements MemberService {  // (1)
    private final UserDetailsManager userDetailsManager;
    private final PasswordEncoder passwordEncoder;

    // (2)
    public InMemoryMemberService(UserDetailsManager userDetailsManager, PasswordEncoder passwordEncoder) {
        this.userDetailsManager = userDetailsManager;
        this.passwordEncoder = passwordEncoder;
    }

    public Member createMember(Member member) {
        // (3)
        List<GrantedAuthority> authorities = createAuthorities(Member.MemberRole.ROLE_USER.name());

        // (4)
        String encryptedPassword = passwordEncoder.encode(member.getPassword());

        // (5)
        UserDetails userDetails = new User(member.getEmail(), encryptedPassword, authorities);

        // (6)
        userDetailsManager.createUser(userDetails);

        return member;
    }

    private List<GrantedAuthority> createAuthorities(String... roles) {
        // (3-1)
        return Arrays.stream(roles)
                .map(role -> new SimpleGrantedAuthority(role))
                .collect(Collectors.toList());
    }
}

InMemoryMemberService ํด๋ž˜์Šค๋Š” MemberService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ตฌํ˜„ ํด๋ž˜์Šค์ด๋ฏ€๋กœ (1)๊ณผ ๊ฐ™์ด implements MemberService๋ฅผ ์ง€์ •ํ•œ๋‹ค.

 

(2)์—์„œ๋Š” UserDetailsManager์™€ PasswordEncoder๋ฅผ DI ๋ฐ›๋Š”๋‹ค.

  • UserDetailsManager๋Š” Spring Security์˜ User๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ SecurityConfiguration์—์„œ Bean์œผ๋กœ ๋“ฑ๋กํ•œ UserDetailsManager๋Š” InMemoryUserDetailsManager ์ด๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ DI ๋ฐ›์€ UserDetailsManager์˜ ํ•˜์œ„ ํƒ€์ž…์€ InMemoryUserDetailsManager์ด๋‹ค.
  • PasswordEncoder๋Š” Spring Secrutiy User๋ฅผ ๋“ฑ๋กํ•  ๋•Œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•ด์ฃผ๋Š” ํด๋ž˜์Šค์ด๋‹ค. Spring Security 5์—์„œ๋Š” InMemory User๋„ ํŒจ์Šค์›Œ๋“œ ์•”ํ˜ธํ™”๊ฐ€ ํ•„์ˆ˜์ด๋‹ค.

Spring Security์—์„œ User๋ฅผ ๋“ฑ๋กํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•ด๋‹น User์˜ ๊ถŒํ•œ(Authority)๋ฅผ ์ง€์ •ํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

๋”ฐ๋ผ์„œ createAuthorities(Member.MemberRole.ROLE_USER.name());์„ ์ด์šฉํ•ด User์˜ ๊ถŒํ•œ ๋ชฉ๋ก์„ List<GrantedAuthority>๋กœ ์ƒ์„ฑํ•˜๊ณ  ์žˆ๋‹ค.

Member ํด๋ž˜์Šค์—๋Š” MemberRole์ด๋ผ๊ณ  ํ•˜๋Š” enum์ด ์ •ํ•ด์ ธ ์žˆ๊ณ , ROLE_USER, ROLE_ADMIN ์ด๋ผ๋Š” enum ํƒ€์ž…์ด ์ •์˜๋˜์–ด ์—ˆ๋‹ค.

Spring Security์—์„œ๋Š” SimpleGrantedAuthority๋ฅผ ์‚ฌ์šฉํ•ด Role ๋ฒ ์ด์Šค ํ˜•ํƒœ์˜ ๊ถŒํ•œ์„ ์ง€์ •ํ•  ๋•Œ, 'ROLE_'+๊ถŒํ•œ๋ช… ํ˜•ํƒœ๋กœ ์ง€์ •ํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค. (๊ทธ๋ ‡์ง€ ์•Š์„ ๊ฒฝ์šฐ, ์ ์ ˆํ•œ ๊ถŒํ•œ ๋งคํ•‘์ด ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์Œ)

(3-1)์—์„œ๋Š” Java์˜ Stream API๋ฅผ ์‚ฌ์šฉํ•ด ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํ•ด๋‹น User์˜ Role์„ ์ „๋‹ฌํ•˜๋ฉด์„œ SimpleGrantedAuthority ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ ํ›„, List<SimpleGrantedAuthority> ํ˜•ํƒœ๋กœ ๋ฆฌํ„ดํ•ด ์ค€๋‹ค.

 

(4)์—์„œ๋Š” PasswordEncoder๋ฅผ ์ด์šฉํ•ด ๋“ฑ๋กํ•  User์˜ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•˜๊ณ  ์žˆ๋‹ค. ๋งŒ์•ฝ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•˜์ง€ ์•Š๊ณ  User๋ฅผ ๋“ฑ๋กํ•œ๋‹ค๋ฉด User ๋“ฑ๋ก์€ ๋˜์ง€๋งŒ, ๋กœ๊ทธ์ธ ์ธ์ฆ ์‹œ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋œ๋‹ค.

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null”

(5)์—์„œ๋Š” Spring Security User๋กœ ๋“ฑ๋กํ•˜๊ธฐ ์œ„ํ•ด UserDetails๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. (Spring Security์—์„œ๋Š” User ์ •๋ณด๋ฅผ UserDetails๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.)

 

์ด์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ณ  ํšŒ์› ๊ฐ€์ž… ๋ฉ”๋‰ด์—์„œ ํ™”์› ์ •๋ณด๋ฅผ ๋“ฑ๋กํ•œ ํ›„, ๋“ฑ๋กํ•œ ํšŒ์› ์ •๋ณด๋กœ ๋กœ๊ทธ์ธ์„ ์ˆ˜ํ–‰ํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ๋กœ๊ทธ์ธ์ด ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

์ง€๊ธˆ๊นŒ์ง€ ๋กœ๊ทธ์ธ ์ธ์ฆ ๋ฐฉ์‹์€ ์˜ค๋กœ์ง€ Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” InMemory User๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ธ์ฆ ๋ฐฉ์‹์ด๋‹ค.

 

InMemory User๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋ฉด ๋“ฑ๋กํ–ˆ๋˜ User ์ •๋ณด๊ฐ€ ๋‹ค ์‚ฌ๋ผ์ง€๋ฏ€๋กœ, ๊ฐ„๋‹จํ•œ Member ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•ด์„œ ํšŒ์› ์ธ์ฆ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ ํšŒ์› ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์—์„œ ๊ด€๋ฆฌํ•˜๋„๋ก ํ•ด๋ณด์ž.

 

Spring Security์—์„œ๋Š” User ์ธ์ฆ ์ •๋ณด๋ฅผ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜๊ณ , ํ…Œ์ด๋ธ”์— ์ €์žฅ๋œ ์ธ์ฆ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ์ธ์ฆ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, ๊ทธ ์ค‘ ํ•œ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ๋ฐ”๋กœ Custom UserDetailsService๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

 

*์ผ๋ฐ˜์ ์œผ๋กœ Spring Security์—์„œ๋Š” ์ธ์ฆ์„ ์‹œ๋„ํ•˜๋Š” ์ฃผ์ฒด๋ฅผ User(๋น„์Šทํ•œ ์˜๋ฏธ๋กœ Principal)๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. Principal์€ User์˜ ๊ตฌ์ฒด์ ์ธ ์ •๋ณด๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ Spring Security์—์„œ์˜ Username์„ ์˜๋ฏธํ•œ๋‹ค.  ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” Member ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๊ฐ€ ๋กœ๊ทธ์ธ ์ธ์ฆ ์ •๋ณด๋ฅผ ํฌํ•จํ•  ํ…๋ฐ ์ด Member ์—”ํ‹ฐํ‹ฐ๊ฐ€ Spring Security์˜ User ์ •๋ณด๋ฅผ ํฌํ•จํ•œ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

 

๋กœ๊ทธ์ธ ์ธ์ฆ์„ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์ธ์ฆ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ฏ€๋กœ, InMemory User๋ฅผ ์œ„ํ•œ ์„ค์ •๋“ค์€ ๋” ์ด์ƒ ํ•„์š”์—†์œผ๋ฏ€๋กœ ์ œ๊ฑฐํ•ด์•ผ ํ•œ๋‹ค.

 

package com.codestates.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        //HttpSecurity๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์— ๋Œ€ํ•œ ๋ณด์•ˆ ์„ค์ • ๊ตฌ์„ฑ
        http
                .headers().frameOptions().sameOrigin() //(1)
                .and()
                .csrf().disable()
                .formLogin()
                .loginPage("/auths/login-form")
                .loginProcessingUrl("/process_login")
                .failureUrl("/auths/login-form?error")
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .and()
                .exceptionHandling().accessDeniedPage("/auth/access-denied")
                .and()
                .authorizeHttpRequests(authorize -> authorize
                        .antMatchers("/orders/**").hasRole("ADMIN")
                        .antMatchers("/members/my-page").hasRole("USER")
                        .antMatchers("/**").permitAll()
                );

        return http.build();

    }

    //InMemory User๋ฅผ ์œ„ํ•œ ์„ค์ •์ด๋ฏ€๋กœ ์ œ๊ฑฐ ๋Œ€์ƒ...
//    @Bean
//    public UserDetailsManager userDetailsService(){
//        UserDetails user =
//                User.withDefaultPasswordEncoder()
//                        .username("jeein@naver.com")
//                        .password("1111")
//                        .roles("USER")
//                        .build();
//
//        //(1)
//        UserDetails admin =
//                User.withDefaultPasswordEncoder()
//                        .username("admin@naver.com")
//                        .password("2222")
//                        .roles("ADMIN")
//                        .build();
//
//
//        return new InMemoryUserDetailsManager(user,admin);
//    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

}

SecurityConfiguration ํด๋ž˜์Šค์—์„œ (1)์€ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ H2 ์›น ์ฝ˜์†”์„ ์ •์ƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •์ด๋‹ค.

frameOptions()๋Š” HTML ํƒœ๊ทธ ์ค‘์—์„œ <frame>์ด๋‚˜ <iframe>,<object> ํƒœ๊ทธ์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ• ์ง€์˜ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ•œ๋‹ค.

Spring Security์—์„œ๋Š” ํด๋ฆญ์žฌํ‚น ๊ณต๊ฒฉ์„ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ frameOptions() ๊ธฐ๋Šฅ์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์œผ๋ฉฐ ๋””ํดํŠธ ๊ฐ’์€ DENY์ด๋‹ค.

์ฆ‰, ์œ„์—์„œ ์–ธ๊ทผํ•œ HTML ํƒœ๊ทธ๋ฅผ ์ด์šฉํ•œ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๊ฒ ๋‹ค๋Š” ๋œป์ด๋ฉฐ, (1)๊ณผ ๊ฐ™์ด .frameOptions().sameOrigin()์„ ํ˜ธ์ถœํ•˜๋ฉด ๋™์ผ ์ถœ์ฒ˜๋กœ๋ถ€ํ„ฐ ๋“ค์–ด์˜ค๋Š” request๋งŒ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง์„ ํ—ˆ์šฉํ•œ๋‹ค.

H2 ์›น ์ฝ˜์†”์˜ ํ™”๋ฉด ์ž์ฒด๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” H2 ์›น ์ฝ˜์†”์„ ์ •์ƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก (1)๊ณผ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.

 

(2)์˜ userDetailsService() ๋ฉ”์„œ๋“œ๋Š” InMemory User๋ฆ‰ ๋“ฑ๋กํ•˜๋Š” ์—ญํ• ์„ ํ•˜์ง€๋งŒ, ์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ User๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ User์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ฏ€๋กœ ํ•„์š”๊ฐ€ ์—†๋‹ค.

 

 

JavaConfiguration์˜ Bean ๋“ฑ๋ก ๋ณ€๊ฒฝ

@Configuration
public class JavaConfiguration {
    // (1)
    @Bean
    public MemberService dbMemberService(MemberRepository memberRepository,
                                         PasswordEncoder passwordEncoder) {
        return new DBMemberService(memberRepository, passwordEncoder); (1-1)
    }
}

 

 

DBMemberService๋Š” User ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋Š”๋ฐ, Spring Security ์ž…์žฅ์—์„œ User๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ์ •๋ณด๋Š” ์šฐ๋ฆญ ํšŒ์› ๊ฐ€์ž… ์‹œ ๋“ฑ๋กํ•˜๋Š” ํšŒ์› ์ •๋ณด ์•ˆ์— ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๊ณ  ๋ณด๋ฉด๋œ๋‹ค.

 

package com.codestates.member;

import com.codestates.exception.BusinessLogicException;
import com.codestates.exception.ExceptionCode;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Transactional
public class DBMemberService implements MemberService {

    private final MemberRepository memberRepository;

    private final PasswordEncoder passwordEncoder;

    public DBMemberService(MemberRepository memberRepository, PasswordEncoder passwordEncoder) {
        this.memberRepository = memberRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public Member createMember(Member member) {
        verifyExistsEmail(member.getEmail());

        String encryptedPassword = passwordEncoder.encode(member.getPassword());
        member.setPassword(encryptedPassword);

        Member savedMember = memberRepository.save(member);

        System.out.println("# Create Member in DB");
        return savedMember;
    }

    @Override
    public Member findMember(String email){
        return null;
    }

    private void verifyExistsEmail(String email){
        Optional<Member> member = memberRepository.findByEmail(email);
        if(member.isPresent())
            throw new BusinessLogicException(ExceptionCode.MEMBER_EXISTS);
    }

}

(1)์˜ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด MemberRepository์™€ PasswordEncoder Bean ๊ฐ์ฒด๋ฅผ DI ๋ฐ›๋Š”๋‹ค.

(2)์—์„œ PasswordEncoder๋ฅผ ์ด์šฉํ•ด ํŒจ์Šค์›Œ๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•œ๋‹ค.

(3)์—์„œ ์•”ํ˜ธํ™”๋œ ํŒจ์Šค์›Œ๋“œ๋ฅผ password ํ•„๋“œ์— ๋‹ค์‹œ ํ• ๋‹นํ•œ๋‹ค.

 

๋‹ค์Œ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ User ์ธ์ฆ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” Custom UserDetailsService๋ฅผ ๊ตฌํ˜„ํ•ด ๋ณด์ž.

 

*UserDetailsService๋Š” Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ์ค‘ ํ•˜๋‚˜๋กœ User ์ •๋ณด๋ฅผ ๋กœ๋“œํ•˜๋Š” ํ•ต์‹ฌ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. ์—ฌ๊ธฐ์„œ '๋กœ๋“œ'์˜ ์˜๋ฏธ๋Š” ์ธ์ฆ์— ํ•„์š”ํ•œ User ์ •๋ณด๋ฅผ ์–ด๋”˜๊ฐ€์—์„œ ๊ฐ€์ ธ์˜จ๋‹ค๋Š” ์˜๋ฏธ์ด๋ฉฐ, ์—ฌ๊ธฐ์„œ ๋งํ•˜๋Š” '์–ด๋”˜๊ฐ€'๋Š” ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋  ์ˆ˜๋„ ์žˆ๊ณ , DB ๋“ฑ์˜ ์˜๊ตฌ ์ €์žฅ์†Œ๊ฐ€ ๋  ์ˆ˜๋„ ์žˆ๋‹ค.

์šฐ๋ฆฌ๊ฐ€ InMemory User๋ฅผ ๋“ฑ๋กํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ–ˆ๋˜ InMemoryUserDetailsManager๋Š” UserDetailsManager ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์ด๊ณ , UserDetailsManager๋Š” UserDetailsService๋ฅผ ์ƒ์†ํ•˜๋Š” ํ™•์žฅ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.

 

package com.codestates.auth;

import com.codestates.auth.utils.HelloAuthorityUtils;
import com.codestates.exception.BusinessLogicException;
import com.codestates.exception.ExceptionCode;
import com.codestates.member.Member;
import com.codestates.member.MemberRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Optional;

@Component
public class HelloUserDetailsServiceV1 implements UserDetailsService { //(1)

    private final MemberRepository memberRepository;

    private final HelloAuthorityUtils authorityUtils;

    //(2)
    public HelloUserDetailsServiceV1(MemberRepository memberRepository, HelloAuthorityUtils authorityUtils) {
        this.memberRepository = memberRepository;
        this.authorityUtils = authorityUtils;
    }

    //(3)
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Member> optionalMember = memberRepository.findByEmail(username);
        Member findMember = optionalMember.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));

        //(4)
        Collection<? extends GrantedAuthority> authorities = authorityUtils.createAuthorities(findMember.getEmail());

        //(5)
        return new User(findMember.getEmail(), findMember.getPassword(), authorities);

    }
}

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ ์ธ์ฆ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” Custom UserDetailsService์ธ HelloUserDetailsService์˜ ํด๋ž˜์Šค ์ฝ”๋“œ์ด๋‹ค,

HelloUserDetailsService์™€ ๊ฐ™์€ Custom UserDetailsService๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” (1)๊ณผ ๊ฐ™์ด UserDetailsService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

 

HelloUserDetailsService๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ User๋ฅผ ์กฐํšŒํ•˜๊ณ , ์กฐํšŒํ•œ User์˜ ๊ถŒํ•œ(Role) ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด (2)์™€ ๊ฐ™์ด MemberRepository์™€ HelloAuthorityUtils ํด๋ž˜์Šค๋ฅผ DI ๋ฐ›๋Š”๋‹ค.

 

UserDetailsService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ implements ํ•˜๋Š” ๊ตฌํ˜„ ํด๋ž˜์Šค๋Š” (3)๊ณผ ๊ฐ™์ด loadUserByUserName(String username)์ด๋ผ๋Š” ์ถ”์ƒ ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

 

(4)์—์„œ๋Š” HelloAuthorityUtils๋ฅผ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ ํšŒ์›์˜ ์ด๋ฉ”์ผ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด Role ๊ธฐ๋ฐ˜์˜ ๊ถŒํ•œ ์ •๋ณด(GrantedAuthority) ์ปฌ๋ ‰์…˜์„ ์ƒ์„ฑํ•œ๋‹ค. 

 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ ์ธ์ฆ ์ •๋ณด์™€ (4)์—์„œ ์ƒ์„ฑํ•œ ๊ถŒํ•œ ์ •๋ณด๋ฅผ  Spring Security๊ฐ€ ์•Œ์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— Spring Security์— ์ด ์ •๋ณด๋“ค์„ ์ œ๊ณตํ•ด ์ฃผ์–ด์•ผ ํ•˜๋ฉฐ, (5)์—์„œ๋Š” UserDetails ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์ธ User ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค.

 

(5)์™€ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ User ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ดํ•˜๋ฉด Spring Security๊ฐ€ ์ด ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ์ธ์ฆ ์ ˆ์ฐจ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

์ฆ‰, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ User ์ธ์ฆ ์ •๋ณด๋งŒ Spring Security์—๊ฒŒ ๋„˜๊ฒจ์ฃผ๊ณ , ์ธ์ฆ ์ฒ˜๋ฆฌ๋Š” Spring Security๊ฐ€ ๋Œ€์‹  ํ•ด์ค€๋‹ค.

 

UserDetails๋Š” UserDetailsService์— ์˜ํ•ด ๋กœ๋“œ๋˜์–ด ์ธ์ฆ์„ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ํ•ต์‹ฌ User ์ •๋ณด๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. UserDetails ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด๋Š” Spring Security์—์„œ ๋ณด์•ˆ ์ •๋ณด ์ œ๊ณต์„ ๋ชฉ์ ์œผ๋กœ ์ง์ ‘ ์‚ฌ์šฉ๋˜์ง€๋Š” ์•Š๊ณ , Authentication ๊ฐ์ฒด๋กœ ์บก์Аํ™”๋˜์–ด ์ œ๊ณต๋œ๋‹ค.

 

HelloAuthorityUtils์˜ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

package com.codestates.auth.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
public class HelloAuthorityUtils {
    // (1)
    @Value("${mail.address.admin}")
    private String adminMailAddress;

    // (2)
    private final List<GrantedAuthority> ADMIN_ROLES = AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_USER");

    // (3)
    private final List<GrantedAuthority> USER_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER");
    
    public List<GrantedAuthority> createAuthorities(String email) {
        // (4)
        if (email.equals(adminMailAddress)) {
            return ADMIN_ROLES;
        }
        return USER_ROLES;
    }
}

HelloUserDetailsService์—์„œ Role ๊ธฐ๋ฐ˜์˜ User ๊ถŒํ•œ์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ HelloAuthorityUtils ์ฝ”๋“œ์ด๋‹ค.

(1)์€ application.yml์—์„œ ์ถ”๊ฐ€ํ•œ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ‘œํ˜„์‹์ด๊ณ , (1)๊ณผ ๊ฐ™์ด @Value("${ํ”„๋กœํผํ‹ฐ ๊ฒฝ๋กœ}") ํ‘œํ˜„์‹ ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•˜๋ฉด application.yml ์— ์ •์˜๋˜์–ด ์žˆ๋Š” ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์„ ํด๋ž˜์Šค ๋‚ด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

(1)์—์„œ๋Š” applicaiton.yml ์— ๋ฏธ๋ฆฌ ์ •์˜ํ•œ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ๋‹ค.

application.yml ํŒŒ์ผ์— ์ •์˜ํ•œ ๊ด€๋ฆฌ์ž์šฉ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋Š” ํšŒ์› ๋“ฑ๋ก ์‹œ, ํŠน์ • ์ด๋ฉ”์ผ ์ฃผ์†Œ์— ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.

 

application.yml ํŒŒ์ผ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ด€๋ฆฌ์ž ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.

mail:
  address:
    admin: admin@naver.com

(2)์—์„œ๋Š” Spring Security์—์„œ ์ง€์›ํ•˜๋Š” AuthorityUtils ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ด€๋ฆฌ์ž์šฉ ๊ถŒํ•œ ๋ชฉ๋ก์„ List<GrantedAuthority> ๊ฐ์ฒด๋กœ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•œ๋‹ค.

๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ผ ๊ฒฝ์šฐ, ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ๊นŒ์ง€ ์ถ”๊ฐ€๋กœ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.

 

(3)์—์„œ๋Š” Spring Security์—์„œ ์ง€์›ํ•˜๋Š” AuthorityUtils ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ ์ผ๋ฐ˜ ์‚ฌ์šฉ ๊ถŒํ•œ ๋ชฉ๋ก์„ List<GrantedAuthority> ๊ฐ์ฒด๋กœ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•œ๋‹ค.

 

(4)์—์„œ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›์€ ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ application.yml ํŒŒ์ผ์—์„œ ๊ฐ€์ ธ์˜จ ๊ด€๋ฆฌ์ž์šฉ ์ด๋ฉ”์ผ ์ฃผ์†Œ์™€ ๋™์ผํ•˜๋‹ค๋ฉด ๊ด€๋ฆฌ์ž์šฉ ๊ถŒํ•œ์ธ List<GrantedAuthority> ADMIN_ROLES๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

 

์‹ค๋ฌด์—์„œ๋Š” ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ์ธ์ฆ ์ ˆ์ฐจ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

 

์กฐ๊ธˆ ๋” ์œ ์—ฐํ•˜๊ณ  ๊น”๋”ํ•œ ์ฝ”๋“œ์˜ ๊ตฌ์„ฑ์„ ์œ„ํ•ด ์•ž์—์„œ ์ž‘์„ฑํ•œ HelloUserDetailsService ํด๋ž˜์Šค๋ฅผ ์‚ด์ง ๊ฐœ์„ ํ•ด๋ณด์ž.

package com.codestates.auth;

import com.codestates.auth.utils.HelloAuthorityUtils;
import com.codestates.exception.BusinessLogicException;
import com.codestates.exception.ExceptionCode;
import com.codestates.member.Member;
import com.codestates.member.MemberRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Optional;

@Component
public class HelloUserDetailsServiceV2 implements UserDetailsService { 

    private final MemberRepository memberRepository;

    private final HelloAuthorityUtils authorityUtils;

    
    public HelloUserDetailsServiceV2(MemberRepository memberRepository, HelloAuthorityUtils authorityUtils) {
        this.memberRepository = memberRepository;
        this.authorityUtils = authorityUtils;
    }

    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Member> optionalMember = memberRepository.findByEmail(username);
        Member findMember = optionalMember.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));

        //(1) ๊ฐœ์…˜๋œ ๋ถ€๋ถ„
        return new HelloUserDetails(findMember);
    }
    
    //(2) HelloUserDetails ํด๋ž˜์Šค ์ถ”๊ฐ€
    private final class HelloUserDetails extends Member implements UserDetails{ //(2-1)
        
        //(2-2)
        HelloUserDetails(Member member){
            setMemberId(member.getMemberId());
            setFullName(member.getFullName());
            setEmail(member.getEmail());
            setPassword(member.getPassword());
        }
        

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorityUtils.createAuthorities(this.getEmail()); //(2-3) ๋ฆฌํŒฉํ† ๋ง ํฌ์ธํŠธ
        }

        // (2-4)
        @Override
        public String getUsername() {
            return null;
        }

        @Override
        public boolean isAccountNonExpired() {
            return false;
        }

        @Override
        public boolean isAccountNonLocked() {
            return false;
        }

        @Override
        public boolean isCredentialsNonExpired() {
            return false;
        }

        @Override
        public boolean isEnabled() {
            return false;
        }
    }
}

๊ธฐ์กด์—๋Š” loadUserByUsername() ๋ฉ”์„œ๋“œ์˜ ๋ฆฌํ„ด๊ฐ’์œผ๋กœ new User(findMember.getEmail(), findMember.getPassword(), authorities);๋ฅผ ๋ฆฌํ„ดํ–ˆ์ง€๋งŒ ๊ฐœ์„ ๋œ ์ฝ”๋“œ์—์„œ๋Š” (1)๊ณผ ๊ฐ™์ด new HelloUserDetails(findMember); ๋ผ๋Š” Custom UserDetails ํด๋ž˜์Šค์˜ ์ƒ์„ฑ์ž๋กœ findMember๋ฅผ ์ „๋‹ฌํ•˜๋ฉด์„œ ์ฝ”๋“œ๊ฐ€ ์กฐ๊ธˆ ๋” ๊น”๋”ํ•ด์กŒ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์กด์˜ loadUserByUsername() ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ User ๊ถŒํ•œ ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•˜๋Š” Collection<? extends GrantedAuthority> authorities = authorityUtils.createAuthorities(findMember); ์ฝ”๋“œ๊ฐ€ ์‚ฌ๋ผ์กŒ๋‹ค. ์ด ์ฝ”๋“œ๋Š” (2)์—์„œ ์ •์˜ํ•œ HelloUserDetails ํด๋ž˜์Šค ๋‚ด๋ถ€๋กœ ํฌํ•จ๋˜์—ˆ๋‹ค.

 

(2)์˜ HelloUserDetails ํด๋ž˜์Šค๋Š” UserDetails ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๊ณ , ๋˜ํ•œ Member ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๊ณ  ์žˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ ํšŒ์› ์ •๋ณด๋ฅผ Spring Security์˜ User ์ •๋ณด๋กค ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •๊ณผ User์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ณผ์ •์„ ์บก์Аํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋˜ํ•œ, HelloUserDetails ํด๋ž˜์Šค๋Š” Member ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ HelloUserDetails๋ฅผ ๋ฆฌํ„ด๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋Š” ์ธก์—์„œ๋Š” ๋‘ ๊ฐœ์˜ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋ฅผ ๋‹ค ์†์‰ฝ๊ฒŒ ์บ์ŠคํŒ…ํ•ด์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.

 

(2-3)์—์„œ๋Š” HelloAuthorityUtils์˜ createAuthorities()๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด User์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ๋‹ค.

์ด ์ฝ”๋“œ๋Š” ๊ธฐ์กด์—์„œ loadUserByUsername() ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์— ์žˆ์—ˆ์ง€๋งŒ ์ง€๊ธˆ์€ HelloUserDetails ํด๋ž˜์Šค ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉ๋˜๋„๋ก ์บก์Аํ™”๋˜์—ˆ๋‹ค.

 

(2-4)์—์„œ๋Š” Spring Security์—์„œ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋Š” username์„ Member ํด๋ž˜์Šค์˜ email ์ฃผ์†Œ๋กœ ์ฑ„์šฐ๊ณ  ์žˆ๋‹ค. getUsername()์˜ ๋ฆฌํ„ด๊ฐ’์€ null์ผ ์ˆ˜ ์—†๋‹ค. User์˜ ์‹๋ณ„์ž๋Š” ๋ฌด์กฐ๊ฑด ์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ!

 

์ง€๊ธˆ๊นŒ์ง€์˜ ์ฝ”๋“œ๋Š” User์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์กฐํšŒํ•œ User ์ •๋ณด๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ฝ”๋“œ ์ƒ์—์„œ ์กฐ๊ฑด์— ๋งž๊ฒŒ ์ƒ์„ฑํ•˜๊ณ  ์žˆ๋‹ค.

User์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ DB์—์„œ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • User์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ํ…Œ์ด๋ธ” ์ƒ์„ฑ
  • ํšŒ์› ๊ฐ€์ž… ์‹œ, User์˜ ๊ถŒํ•œ ์ •๋ณด(Role)๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๋Š” ์ž‘์—…
  • ๋กœ๊ทธ์ธ ์ธ์ฆ ์‹œ, User์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•˜๋Š” ์ž‘์—…

์šฐ์„ , User์˜ ๊ถŒํ•œ ์ •๋ณด ํ…Œ์ด๋ธ”๋ถ€ํ„ฐ ์ƒ์„ฑํ•ด๋ณด์ž.

 

 

User์˜ ๊ถŒํ•œ ์ •๋ณด ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜๊ธฐ ์ „์— User์™€ User์˜ ๊ถŒํ•œ ์ •๋ณด ๊ฐ„์— ๊ด€๊ณ„๋ฅผ ๋จผ์ € ์ƒ๊ฐํ•ด๋ณด์ž.

package com.codestates.member;

import com.codestates.audit.Auditable;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@NoArgsConstructor
@Getter
@Setter
@Entity
public class Member extends Auditable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long memberId;

    @Column(length = 100, nullable = false)
    private String fullName;

    @Column(nullable = false, updatable = false, unique = true)
    private String email;

    @Column(length = 100, nullable = false)
    private String password;

    @Enumerated(value = EnumType.STRING)
    @Column(length = 20, nullable = false)
    private MemberStatus memberStatus = MemberStatus.MEMBER_ACTIVE;

    //(1) User์˜ ๊ถŒํ•œ ์ •๋ณด ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜๋Š” ์ •๋ณด
    @ElementCollection(fetch = FetchType.EAGER)
    private List<String> roles = new ArrayList<>();

    public Member(String email) {
        this.email = email;
    }

    public Member(String email, String fullName, String password) {
        this.email = email;
        this.fullName = fullName;
        this.password = password;
    }

    public enum MemberStatus {
        MEMBER_ACTIVE("ํ™œ๋™์ค‘"),
        MEMBER_SLEEP("ํœด๋ฉด ์ƒํƒœ"),
        MEMBER_QUIT("ํƒˆํ‡ด ์ƒํƒœ");

        @Getter
        private String status;

        MemberStatus(String status) {
           this.status = status;
        }
    }

    public enum MemberRole {
        ROLE_USER,
        ROLE_ADMIN
    }
}

Member ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์™€ User ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋งคํ•‘ํ•˜๋Š” ๊ฒƒ์€ (1)๊ณผ ๊ฐ™์ด ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

(1)๊ณผ ๊ฐ™์ด List, Set๊ณผ ๊ฐ™์€ ์ปฌ๋ ‰์…˜ ํƒ€์ž…์˜ ํ•„๋“œ๋Š” @ElementCollection ์• ๋„ˆํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ User ๊ถŒํ•œ ์ •๋ณด์™€ ๊ด€๋ จ๋œ ๋ณ„๋„์˜ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์•„๋„ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งคํ•‘ ์ฒ˜๋ฆฌ๊ฐ€ ๋œ๋‹ค.

์ฆ‰, ์ด ์ƒํƒœ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋œ๋‹ค.

 

 

Member ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์™€ ์—ฐ๊ด€ ๊ด€๊ณ„ ๋งคํ•‘์— ๋Œ€ํ•œ ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋œ๋‹ค.

ํ•œ ๋ช…์˜ ํšŒ์›์ด ํ•œ ๊ฐœ ์ด์ƒ์˜ Role์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, MEMBER ํ…Œ์ด๋ธ”๊ณผ MEMBER_ROLES ํ…Œ์ด๋ธ”์€ 1 ๋Œ€ N ๊ด€๊ณ„์ด๋‹ค.

 

ํšŒ์› ๊ฐ€์ž…์„ ํ†ตํ•ด ํšŒ์› ์ •๋ณด๊ฐ€ MEMBER ํ…Œ์ด๋ธ”์— ์ €์žฅ๋  ๋•Œ, MEMBER_ROLES ํ…Œ์ด๋ธ”์˜ MEMBER_MEMBER_ID ์—ด์—๋Š” MEMBER ํ…Œ์ด๋ธ”์˜ ๊ธฐ๋ณธํ‚ค ๊ฐ’์ด ๊ทธ๋ฆฌ๊ณ  ROLES ์—ด์—๋Š” ๊ถŒํ•œ ์ •๋ณด๊ฐ€ ์ €์žฅ๋œ๋‹ค.

 

ํšŒ์› ๊ฐ€์ž… ์‹œ User์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ด๋ณด์ž.

package com.codestates.member;

import com.codestates.auth.utils.HelloAuthorityUtils;
import com.codestates.exception.BusinessLogicException;
import com.codestates.exception.ExceptionCode;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Transactional
public class DBMemberService implements MemberService {
    ...
    ...
  
    private final HelloAuthorityUtils authorityUtils;

    ...
    ...

    public Member createMember(Member member) {
        verifyExistsEmail(member.getEmail());
        String encryptedPassword = passwordEncoder.encode(member.getPassword());
        member.setPassword(encryptedPassword);

        // (1) Role์„ DB์— ์ €์žฅ
        List<String> roles = authorityUtils.createRoles(member.getEmail());
        member.setRoles(roles);

        Member savedMember = memberRepository.save(member);

        return savedMember;
    }

    ...
    ...
}

DBMemberService์—์„œ ํšŒ์› ๋“ฑ๋ก ์‹œ, ํšŒ์›์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋œ๋‹ค.

(1)์—์„œ๋Š” authorityUtils.createRoles(member.getEmail());๋ฅผ ํ†ตํ•ด ํšŒ์›์˜ ๊ถŒํ•œ ์ •๋ณด(List<String> roles)๋ฅผ ์ƒ์„ฑํ•œ ๋’ค member ๊ฐ์ฒด์—๊ฒŒ ๋„˜๊ฒจ์ฃผ๊ณ  ์žˆ๋‹ค.

 

์•„๋ž˜์˜ ์ฝ”๋“œ๋Š” createRoles() ๋ฉ”์„œ๋“œ๊ฐ€ ์ถ”๊ฐ€๋œ HelloAuthorityUtils ํด๋ž˜์Šค์˜ ์ฝ”๋“œ์ด๋‹ค.

package com.codestates.auth.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.stream.Collectors;

@Component
public class HelloAuthorityUtils {
    @Value("${mail.address.admin}")
    private String adminMailAddress;

    ...
    ...

    private final List<String> ADMIN_ROLES_STRING = List.of("ADMIN", "USER");
    private final List<String> USER_ROLES_STRING = List.of("USER");

    ...
    ...

    // (1) DB ์ €์žฅ์šฉ
    public List<String> createRoles(String email) {
        if (email.equals(adminMailAddress)) {
            return ADMIN_ROLES_STRING;
        }
        return USER_ROLES_STRING;
    }
}

(1)์—์„œ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ application.yml ํŒŒ์ผ์˜ mail.address.admin ํ”„๋กœํผํ‹ฐ์— ์ •์˜๋œ ์ด๋ฉ”์ผ ์ฃผ์†Œ์™€ ๋™์ผํ•˜๋ฉด ๊ด€๋ฆฌ์ž Role ๋ชฉ๋ก(ADMIN_ROLES_STRING)์„ ๋ฆฌํ„ดํ•˜๊ณ , ๊ทธ ์™ธ์—๋Š” ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž Role ๋ชฉ๋ก(USER_ROLES_STRING)์„ ๋ฆฌํ„ดํ•œ๋‹ค.

 

๋กœ๊ทธ์ธ ์ธ์ฆ ์‹œ, User์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

package com.codestates.auth;

import com.codestates.auth.utils.HelloAuthorityUtils;
import com.codestates.exception.BusinessLogicException;
import com.codestates.exception.ExceptionCode;
import com.codestates.member.Member;
import com.codestates.member.MemberRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Optional;

@Component
public class HelloUserDetailsServiceV3 implements UserDetailsService {
    private final MemberRepository memberRepository;
    private final HelloAuthorityUtils authorityUtils;

    public HelloUserDetailsServiceV3(MemberRepository memberRepository, HelloAuthorityUtils authorityUtils) {
        this.memberRepository = memberRepository;
        this.authorityUtils = authorityUtils;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Member> optionalMember = memberRepository.findByEmail(username);
        Member findMember = optionalMember.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));

        return new HelloUserDetails(findMember);
    }

    private final class HelloUserDetails extends Member implements UserDetails {
        HelloUserDetails(Member member) {
            setMemberId(member.getMemberId());
            setFullName(member.getFullName());
            setEmail(member.getEmail());
            setPassword(member.getPassword());
            setRoles(member.getRoles());        // (1)
        }

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            // (2) DB์— ์ €์žฅ๋œ Role ์ •๋ณด๋กœ User ๊ถŒํ•œ ๋ชฉ๋ก ์ƒ์„ฑ
            return authorityUtils.createAuthorities(this.getRoles());
        }

        ...
        ...
    }

}

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ MEMBER_ROLES ํ…Œ์ด๋ธ”์—์„œ ์กฐํšŒํ•œ Role์„ ๊ธฐ๋ฐ˜์œผ๋กœ User ๊ถŒํ•œ ๋ชฉ๋ก(List<GrantedAuthority>)์„ ์ƒ์„ฑํ•˜๋Š” ๋กœ์ง์ด ์ถ”๊ฐ€๋œ HelloUserDetailsService ํด๋ž˜์Šค.

 

๋จผ์ € (1)์—์„œ๋Š” HelloUserDetails๊ฐ€ ์ƒ์†ํ•˜๊ณ  ์žˆ๋Š” Member(extends Member)์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ List<String> roles๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  (2)์—์„œ ๋‹ค์‹œ Member(extends Member)์— ์ „๋‹ฌํ•œ Role ์ •๋ณด๋ฅผ authorityUtils.createAuthorities() ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•ด์„œ ๊ถŒํ•œ ๋ชฉ๋ก(List<GrantedAuthority>์„ ์ƒ์„ฑํ•œ๋‹ค.

 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ Role ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค์ง€ ์•Š์•˜์„ ๋•Œ์—๋Š” authorityUtils.createAuthorities(this.getEmail()); ์ด์—ˆ์Œ.

 

์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ Role ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ User ๊ถŒํ•œ ๋ชฉ๋ก์„ ์ƒ์„ฑํ•˜๋Š” createAuthorities(List<String> roles) ๋ฉ”์„œ๋“œ๊ฐ€ ์ถ”๊ฐ€๋œ HelloAuthorityUtils๋ฅผ ๋ณด์ž

package com.codestates.auth.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.stream.Collectors;

@Component
public class HelloAuthorityUtils {
    @Value("${mail.address.admin}")
    private String adminMailAddress;

    private final List<GrantedAuthority> ADMIN_ROLES = AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_USER");
    private final List<GrantedAuthority> USER_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER");
    private final List<String> ADMIN_ROLES_STRING = List.of("ADMIN", "USER");
    private final List<String> USER_ROLES_STRING = List.of("USER");

    // ๋ฉ”๋ชจ๋ฆฌ ์ƒ์˜ Role์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ถŒํ•œ ์ •๋ณด ์ƒ์„ฑ.
    public List<GrantedAuthority> createAuthorities(String email) {
        if (email.equals(adminMailAddress)) {
            return ADMIN_ROLES;
        }
        return USER_ROLES;
    }

    // (1) DB์— ์ €์žฅ๋œ Role์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ถŒํ•œ ์ •๋ณด ์ƒ์„ฑ
    public List<GrantedAuthority> createAuthorities(List<String> roles) {
       List<GrantedAuthority> authorities = roles.stream()
               .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) // (2)
               .collect(Collectors.toList());
       return authorities;
    }

    ...
    ...
}

(1)์„ ๋ณด๋ฉด ๊ธฐ์กด์—๋Š” application.yml ํŒŒ์ผ์˜ mail.address.admin ํ”„๋กœํผํ‹ฐ์— ์ •์˜๋œ ๊ด€๋ฆฌ์ž ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ด€๋ฆฌ์ž Role์„ ์ถ”๊ฐ€ํ•˜์˜€์œผ๋‚˜ ์ด์ œ๋Š” ๊ทธ๋Ÿด ํ•„์š”๊ฐ€ ์—†๋‹ค.

๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ง€๊ณ  ์˜จ Role ๋ชฉ๋ก(List<String> roles)์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ด์„œ ๊ถŒํ•œ ๋ชฉ๋ก์„ ๋งŒ๋“ค๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์—

์ฃผ์˜ํ•ด์•ผ ํ•  ๊ฒƒ์€ (2)์™€ ๊ฐ™์ด SimpleGrantedAuthority ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฐ’์ด "USER" ๋˜๋Š” "ADMIN"์œผ๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด ์•ˆ๋˜๊ณ  "ROLE_USER", "ROLE_ADMIN" ํ˜•ํƒœ๋กœ ๋„˜๊ฒจ์ฃผ์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ.

 

Custom AuthenticationProvider๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

์•ž์—์„œ Custom UserDetailsService๋ฅผ ์ด์šฉํ•ด ๋กœ๊ทธ์ธ ์ธ์ฆ์„ ํ•˜๋Š” ๋ฐฉ์‹์€ Spring Security๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์ธ์ฆ์„ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•ด ์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค.

 

์ด๋ฒˆ์—๋Š” Custom AuthenticationProvider๋ฅผ ์ด์šฉํ•ด ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ๋กœ๊ทธ์ธ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด์ž.

Custom AuthenticationProvider๋ฅผ ์ดํ•ดํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ณ , ๋˜์•ˆ ๋ณด์•ˆ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š” ์ ์ ˆํ•œ ์ธ์ฆ ๋ฐฉ์‹ ์˜ˆ๋ฅผ ๋“ค์–ด (2 Factor ์ธ์ฆ ๋“ฑ)์„ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๊ฒฝ์šฐ, Custom AuthenticationProvider๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

package com.codestates.auth;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Optional;

@Component
public class HelloUserAuthenticationProvider implements AuthenticationProvider {   // (1)
    private final HelloUserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    public HelloUserAuthenticationProvider(HelloUserDetailsService userDetailsService, 
                                           PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }


    // (3)
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken authToken = (UsernamePasswordAuthenticationToken) authentication;  // (3-1)

        // (3-2)
        String username = authToken.getName();
        Optional.ofNullable(username).orElseThrow(() -> new UsernameNotFoundException("Invalid User name or User Password"));

        // (3-3)
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        String password = userDetails.getPassword();
        verifyCredentials(authToken.getCredentials(), password);    // (3-4)

        Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();  // (3-5)

        // (3-6)
        return UsernamePasswordAuthenticationToken.authenticated(username, password, authorities);
    }

    // (2) HelloUserAuthenticationProvider๊ฐ€ Username/Password ๋ฐฉ์‹์˜ ์ธ์ฆ์„ ์ง€์›ํ•œ๋‹ค๋Š” ๊ฒƒ์„ Spring Security์— ์•Œ๋ ค์ค€๋‹ค.
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.equals(authentication);
    }

    private void verifyCredentials(Object credentials, String password) {
        if (!passwordEncoder.matches((String)credentials, password)) {
            throw new BadCredentialsException("Invalid User name or User Password");
        }
    }
}

(1)๊ณผ ๊ฐ™์ด AuthenticationProvide์˜ ๊ตฌํ˜„ ํด๋ž˜์Šค๋กœ์จ HelloUserAuthenticationProvider๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. 

Spring Security๋Š” ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด AuthentictionProvider๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๊ฐ€ Spring Bean์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค๋ฉด ํ•ด๋‹น AuthenticationProvider๋ฅผ ์ด์šฉํ•ด์„œ ์ธ์ฆ์„ ์ง„ํ–‰ํ•œ๋‹ค.

AuthenticationProvider ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„ ํด๋ž˜์Šค๋Š” authenticate(Authentication authentication) ๋ฉ”์„œ๋“œ์™€ supports(Class<?> authentication) ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

๊ทธ ์ค‘ (2)์˜ supports(Class<?> authentication) ๋ฉ”์„œ๋“œ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๊ตฌํ˜„ํ•˜๋Š” Custom AuthenticationProvider(HelloUserAthenticationProvider)๊ฐ€ Username/Password ๋ฐฉ์‹์˜ ์ธ์ฆ์„ ์ง€์›ํ•œ๋‹ค๋Š” ๊ฒƒ์„ Spring Securityํ•œํ…Œ ์•Œ๋ ค์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

 

supports() ์˜ ๊ฐ’์ด true์ธ ๊ฒฝ์šฐ, Spring Security๋Š” ํ•ด๋‹น AuthenticationProvider์˜ authenticate() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ธ์ฆ์„ ์ง„ํ–‰ํ•œ๋‹ค.

 

(3)์˜ authenticate(Authentication authentication)์—์„œ ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ์ž‘์„ฑํ•œ ์ธ์ฆ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.

 

(3-1)์—์„œ authentication์„ ์บ์ŠคํŒ…ํ•˜์—ฌ UsernamePasswordAuthenticationToken์„ ์–ป๋Š”๋‹ค.

์ด UsernamePasswordAuthenticationToken ๊ฐ์ฒด์—์„œ (3-2)์™€ ๊ฐ™์ด ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ Username์„ ์–ป์€ ํ›„, ์กด์žฌํ•˜๋Š”์ง€ ์ฒดํฌํ•œ๋‹ค.

Username์ด ์กด์žฌํ•˜๋ฉด (3-3)๊ณผ ๊ฐ™์ด userDetailsService๋ฅผ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํ•ด๋‹น ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•œ๋‹ค.

 

(3-4)์—์„œ ๋กœ๊ทธ์ธ ์ •๋ณด์— ํฌํ•จ๋œ ํŒจ์Šค์›Œ๋“œ(authToken.getCredentials())์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ์‚ฌ์šฉ์ž์˜ ํŒจ์Šค์›Œ๋“œ ์ •๋ณด์™€ ์ผ์น˜ํ•˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.

 

(3-4)์˜ ์ธ์ฆ ๊ณผ์ •์„ ํ†ต๊ณผํ–ˆ๋‹ค๋ฉด ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์„ฑ๊ณตํ•œ ์‚ฌ์šฉ์ž์ด๋ฏ€๋กœ (3-5)์™€ ๊ฐ™์ด ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์„ ์ƒ์„ฑํ•œ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ (3-6)๊ณผ ๊ฐ™์ด ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ฆฌํ„ด๊ฐ’์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค. ์ด ์ธ์ฆ ์ •๋ณด๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Spring Security์—์„œ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋œ๋‹ค.

 

 

*SecurityConfiguration์˜ .loginProcessingUrl("/process_login")์—์„œ "/process_login"์— ๋Œ€ํ•œ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด HelloUserAuthenticationProvider๊ฐ€ ๋™์ž‘ํ•˜์—ฌ ๋กœ๊ทธ์ธ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋˜๊ณ , ๋งŒ์•ฝ Custom AuthenticationProvider๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์œผ๋ฉด ๋‚ด๋ถ€์ ์œผ๋กœ AuthenticationProvider๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ , ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณต๋˜๋Š” DaoAuthenticationProvider๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

 

 

์ด์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ณ , ํšŒ์› ๊ฐ€์ž…์„ ํ•œ ๋’ค, ๋กœ๊ทธ์ธ์„ ํ•ด๋ณด์ž.

ํšŒ์› ๊ฐ€์ž… ์‹œ ๋“ฑ๋กํ•œ ์ด๋ฉ”์ผ ์ฃผ์†Œ/ํŒจ์Šค์›Œ๋“œ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ Hello, Spring Security์˜ ๋ฉ”์ธ ํ™”๋ฉด์ด ๋ธŒ๋ผ์šฐ์ €์— ํ‘œ์‹œ๋œ๋‹ค.

 

๋งŒ์•ฝ ํšŒ์› ๊ฐ€์ž…์„ ํ•˜์ง€ ์•Š๊ณ  ๋กœ๊ทธ์ธ์„ ํ•  ๊ฒฝ์šฐ ์ธ์ฆ์— ์‹คํŒจํ•˜๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•˜์–€ ์—๋Ÿฌ ํ™”๋ฉด์„ ๋งŒ๋‚˜๊ฒŒ ๋œ๋‹ค.

 

 

์šฐ๋ฆฌ๊ฐ€ ์•ž์—์„œ HelloUserDetailsService๋ฅผ ํ†ตํ•ด ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ์—๋Š” ์ธ์ฆ ์‹คํŒจ ์‹œ, Spring Security ๋‚ด๋ถ€์—์„œ ์ธ์ฆ ์‹คํŒจ์— ๋Œ€ํ•œ ์ „์šฉ Exception์ธ AuthenticationException์„ throwํ•˜๊ฒŒ ๋˜๊ณ , ์ด AuthenticationException์ด throw ๋˜๋ฉด ๊ฒฐ๊ณผ์ ์œผ๋กœ SecurityCofiguration์—์„œ ์„ค์ •ํ•œ .failureUrl("auths/login-form?error")๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ ํผ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๋ฉด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด "๋กœ๊ทธ์ธ ์ธ์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."๋ผ๋Š” ์ธ์ฆ ์‹คํŒจ ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.

 

 

๊ทธ๋Ÿฐ๋ฐ Custom AuthenticationProvider๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ํšŒ์› ๊ฐ€์ž… ์ „ ์ธ์ฆ ์‹คํŒจ ์‹œ, ์™œ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์€ ํ™”๋ฉด์ด ์•„๋‹Œ "Whitelebel Error Page" ๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์ผ๊นŒ?

 

์ด์œ ๋Š” MemberService์—์„œ ๋“ฑ๋ก๋œ ํšŒ์› ์ •๋ณด๊ฐ€ ์—†์œผ๋ฉด, BusinessLogicException์„ throw ํ•˜๋Š”๋ฐ ์ด๋•Œ BusinessLogicException์ด Custom AuthenticationProvider๋ฅผ ๊ฑฐ์ณ์„œ ๊ทธ๋Œ€๋กœ Spring Security ๋‚ด๋ถ€ ์˜์—ญ์œผ๋กœ throw๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

Spring Security์—์„œ๋Š” ์ธ์ฆ ์‹คํŒจ ์‹œ, authenticationException์ด throw๋˜์ง€ ์•Š์œผ๋ฉด Exception์— ๋Œ€ํ•œ ๋ณ„๋„์˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๊ณ , ์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ์˜ ํ†ฐ์บฃ ์ชฝ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ๋„˜๊ธด๋‹ค.

 

๊ฒฐ๊ตญ ์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ ์˜์—ญ์—์„œ ํ•ด๋‹น Exception์— ๋Œ€ํ•œ "/error" URL๋กœ ํฌ์›Œ๋”ฉํ•˜๋Š”๋ฐ ์šฐ๋ฆฌ๊ฐ€ ํŠน๋ณ„ํžˆ "/error" URL๋กœ ํฌ์›Œ๋”ฉ๋˜์—ˆ์„ ๋•Œ ๋ณด์—ฌ์ค„ ๋ทฐ ํŽ˜์ด์ง€๋ฅผ ๋ณ„๋„๋กœ ๊ตฌ์„ฑํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋””ํดํŠธ ํŽ˜์ด์ง€์ธ "Whitelebel Error Page"๋ฅผ ๋ธŒ๋ผ์šฐ์ €์— ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ.

 

โญ๏ธ ํ•ด๊ฒฐ์ฑ…์€ ๊ฐ„๋‹จํ•จ. Custom AuthenticationProvider์—์„œ Exception์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ, ์ด Exception์„ catchํ•ด์„œ AuthenticationException์œผ๋กœ rethrow๋ฅผ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

package com.codestates.auth;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Optional;

@Component
public class HelloUserAuthenticationProvider implements AuthenticationProvider {
    private final HelloUserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    public HelloUserAuthenticationProvider(HelloUserDetailsService userDetailsService,
                                           PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }

    // V2: AuthenticationException์„ rethrow ํ•˜๋Š” ๊ฐœ์„  ์ฝ”๋“œ
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken authToken = (UsernamePasswordAuthenticationToken) authentication;

        String username = authToken.getName();
        Optional.ofNullable(username).orElseThrow(() -> new UsernameNotFoundException("Invalid User name or User Password"));
        try {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            String password = userDetails.getPassword();
            verifyCredentials(authToken.getCredentials(), password);

            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            return UsernamePasswordAuthenticationToken.authenticated(username, password, authorities);
        } catch (Exception ex) {
            throw new UsernameNotFoundException(ex.getMessage()); // (1) AuthenticationException์œผ๋กœ ๋‹ค์‹œ throw ํ•œ๋‹ค.
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.equals(authentication);
    }

    private void verifyCredentials(Object credentials, String password) {
        if (!passwordEncoder.matches((String)credentials, password)) {
            throw new BadCredentialsException("Invalid User name or User Password");
        }
    }
}

(1)์—์„œ UsernameNotFoundException์„ throw ํ•˜๋„๋ก ์ˆ˜์ •๋˜์—ˆ๋Š”๋ฐ, UsernameNotFoundException์„ throwํ•˜๋„๋ก ์ˆ˜์ •๋˜์—ˆ๋Š”๋ฐ, UsernameNoFoundException์€ AuthenticationException์„ ์ƒ์†ํ•˜๋Š” ํ•˜์œ„ Exception์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด UsernameNotFoundException์ด throwํ•˜๊ฒŒ ๋˜๋ฉด Spring Security์ชฝ์—์„œ ์ •์ƒ์ ์œผ๋กœ catchํ•ด์„œ ์ •์ƒ์ ์ธ ์ธ์ฆ ์‹คํŒจ ํ™”๋ฉด์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์‹œ์ผœ์ค€๋‹ค.

 

 

โญ Custom AuthenticationProvider์—์„œ AuthenticationException์ด ์•„๋‹Œ Exception์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ์—๋Š” ๊ผญ AuthenticationException์„ rethrow ํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ๊ตฌ์„ฑํ•ด์•ผ ํ•œ๋‹ค

 

 

*AuthenticationProvider๋Š” Spring Security์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์ธ์ง€์— ๋Œ€ํ•œ ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” Spring Security ์ปดํฌ๋„ŒํŠธ์ด๋‹ค. AuthenticationProvider๋Š” ์ธํ„ฐํŽ˜์ด์Šค ํ˜•ํƒœ.

 

๋‹ค์Œ ํฌ์ŠคํŒ…์„ ํ†ตํ•ด Srpgin Security์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ์ผ์–ด๋‚˜๋Š” User ์ธ์ฆ๊ณผ ๊ถŒํ•œ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ ๊ณผ์ •์„ ๊ตฌ์ฒด์ ์œผ๋กœ ์•Œ์•„๋ณด๋„๋ก ํ•˜์ž.