[Security] JWT(JSON Web Token)
Categories: Spring
๐ ๊ฐ์ธ์ ์ธ ๊ณต๊ฐ์ผ๋ก ๊ณต๋ถ๋ฅผ ๊ธฐ๋กํ๊ณ ๋ณต์ตํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๋ธ๋ก๊ทธ์
๋๋ค.
์ ํํ์ง ์์ ์ ๋ณด๊ฐ ์์ ์ ์์ผ๋ ์ฐธ๊ณ ๋ฐ๋๋๋ค :๐ธ
[ํ๋ฆฐ ๋ด์ฉ์ ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์๋ฉด ๋ณต๋ฐ์ผ์ค๊ฑฐ์์]
JWT(JSON Web Token)
ํ ํฐ ๊ธฐ๋ฐ ์๊ฒฉ ์ฆ๋ช
์ธ์ ๊ธฐ๋ฐ ์๊ฒฉ ์ฆ๋ช
์ธ์ ๊ธฐ๋ฐ ์๊ฒฉ ์ฆ๋ช ๋ฐฉ์์ ์๋ฒ ์ธก์ ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ธ์ ํํ๋ก ์ธ์ ์ ์ฅ์์ ์ ์ฅํ๋ ๋ฐฉ์์ด๋ค.
- ์ธ์
๊ธฐ๋ฐ ์๊ฒฉ ์ฆ๋ช
์ ํน์ง
- ์ธ์ ์ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์๋ฒ ์ธก ์ธ์ ์ ์ฅ์์์ ๊ด๋ฆฌํ๋ค.
- ์์ฑ๋ ์ฌ์ฉ์ ์ธ์ ์ ๊ณ ์ ID์ธ ์ธ์ ID๋ ํด๋ผ์ด์ธํธ์ ์ฟ ํค์ ์ ์ฅ๋์ด request ์ ์ก ์, ์ธ์ฆ๋ ์ฌ์ฉ์์ธ์ง๋ฅผ ์ฆ๋ช ํ๋ ์๋จ์ผ๋ก ์ฌ์ฉ๋๋ค.
- ์ธ์ ID๋ง ํด๋ผ์ด์ธํธ ์ชฝ์์ ์ฌ์ฉํ๋ฏ๋ก ์๋์ ์ผ๋ก ์ ์ ๋คํธ์ํฌ ํธ๋ํฝ์ ์ฌ์ฉํ๋ค.
- ์๋ฒ ์ธก์์ ์ธ์ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋ฏ๋ก ๋ณด์์ฑ ์ธก๋ฉด์์ ์กฐ๊ธ ๋ ์ ๋ฆฌํ๋ค.
- ์๋ฒ์ ํ์ฅ์ฑ ๋ฉด์์๋ ์ธ์ ๋ถ์ผ์น ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ๋๋ค.
- ์ธ์ ๋ฐ์ดํฐ๊ฐ ๋ง์์ง๋ฉด ์ง์๋ก ์๋ฒ์ ๋ถ๋ด์ด ๊ฐ์ค๋ ์ ์๋ค.
- SSR(Server Side Rendering) ๋ฐฉ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํฉํ ๋ฐฉ์์ด๋ค
ํ ํฐ๊ธฐ๋ฐ ์๊ฒฉ ์ฆ๋ช
ํ ํฐ ๊ธฐ๋ฐ ์๊ฒฉ ์ฆ๋ช ๋ฐฉ์์ ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ํ ํฐ์ ์ ์ฅํ๊ณ , ์ ๊ทผ ๊ถํ์ ๋ถ์ฌํด ์ ๊ทผ ๊ถํ์ด ๋ถ์ฌ๋ ํน์ ๋ฆฌ์์ค์๋ง ์ ๊ทผํ ์ ์๊ฒ ํ๋ ๋ฐฉ์์ด๋ค.
- ํ ํฐ ๊ธฐ๋ฐ ์๊ฒฉ ์ฆ๋ช
์ ํน์ง
- ํ ํฐ์ ํฌํจ๋ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด๋ ์๋ฒ ์ธก์์ ๋ณ๋์ ๊ด๋ฆฌ๋ฅผ ํ์ง ์๋๋ค.
- ์์ฑ๋ ํ ํฐ์ ํค๋์ ํฌํจํด request ์ ์ก ์, ์ธ์ฆ๋ ์ฌ์ฉ์์ธ์ง๋ฅผ ์ฆ๋ช ํ๋ ์๋จ์ผ๋ก ์ฌ์ฉ๋๋ค.
- ํ ํฐ ๋ด์ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด ๋ฑ์ ํฌํจํ๊ณ ์์ผ๋ฏ๋ก ์ธ์ ์ ๋นํด ์๋์ ์ผ๋ก ๋ง์ ๋คํธ์ํฌ ํธ๋ํฝ์ ์ฌ์ฉํ๋ค.
- ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฒ ์ธก์์ ํ ํฐ์ ๊ด๋ฆฌํ์ง ์์ผ๋ฏ๋ก ๋ณด์์ฑ ์ธก๋ฉด์์ ์กฐ๊ธ ๋ ๋ถ๋ฆฌํ๋ค.
- ์ธ์ฆ๋ ์ฌ์ฉ์ request์ ์ํ๋ฅผ ์ ์งํ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ ์๋ฒ์ ํ์ฅ์ฑ ๋ฉด์์ ์ ๋ฆฌํ๊ณ , ์ธ์ ๋ถ์ผ์น ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
- ํ ํฐ์ ํฌํจ๋๋ ์ฌ์ฉ์ ์ ๋ณด๋ ํ ํฐ์ ํน์ฑ์ ์ํธํ๊ฐ ๋์ง ์๊ธฐ ๋๋ฌธ์ ๊ณต๊ฒฉ์์๊ฒ ํ ํฐ์ด ํ์ทจ๋ ๊ฒฝ์ฐ, ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ์ ๊ณตํ๋ ์ ์ด ๋๋ฏ๋ก ๋ฏผ๊ฐํ ์ ๋ณด๋ ํ ํฐ์ ํฌํจํ์ง ๋ง์์ผ ํ๋ค.
- ๊ธฐ๋ณธ์ ์ผ๋ก ํ ํฐ์ด ๋ง๋ฃ๋๊ธฐ ์ ๊น์ง๋ ํ ํฐ์ ๋ฌดํจํ์ํฌ ์ ์๋ค.
- CSR(Client Side Rendering) ๋ฐฉ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํฉํ ๋ฐฉ์์ด๋ค
JWT(JSON Web Token)
JWT๋ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ณ ๊ฐ๊ฒฐํ๊ฒ ์ ์กํ๊ธฐ ์ํด ๊ณ ์๋ ์ธํฐ๋ท ํ์ค ์ธ์ฆ ๋ฐฉ์์ผ๋ก์จ ํ ํฐ ์ธ์ฆ ๋ฐฉ์์์ ๊ฐ์ฅ ๋ฒ์ฉ์ ์ผ๋ก ์ฌ์ฉ๋๋ฉฐ JSON ํฌ๋งท์ ํ ํฐ ์ ๋ณด๋ฅผ ์ธ์ฝ๋ฉ ํ, ์ธ์ฝ๋ฉ ๋ ํ ํฐ ์ ๋ณด๋ฅผ Secret Key๋ก ์๋ช (Sign)ํ ๋ฉ์์ง๋ฅผ Web Token์ผ๋ก์จ ์ธ์ฆ ๊ณผ์ ์ ์ฌ์ฉ
JWT ๊ณต์ ์ฌ์ดํธ : https://jwt.io/
JWT์ ์ข ๋ฅ
JWT๋ ๋ณดํต ๋ค์๊ณผ ๊ฐ์ด ๋ ๊ฐ์ง ์ข ๋ฅ์ ํ ํฐ์ ์ฌ์ฉ์์ ์๊ฒฉ ์ฆ๋ช ์ ์ด์ฉํ๋ค โ Access Token & Refresh Token
- ์ก์ธ์ค ํ ํฐ(Access Token)
- ๋ณดํธ๋ ์ ๋ณด๋ค(์ฌ์ฉ์์ ์ด๋ฉ์ผ, ์ฐ๋ฝ์ฒ, ์ฌ์ง ๋ฑ)์ ์ ๊ทผํ ์ ์๋ ๊ถํ ๋ถ์ฌ์ ์ฌ์ฉ
- ํด๋ผ์ด์ธํธ๊ฐ ์ฒ์ ์ธ์ฆ์ ๋ฐ๊ฒ ๋ ๋(๋ก๊ทธ์ธ ์), Access Token๊ณผ Refresh Token ๋ ๊ฐ์ง๋ฅผ ๋ค ๋ฐ์ง๋ง, ์ค์ ๋ก ๊ถํ์ ์ป๋ ๋ฐ ์ฌ์ฉํ๋ ํ ํฐ์ Access Token
- ์ฃผ์ ๊ถํ์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ Access Token์๋ ๋น๊ต์ ์งง์ ์ ํจ ๊ธฐ๊ฐ์ ์ฃผ์ด ํ์ทจ๋๋๋ผ๋ ์ค๋ซ๋์ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ค. โ Access Token์ ์ ํจ๊ธฐ๊ฐ์ด ๋ง๋ฃ๋๋ค๋ฉด Refresh Token์ ์ฌ์ฉํ์ฌ ์๋ก์ด Access Token์ ๋ฐ๊ธ๋ฐ๋๋ค. ์ด๋, ์ฌ์ฉ์๋ ๋ค์ ๋ก๊ทธ์ธ ์ธ์ฆ์ ํ ํ์๊ฐ ์๋ค.
- ๋ฆฌํ๋ ์ ํ ํฐ(Refresh Token)
- ์ก์ธ์ค ํ ํฐ์ ์ฌ๋ฐ๊ธ๋ฐ๊ธฐ ์ํ ์ ๋ณด๋ง ๋ด๊ณ ์์.
- Access Token์ ์ ํจ๊ธฐ๊ฐ์ด ๋ง๋ฃ๋๋ค๋ฉด Refresh Token์ ์ฌ์ฉํ์ฌ
- ๋ฆฌํ๋ ์ฌํ ํฐ์ ์ ํจ๊ธฐ๊ฐ์ด ๊ธธ๊ธฐ ๋๋ฌธ์ ํ์ทจ๋นํ๋ฉด ๋ฌธ์ ๊ฐ ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ฆฌํ๋ ์ฌํ ํฐ์ ์ผํ์ฉ์ผ๋ก ๋ง๋ ๋ค.
- ์ฆ ์ํ๋ฅผ ๊ฐ์ง์ง ์์ง๋ง. ์ก์ธ์ค ํ ํฐ์ด ๋ง๋ฃ๋์ ๋ฆฌํ๋ ์์์ ์ก์ธ์คํ ํฐ์ ์์ฒญ ํ ๋ ๋ฆฌํ๋ ์ ํ ํฐ๋ ํจ๊ป ์ฌ๋ฐํ ํ๋ค.
- ์ฌ์ฉ์์
ํธ์
๋ณด๋ค์ ๋ณด๋ฅผ ์งํค๋ ๊ฒ์ด ๋ ์ค์
ํ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ Refresh Token์ ์ฌ์ฉํ์ง ์๋ ๊ณณ์ด ๋ง๋ค.
JWT ๊ตฌ์กฐ
JWT๋ ์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด .
์ผ๋ก ๋๋์ด์ง ์ธ ๋ถ๋ถ์ด ์กด์ฌํ๋ค.
- Header
- Header๋ ์ด๊ฒ์ด ์ด๋ค ์ข ๋ฅ์ ํ ํฐ์ธ์ง(์ง๊ธ์ ๊ฒฝ์ฐ์ JWT), ์ด๋ค ์๊ณ ๋ฆฌ์ฆ์ผ๋ก Signํ ์ง ์ ์ํ๋ค.
-
JSON Web Token์ด๋ผ๋ ์ด๋ฆ์ ๊ฑธ๋ง๊ฒ JSON ํฌ๋งท ํํ๋ก ์ ์๋์ด ์๋ค.
1 2 3 4
{ "alg": "HS256", "typ": "JWT" }
- ๋จ๋ฐฉํฅ ์ํธํ
์๋ฒ์์๋ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์๊ทธ๋์ฒ๋ก ํ์ธํ๋ค โ ์๊ทธ๋์ฒ๊ฐ ๊ฒ์ฆ์ ๋์์ด๋ค ,
JWT์ ํค๋์ ํ๋ ์ด ๋ก๋๋ ํด์ํจ์ ๊ฒ์ฆ๊ณผ์ ์์ ๊ฑธ๋ฌ์ง
๋๊ฐ์ง ๋ฐ์ดํฐ๋ฅผ ๋ณตํธํํ๋๋ผ๋ ๋ณดํต ํด๋ผ์ด์ธํธ์์ ์๋ฒ๋ก ๋ณด๋ผ๋ ํค๋์ ๋ด์์ ๋ณด
-
Payload
1 2 3 4 5
{ "sub": "someInformation", "name": "phillip", "iat": 151623391 }
- Payload์๋ ์๋ฒ์์ ํ์ฉํ ์ ์๋ ์ฌ์ฉ์์ ์ ๋ณด๊ฐ ๋ด๊ฒจ ์๋ค.
- ์ด๋ค ์ ๋ณด์ ์ ๊ทผ ๊ฐ๋ฅํ์ง์ ๋ํ ๊ถํ์ ๋ด์ ์๋ ์๊ณ , ์ฌ์ฉ์์ ์ด๋ฆ ๋ฑ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ด์ ์ ์๋ค.
- Payload๋ ๋ค์์ผ๋ก ์ค๋ช ํ Signature๋ฅผ ํตํด ์ ํจ์ฑ์ด ๊ฒ์ฆ๋ ์ ๋ณด์ด๊ธด ํ์ง๋ง, ๋ฏผ๊ฐํ ์ ๋ณด๋ ๋ด์ง ์๋ ๊ฒ์ด ์ข๋ค. (์์ฝ๊ฒ ์ฝ๋ฉ ํ ์ ์๊ธฐ ๋๋ฌธ)
- ์ฒซ ๋ฒ์งธ ๋ถ๋ถ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก, ์ JSON ๊ฐ์ฒด๋ฅผ base64๋ก ์ธ์ฝ๋ฉํ๋ฉด JWT์ ๋ ๋ฒ์งธ ๋ธ๋ก์ด ์์ฑ๋ฉ๋๋ค.
- Signature
- base64๋ก ์ธ์ฝ๋ฉ ๋ ์ฒซ ๋ฒ์งธ, ๊ทธ๋ฆฌ๊ณ ๋ ๋ฒ์งธ ๋ถ๋ถ์ด ์์ฑ๋์๋ค๋ฉด, Signature์์๋ ์ํ๋ ๋น๋ฐ ํค(Secret Key)์ Header์์ ์ง์ ํ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ์ฌ Header์ Payload์ ๋ํด์ ๋จ๋ฐฉํฅ ์ํธํ๋ฅผ ์ํํ๋ค.
- ์ด๋ ๊ฒ ์ํธํ๋ ๋ฉ์์ง๋ ํ ํฐ์ ์๋ณ์กฐ ์ ๋ฌด๋ฅผ ๊ฒ์ฆํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
-
์๋ฅผ ๋ค์ด, ๋ง์ฝ HMAC SHA256 ์๊ณ ๋ฆฌ์ฆ(์ํธํ ๋ฐฉ๋ฒ ์ค ํ๋)์ ์ฌ์ฉํ๋ค๋ฉด Signature๋ ์๋์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์์ฑ๋จ
1
HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);
JWT ์ฌ์ฉ ์์
JWT๋ ๊ถํ ๋ถ์ฌ์ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
์๋ก ๋ค์ด๋ก๋ํ A
๋ผ๋ ์ฑ์ด Gmail๊ณผ ์ฐ๋๋์ด ์ด๋ฉ์ผ์ ์ฝ์ด์์ผ ํ๋ค๊ณ ์๊ฐํด ๋ด
์๋ค.
์ด ๊ฒฝ์ฐ, ์ฌ์ฉ์๋
- Gmail ์ธ์ฆ์๋ฒ์ ๋ก๊ทธ์ธ ์ ๋ณด(์์ด๋, ๋น๋ฐ๋ฒํธ)๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ธ์ฆ์ ์ฑ๊ณตํ ๊ฒฝ์ฐ, JWT๋ฅผ ๋ฐ๊ธ๋ฐ์ต๋๋ค.
- A ์ฑ์ JWT๋ฅผ ์ฌ์ฉํด ํด๋น ์ฌ์ฉ์์ ์ด๋ฉ์ผ์ ์ฝ๊ฑฐ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ์ ์ฐจ
- ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ์์ด๋/๋น๋ฐ๋ฒํธ๋ฅผ ๋ด์ ๋ก๊ทธ์ธ ์์ฒญ์ ๋ณด๋ ๋๋ค.
- ์์ด๋/๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ๋์ง ํ์ธํ๊ณ , ํด๋ผ์ด์ธํธ์๊ฒ ๋ณด๋ผ ์ํธํ๋ ํ ํฐ์ ์์ฑํฉ๋๋ค.
- Access Token๊ณผ Refresh Token์ ๋ชจ๋ ์์ฑํฉ๋๋ค.
- ํ ํฐ์ ๋ด๊ธธ ์ ๋ณด(Payload)๋ ์ฌ์ฉ์๋ฅผ ์๋ณํ ์ ๋ณด, ์ฌ์ฉ์์ ๊ถํ ์ ๋ณด ๋ฑ์ด ๋ ์ ์์ต๋๋ค.
- Refresh Token ์ด์ฉํด ์๋ก์ด Access Token์ ์์ฑํ ๊ฒ์ด๋ฏ๋ก ๋ ์ข
๋ฅ์ ํ ํฐ์ด
๊ฐ์ ์ ๋ณด๋ฅผ ๋ด์ ํ์
๋ ์์ต๋๋ค.
- Access Token๊ณผ Refresh Token์ ๋ชจ๋ ์์ฑํฉ๋๋ค.
- ํ ํฐ์ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์กํ๋ฉด, ํด๋ผ์ด์ธํธ๋ ํ ํฐ์ ์ ์ฅํฉ๋๋ค.
- ์ ์ฅํ๋ ์์น๋ Local Storage, Session Storage, Cookie ๋ฑ์ด ๋ ์ ์์ต๋๋ค.
- ํด๋ผ์ด์ธํธ๊ฐ HTTP Header(Authorization Header) ๋๋ ์ฟ ํค์ ํ ํฐ์ ๋ด์ request๋ฅผ ์ ์กํฉ๋๋ค.
- Bearer authentication์ ์ด์ฉํฉ๋๋ค. ์ฐธ๊ณ ๋งํฌ1(์์ฝ), ๋งํฌ2(์์ธ)
- ์๋ฒ๋ ํ ํฐ์ ๊ฒ์ฆํ์ฌ โ์ ์ฐ๋ฆฌ๊ฐ ๋ฐ๊ธํด ์ค ํ ํฐ์ด ๋ง๋ค!โ๋ผ๋ ํ๋จ์ด ๋ ๊ฒฝ์ฐ, ํด๋ผ์ด์ธํธ์ ์์ฒญ์ ์ฒ๋ฆฌํ ํ ์๋ต์ ๋ณด๋ด์ค๋ค.
์ฅ๋จ์
JWT๋ฅผ ํตํ ์ธ์ฆ์ ์ฅ์
- ์ํ๋ฅผ ์ ์งํ์ง ์๊ณ (Stateless), ํ์ฅ์ ์ฉ์ดํ(Scalable) ์ ํ๋ฆฌ์ผ์ด์
์ ๊ตฌํํ๊ธฐ ์ฉ์ดํ๋ค.
- ์๋ฒ๋ ํด๋ผ์ด์ธํธ์ ๋ํ ์ ๋ณด๋ฅผ ์ ์ฅํ ํ์ ์์ต๋๋ค. (ํ ํฐ์ด ์ ์์ ์ผ๋ก ๊ฒ์ฆ๋๋์ง๋ง ํ๋จํฉ๋๋ค)
- ํด๋ผ์ด์ธํธ๋ request๋ฅผ ์ ์กํ ๋๋ง๋ค ํ ํฐ์ ํค๋์ ํฌํจ์ํค๋ฉด ๋๋ค
- ์ฌ๋ฌ ๋์ ์๋ฒ๋ฅผ ์ด์ฉํ ์๋น์ค๋ผ๋ฉด ํ๋์ ํ ํฐ์ผ๋ก ์ฌ๋ฌ ์๋ฒ์์ ์ธ์ฆ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ JWT๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ํจ๊ณผ์ ์ด๋ค.
๋ง์ฝ์ ์ธ์ ๋ฐฉ์์ด๋ผ๋ฉด ๋ชจ๋ ์๋ฒ๊ฐ ํด๋น ์ฌ์ฉ์์ ์ธ์ ์ ๋ณด๋ฅผ ๊ณต์ ํ๊ณ ์์ด์ผ ํ๋ค.
- ํด๋ผ์ด์ธํธ๊ฐ request๋ฅผ ์ ์กํ ๋๋ง๋ค ์๊ฒฉ ์ฆ๋ช
์ ๋ณด๋ฅผ ์ ์กํ ํ์๊ฐ ์๋ค.
- HTTP Basic ๊ฐ์ ์ธ์ฆ ๋ฐฉ์์ request๋ฅผ ์ ์กํ ๋๋ง๋ค ์๊ฒฉ ์ฆ๋ช ์ ๋ณด๋ฅผ ํฌํจํด์ผ ํ์ง๋ง JWT์ ๊ฒฝ์ฐ ํ ํฐ์ด ๋ง๋ฃ๋๊ธฐ ์ ๊น์ง๋ ํ ๋ฒ์ ์ธ์ฆ๋ง ์ํํ๋ฉด ๋๋ค.
- ์ธ์ฆ์ ๋ด๋นํ๋ ์์คํ
์ ๋ค๋ฅธ ํ๋ซํผ์ผ๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ด ์ฉ์ดํ๋ค
- ์ฌ์ฉ์์ ์๊ฒฉ ์ฆ๋ช ์ ๋ณด๋ฅผ ์ง์ ๊ด๋ฆฌํ์ง ์๊ณ , Github, Google ๋ฑ์ ๋ค๋ฅธ ํ๋ซํผ์ ์๊ฒฉ ์ฆ๋ช ์ ๋ณด๋ก ์ธ์ฆํ๋ ๊ฒ์ด ๊ฐ๋ฅ
- ํ ํฐ ์์ฑ์ฉ ์๋ฒ๋ฅผ ๋ง๋ค๊ฑฐ๋, ๋ค๋ฅธ ํ์ฌ์์ ํ ํฐ ๊ด๋ จ ์์ ์ ๋งก๊ธฐ๋ ๊ฒ ๋ฑ ๋ค์ํ ํ์ฉ์ด ๊ฐ๋ฅ
- ๊ถํ ๋ถ์ฌ์ ์ฉ์ดํ๋ค
- ํ ํฐ์ Payload(๋ด์ฉ๋ฌผ) ์์ ํด๋น ์ฌ์ฉ์์ ๊ถํ ์ ๋ณด๋ฅผ ํฌํจํ๋ ๊ฒ์ด ์ฉ์ดํ๋ค
JWT๋ฅผ ํตํ ์ธ์ฆ์ ๋จ์
- Payload๋ ๋์ฝ๋ฉ์ด ์ฉ์ดํ๋ค
- Payload๋ base64๋ก ์ธ์ฝ๋ฉ ๋๊ธฐ ๋๋ฌธ์ ํ ํฐ์ ํ์ทจํ์ฌ Payload๋ฅผ ๋์ฝ๋ฉํ๋ฉด ํ ํฐ ์์ฑ ์ ์ ์ฅํ ๋ฐ์ดํฐ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ Payload์๋ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ํฌํจํ์ง ์์์ผ ํ๋ค.
- ํ ํฐ์ ๊ธธ์ด๊ฐ ๊ธธ์ด์ง๋ฉด ๋คํธ์ํฌ์ ๋ถํ๋ฅผ ์ค ์ ์๋ค
-
ํ ํฐ์ ์ ์ฅํ๋ ์ ๋ณด์ ์์ด ๋ง์์ง์๋ก ํ ํฐ์ ๊ธธ์ด๋ ๊ธธ์ด์ง๋ค.
๋ฐ๋ผ์ request๋ฅผ ์ ์กํ ๋๋ง๋ค ๊ธธ์ด๊ฐ ๊ธด ํ ํฐ์ ํจ๊ป ์ ์กํ๋ฉด ๋คํธ์ํฌ์ ๋ถํ๋ฅผ ์ค ์ ์๋ค.
-
- ํ ํฐ์ ์๋์ผ๋ก ์ญ์ ๋์ง ์๋๋ค.
- ์ฆ ํ ๋ฒ ์์ฑ๋ ํ ํฐ์ ์๋์ผ๋ก ์ญ์ ๋์ง ์๊ธฐ ๋๋ฌธ์ ํ ํฐ ๋ง๋ฃ ์๊ฐ์ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํ๋ค.
- ๋ํ ํ ํฐ์ด ํ์ทจ๋ ๊ฒฝ์ฐ ํ ํฐ์ ๊ธฐํ์ด ๋ง๋ฃ๋ ๋๊น์ง ํ ํฐ ํ์ทจ์๊ฐ ํด๋น ํ ํฐ์ ์ ์์ ์ผ๋ก ์ด์ฉํ ์ ์์ผ๋ฏ๋ก ๋ง๋ฃ ์๊ฐ์ ๋๋ฌด ๊ธธ๊ฒ ์ค์ ํ์ง ์์์ผ ํ๋ค.
์ปคํผ ์ฃผ๋ฌธ์ ํ๋ฆฌ์ผ์ด์ ์ JWT์ ์ฉ
์ฌ์ ์์ (์์กด์ฑ์ถ๊ฐ, ๋น๋ฐ๋ฒํธ๊ด๋ จ ํ๋ ์ถ๊ฐ)
1. ์์กด๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ
1
2
3
4
5
6
7
// Spring Security๋ฅผ ์ ์ฉํ๊ธฐ ์ํด spring-boot-starter-security๋ฅผ ์ถ๊ฐ
implementation 'org.springframework.boot:spring-boot-starter-security'
// (2) JWT ๊ธฐ๋ฅ์ ์ํ jjwt ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
2. JWT ์ ์ฉ ์ Spring Security๋ฅผ ์ด์ฉํด์ ์ต์ํ์ ๋ณด์๊ตฌ์ฑ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.springboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
http
.headers().frameOptions().sameOrigin()
//๋์ผ ์ถ์ฒ๋ก๋ถํฐ ๋ค์ด์ค๋ request๋ง ํ์ด์ง ๋ ๋๋ง์ ํ์ฉ
.and()
.csrf().disable() //๋ฐฐํฌํ ๋๋ง ๋ณ๊ฒฝํ๋ฉด ๋จ.
.cors(withDefaults()) //corsConfigurationSource๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ฑ๋ก๋ Bean์ ์ด์ฉ
//CORS๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฐ์ฅ ์ฌ์ด ๋ฐฉ๋ฒ์ CorsFilter๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ธ๋ฐ
// CorsConfigurationSource Bean์ ์ ๊ณตํจ์ผ๋ก์จ CorsFilter๋ฅผ ์ ์ฉํ ์ ์๋ค.
.formLogin().disable() //ํผ๋ก๊ทธ์ธ ์ฌ์ฉํ์ง ์๊ฒ๋ค.
.httpBasic().disable()
.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
//์ธ์ฆ์ธ๊ฐ ํ ๋ณ๊ฒฝ์์
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//Cors์ค์ ์ถ๊ฐ, corsConfigurationSource๋ฅผ ๋น์ผ๋ก ๋๋ก
@Bean
CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
//๋ชจ๋ ์ถ์ฒ(Origin)์ ๋ํด ์คํฌ๋ฆฝํธ ๊ธฐ๋ฐ์ HTTP ํต์ ์ ํ์ฉํ๋๋ก ์ค์
configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
//ํ๋ผ๋ฏธํฐ๋ก ์ง์ ํ HTTP Method์ ๋ํ HTTP ํต์ ์ ํ์ฉ
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",configuration);
return source;
}
}
Password ํ๋์ถ๊ฐ
-
MemberDto.Post
1 2
@NotBlank private String password;
-
Member -password, Authorize ์ถ๊ฐ
1 2 3 4 5
@Column(length = 100, nullable = false) private String password; @ElementCollection(fetch = FetchType.EAGER) private List<String>role = new ArrayList<>();
-
MemberService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
@Transactional @Service public class MemberService { private final MemberRepository memberRepository; private final ApplicationEventPublisher publisher; // ์ถ๊ฐ private final PasswordEncoder passwordEncoder; private final JwtAuthorityUtils jwtAuthorityUtils; public MemberService(MemberRepository memberRepository, ApplicationEventPublisher publisher, PasswordEncoder passwordEncoder, JwtAuthorityUtils jwtAuthorityUtils) { this.memberRepository = memberRepository; this.publisher = publisher; //์ถ๊ฐ this.passwordEncoder = passwordEncoder; this.jwtAuthorityUtils = jwtAuthorityUtils; } public Member createMember(Member member) { verifyExistsEmail(member.getEmail()); //ํจ์ค์๋ ์ํธํ ํ์ - ๋จ๋ฐฉํฅ ์ํธ String encrypedPassword = passwordEncoder.encode(member.getPassword()); member.setPhone(encrypedPassword); //DB์ UserRoles ์ ์ฅ List<String> roles = jwtAuthorityUtils.createRoles(member.getEmail()); member.setRoles(roles); Member savedMember = memberRepository.save(member); publisher.publishEvent(new MemberRegistrationApplicationEvent(this, savedMember)); return savedMember; }
๋ฉฑ๋ฑ์ฑ์ ๊ฐ์ง๋ ๊ฒ. (์๋ฒ์ ๋์ผํ ์์ฒญ์ ํ๋์ง๋ฅผ ๋ด์ผ ํจ.= GET,POST,DELETE
-
JwtAuthorityUtils ํด๋์ค ์์ฑ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
package com.springboot.auth.utils; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Component public class JwtAuthorityUtils { @Value("${mail.address.admin}") private String adminMailAddress; private final List<GrantedAuthority> ADMIN_ROLES = AuthorityUtils.createAuthorityList("ROLE_ADMIN","ROLEUSER"); 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"); public List<GrantedAuthority> createAutrorities(String email){ if(email.equals(adminMailAddress)){ return ADMIN_ROLES; } return USER_ROLES; } public List<GrantedAuthority>createAuthority(List<String>roles){ return roles.stream() .map(role-> new SimpleGrantedAuthority("ROLE_"+role)) .collect(Collectors.toList()); } public List<String>createRoles(String email){ if(email.equals(adminMailAddress)){ return ADMIN_ROLES_STRING; } return USER_ROLES_STRING; } }
๋ก๊ทธ์ธ ์ธ์ฆ
์ฌ์ฉ์์ Username(์ด๋ฉ์ผ ์ฃผ์)๊ณผ Password๋ก ๋ก๊ทธ์ธ ์ธ์ฆ์ ์ฑ๊ณตํ๋ฉด ๋ก๊ทธ์ธ ์ธ์ฆ์ ์ฑ๊ณตํ ์ฌ์ฉ์์๊ฒ JWT๋ฅผ ์์ฑ ๋ฐ ๋ฐ๊ธํ๊ธฐ
์ฌ์ฉ์์ ๋ก๊ทธ์ธ ์ธ์ฆ ์ฑ๊ณต ํ, JWT๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌ๋๋ ๊ณผ์
- ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ ์ธก์ ๋ก๊ทธ์ธ ์ธ์ฆ ์์ฒญ(Username/Password๋ฅผ ์๋ฒ ์ธก์ ์ ์ก)
- ๋ก๊ทธ์ธ ์ธ์ฆ์ ๋ด๋นํ๋ Security Filter(
JwtAuthenticationFilter
)๊ฐ ํด๋ผ์ด์ธํธ์ ๋ก๊ทธ์ธ ์ธ์ฆ ์ ๋ณด ์์ - Security Filter๊ฐ ์์ ํ ๋ก๊ทธ์ธ ์ธ์ฆ ์ ๋ณด๋ฅผ AuthenticationManager์๊ฒ ์ ๋ฌํด ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ์์
- AuthenticationManager๊ฐ Custom UserDetailsService(
MemberDetailsService
)์๊ฒ ์ฌ์ฉ์์ UserDetails ์กฐํ๋ฅผ ์์ - Custom UserDetailsService(
MemberDetailsService
)๊ฐ ์ฌ์ฉ์์ ํฌ๋ฆฌ๋ด์ ์ DB์์ ์กฐํํ ํ, AuthenticationManager์๊ฒ ์ฌ์ฉ์์ UserDetails๋ฅผ ์ ๋ฌ - AuthenticationManager๊ฐ ๋ก๊ทธ์ธ ์ธ์ฆ ์ ๋ณด์ UserDetails์ ์ ๋ณด๋ฅผ ๋น๊ตํด ์ธ์ฆ ์ฒ๋ฆฌ
- JWT ์์ฑ ํ, ํด๋ผ์ด์ธํธ์ ์๋ต์ผ๋ก ์ ๋ฌ
4๋ฒ๊ณผ 6๋ฒ์ Spring Security์ AuthenticationManager๊ฐ ๋์ ์ฒ๋ฆฌํด์ค
1๏ธโฃ Custom UserDetailsService ๊ตฌํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.springboot.auth.userDetails;
import com.springboot.auth.utils.JwtAuthorityUtils;
import com.springboot.exception.BusinessLogicException;
import com.springboot.exception.ExceptionCode;
import com.springboot.member.entity.Member;
import com.springboot.member.repository.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 java.util.Collection;
import java.util.Optional;
public class MemberDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;
private final JwtAuthorityUtils authorityUtils;
public MemberDetailsService(MemberRepository memberRepository, JwtAuthorityUtils authorityUtils) {
this.memberRepository = memberRepository;
this.authorityUtils = authorityUtils;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//DB์์ ์กฐํ
Optional<Member> optionalMember = memberRepository.findByEmail(username);
Member findMember = optionalMember.orElseThrow(()-> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
//Member๋ DB์์ ๊ฐ์ ธ์จ ๊ฒ์ด๋ฏ๋ก - ์ํธํ ๋์ด์๋ค.
return new MemberDetails(findMember);
}
//Member๋ฅผ ํ์ฅ
private final class MemberDetails extends Member implements UserDetails{
//MemberDetails๋ ์์ฑ์ -> ์ด๊ฑด ๋ฐ๋ก ์์ ๋ฐํ๋ ๊ฐ
// = DB์์ ๊ฐ์ ธ์จ ๊ฒ. = ์ํธํ ๋์ด์์.
MemberDetails(Member member){
setMemberId(member.getMemberId());
setEmail(member.getEmail());
setPassword(member.getPassword());
setRoles(member.getRoles());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorityUtils.createAuthority(this.getRoles());
}
@Override
public String getUsername() {
return this.getEmail();
}
@Override
public boolean isAccountNonExpired() {
return true; //false์์ ์์
}
@Override
public boolean isAccountNonLocked() {
return true; //false์์ ์์
}
@Override
public boolean isCredentialsNonExpired() {
return true; //false์์ ์์
}
@Override
public boolean isEnabled() {
return true; //false์์ ์์
}
}
}
2๏ธโฃ ๋ก๊ทธ์ธ ์ธ์ฆ ์ ๋ณด ์ญ์ง๋ ฌํ(Deserialization)๋ฅผ ์ํ LoginDTO ํด๋์ค ์์ฑ
ํด๋ผ์ด์ธํธ๊ฐ ์ ์กํ Username/Password ์ ๋ณด๋ฅผ Security Filter์์ ์ฌ์ฉํ ์ ์๋๋ก ์ญ์ง๋ ฌํ(Deserialization) ํ๊ธฐ ์ํ DTO ํด๋์ค์ด๋ค.
1
2
3
4
5
6
7
8
9
10
package com.springboot.auth.dto;
import lombok.Getter;
@Getter //dto์๋ Getter ํ์.
public class LoginDto {
private String username;
private String password;
}
3๏ธโฃ JWT๋ฅผ ์์ฑํ๋ JwtTokenizer ๊ตฌํ
-
JwtTokenizer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
package com.springboot.auth.jwt; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; public class JwtTokenizer { @Getter @Value("${jwt.key}") //yml์ ์ ์ฅ ํ์ private String secretKey; @Getter @Value("${jwt.access-token-expiration-minute}") //yml์ ์ ์ฅ ํ์ private int accessTokenExpirationMinutes; @Getter @Value("${jwt.refresh-token-expiration-minute}") //yml์ ์ ์ฅ ํ์ private int refreshTokenExpirationMinutes; }
- application.yml
-
${JWT_SECRET_KEY}๋ ๋จ์ํ ๋ฌธ์์ด์ด ์๋๋ผ OS์ ์์คํ ํ๊ฒฝ ๋ณ์์ ๊ฐ์ ์ฝ์ด์ค๋ ์ผ์ข ์ ํํ์
-
- ์์คํ
ํ๊ฒฝ๋ณ์ ํธ์ง (์์คํ
ํ๊ฒฝ๋ณ์ํธ์ง- ํ๊ฒฝ๋ณ์ - ์์คํ
๋ณ์ ์ถ๊ฐ)
-
JWT์ ์๋ช ์ ์ฌ์ฉ๋๋ Secret Key ์ ๋ณด๋ ๋ฏผ๊ฐํ(sensitive) ์ ๋ณด์ด๋ฏ๋ก ์์คํ ํ๊ฒฝ ๋ณ์์ ๋ณ์๋ก ๋ฑ๋ก
-
4๏ธโฃ ๋ก๊ทธ์ธ ์ธ์ฆ ์์ฒญ์ ์ฒ๋ฆฌํ๋ Custom Security Filter ๊ตฌํ
JwtAuthenticationFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//UsernamePasswordAuthenticationfilter๋ฅผ ๊ต์ฒดํ๋ ๊ฒ.
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
//ํผ ๋ก๊ทธ์ธ ๋ฐฉ์์์ ์ฌ์ฉํ๋ ๋ํดํธ Security Filter๋ก์จ,
//ํผ ๋ก๊ทธ์ธ์ด ์๋๋๋ผ๋ Username/Password ๊ธฐ๋ฐ์ ์ธ์ฆ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด
// UsernamePasswordAuthenticationFilter๋ฅผ ํ์ฅํด์ ๊ตฌํ
private final AuthenticationManager authenticationManager;
// ๋ก๊ทธ์ธ ์ธ์ฆ ์ ๋ณด(Username/Password)๋ฅผ ์ ๋ฌ๋ฐ์ UserDetailsService์ ์ธํฐ๋์
ํ ๋ค ์ธ์ฆ ์ฌ๋ถ๋ฅผ ํ๋จ
private final JwtTokenizer jwtTokenizer;
//ํด๋ผ์ด์ธํธ๊ฐ ์ธ์ฆ์ ์ฑ๊ณตํ ๊ฒฝ์ฐ, JWT๋ฅผ ์์ฑ ๋ฐ ๋ฐ๊ธ
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtTokenizer jwtTokenizer) {
this.authenticationManager = authenticationManager;
this.jwtTokenizer = jwtTokenizer;
}
@SneakyThrows //try-catch๋ฅผ ๋์ฒด
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
//ํด๋ผ์ด์ธํธ์์ ์ ์กํ Username๊ณผ Password๋ฅผ DTO ํด๋์ค๋ก ์ญ์ง๋ ฌํ(Deserialization) ํ๊ธฐ ์ํด ObjectMapper ์ธ์คํด์ค๋ฅผ ์์ฑ
ObjectMapper objectMapper = new ObjectMapper();
LoginDto loginDto = objectMapper.readValue(request.getInputStream(), LoginDto.class);
//ServletInputStream์ LoginDto ํด๋์ค์ ๊ฐ์ฒด๋ก ์ญ์ง๋ ฌํํ๋ค.
// token ๋ง๋๋ ์ด์ UsernamePasswordAuthenticationToken ์ ๋์ฒดํ๋ ๊ฒ, ๊ตฌํ์ฒด๋ ์ค์ ๋ก ๋ง๋ค์ด์ ธ์์
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());
return authenticationManager.authenticate(authenticationToken);
}
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authentication) throws ServletException, IOException {
Member member = (Member) authentication.getPrincipal();
//token ๋ง๋ค๊ธฐ
String accessToken= delegateAceessToken(member);
String refreshToken = delegateRefreshToken(member);
response.setHeader("Authorization", "Bearer " + accessToken);
response.setHeader("Refresh", refreshToken);
this.getSuccessHandler().onAuthenticationSuccess(request,response,authentication);
}
protected String delegateAceessToken(Member member){
Map<String,Object> claims = new HashMap<>();
claims.put("username",member.getEmail());
claims.put("roles", member.getRoles());
//token์์ ์ ๋ ๋น๋ฐ๋ฒํธ ๋ฃ์ง์์, playload ๋์ฝ๋ฉ์ ์์ฒญ์ฝ๋ค.
String subject = member.getEmail();
Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getAccessTokenExpirationMinutes());
String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());
String accessToken = jwtTokenizer.generateAccessToken(claims, subject, expiration, base64EncodedSecretKey);
return accessToken;
//๋ฏผ๊ฐํ ์ ๋ณด๋ ์ ๋ ํ๋ฌธํ ํ๋ฉด ์๋จ.
//์คํ๋ง์์ ์ธ์ฆ์ธ๊ฐ๋ ๋ฌด์กฐ๊ฑด security์ฌ์ฉ
}
protected String delegateRefreshToken(Member member) {
String subject = member.getEmail();
Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getRefreshTokenExpirationMinutes());
String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());
String refreshToken = jwtTokenizer.generateRefreshToken(subject, expiration, base64EncodedSecretKey);
return refreshToken;
}
//์คํจํ์๋ ์๋์ผ๋ก ๋ฑ๋ก ๋๋ค.
//์ฑ๊ณตํ์๋๋ ์ฑ๊ณตํ๊ณ ๋์ ์ํํ ์์
์ด ํ์ํ๊ธฐ ๋๋ฌธ์ ๋ช
์์ ์ผ๋ก ๋ฃ์ด์ฃผ์ด์ผ
}
5๏ธโฃ Custom Filter ์ถ๊ฐ๋ฅผ ์ํ SecurityConfiguration ์ค์ ์ถ๊ฐ
SecurityConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Configuration
public class SecurityConfiguration {
private final JwtTokenizer jwtTokenizer;
public SecurityConfiguration(JwtTokenizer jwtTokenizer) {
this.jwtTokenizer = jwtTokenizer;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
http
.headers().frameOptions().sameOrigin()
.and()
.csrf().disable() //๋ฐฐํฌํ ๋๋ง ๋ณ๊ฒฝํ๋ฉด ๋จ.
.cors(withDefaults())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable() //ํผ๋ก๊ทธ์ธ ์ฌ์ฉํ์ง ์๊ฒ๋ค.
.httpBasic().disable()
.apply(new CustomFilterConfigurer())
.and()
.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//Cors์ค์ ์ถ๊ฐ, corsConfigurationSource๋ฅผ ๋น์ผ๋ก ๋๋ก
@Bean
CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",configuration);
return source;
}
public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer,HttpSecurity>{
//AbstractHttpConfigurer๋ฅผ ์์ํด์ Custom Configurer๋ฅผ ๊ตฌํ
//AbstractHttpConfigurer๋ฅผ ์์ํ๋ ํ์
๊ณผ HttpSecurityBuilder๋ฅผ ์์ํ๋ ํ์
์ ์ ๋๋ฆญ ํ์
์ผ๋ก ์ง์ ํ ์ ์๋ค.
@Override //configure() ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํด์ Configuration์ ์ปค์คํฐ๋ง์ด์ง
public void configure(HttpSecurity builder){
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
//getSharedObject()๋ฅผ ํตํด์ Spring Security์ ์ค์ ์ ๊ตฌ์ฑํ๋ SecurityConfigurer ๊ฐ์ ๊ณต์ ๋๋ ๊ฐ์ฒด๋ฅผ ์ป์ ์ ์๋ค.
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
// JwtAuthenticationFilter์์ ์ฌ์ฉ๋๋ AuthenticationManager์ JwtTokenizer๋ฅผ DIํด์ค๋ค.
jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login"); //์์๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ก๊ทธ์ธ์ด ์ ์ฉ๋จ ("/login)
builder.addFilter(jwtAuthenticationFilter);
// addFilter() ๋ฉ์๋๋ฅผ ํตํด JwtAuthenticationFilter๋ฅผ Spring Security Filter Chain์ ์ถ๊ฐ
}
}
}
์จ์ ํ ์ดํดํ๊ธฐ ํ๋ค๊ธฐ ๋๋ฌธ์ ์ด๋ ๊ฒ ์ฐ๋ ๊ตฌ๋ ํ๊ณ ์ธ์ฐ๊ธฐโฆ.?
๋ก๊ทธ์ธ ์๋
-
ํ์๊ฐ์
-
๋ก๊ทธ์ธ
-
๋น๋ฐ๋ฒํธ ์๋ชป ์ ๋ ฅ์ ์๋ฌ ๋ฐ์
HttpStatus.UNAUTHORIZED(401)
์ํ ์ฝ๋๋ ์ธ์ฆ์ ์คํจํ ๊ฒฝ์ฐ ์ ๋ฌํ ์ ์๋ HTTP statusโ ์ถ๊ฐ ๊ฐ์ ํ์
๋ก๊ทธ์ธ ์ธ์ฆ ์ฑ๊ณต ๋ฐ ์คํจ์ ๋ฐ๋ฅธ ์ถ๊ฐ ์ฒ๋ฆฌ
1๏ธโฃ AuthenticationSuccessHandler ๊ตฌํ
1
2
3
4
5
6
7
8
@Slf4j
public class MemberAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
log.info("# Authenticated successfully!");
}
}
2๏ธโฃ AuthenticationFailureHandler ๊ตฌํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//component ์๋๊ฑด ์คํ๋ง ์ปจํ
์ด๋๊ฐ ์ก์์ฃผ๋ ๊ฒ์ด ์๋๊ธฐ ๋๋ฌธ์,
//์ธ์ฆ์ ๋ด๋นํ๋ JwtAuthenticationFilter์ ์ด ๊ฐ์ฒด๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํจ.
@Slf4j
public class MemberAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//๋ณดํต ๋ก๊น
๊ธฐ๋ฅ ํฌํจ
log.info("Authenticated failed");
log.error("Authenticated failed", exception.getMessage());
sendErrorResponse(response);
}
private void sendErrorResponse(HttpServletResponse response) throws IOException {
Gson gson = new Gson();
ErrorResponse errorResponse = ErrorResponse.of(HttpStatus.UNAUTHORIZED);
//HttpStatus.UNAUTHORIZED ์ํ ์ฝ๋๋ฅผ ์ ๋ฌ
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
// Content Type์ด โapplication/jsonโ์ด๋ผ๋ ๊ฒ์ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ ค์ค ์ ์๋๋ก MediaType.APPLICATION_JSON_VALUE๋ฅผ HTTP Header์ ์ถ๊ฐ
response.setStatus(HttpStatus.UNAUTHORIZED.value());
// response์ status๊ฐ 401 ์์ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ ค์ค ์ ์๋๋ก HttpStatus.UNAUTHORIZED.value()์ HTTP Header์ ์ถ๊ฐ
response.getWriter().write(gson.toJson(errorResponse,ErrorResponse.class));
//Gson์ ์ด์ฉํด ErrorResponse ๊ฐ์ฒด๋ฅผ JSON ํฌ๋งท ๋ฌธ์์ด๋ก ๋ณํ ํ,
//์ถ๋ ฅ ์คํธ๋ฆผ์ ์์ฑ
}
}
3๏ธโฃ AuthenticationSuccessHandler์ AuthenticationFailureHandler ์ถ๊ฐ
AuthenticationFailureHandler ์ธํฐํ์ด์ค์ ๊ตฌํ ํด๋์ค๋ฅผ JwtAuthenticationFilter
์ ๋ฑ๋กํ๋ฉด ๋ก๊ทธ์ธ ์ธ์ฆ ์, ๋ ํธ๋ค๋ฌ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
public class SecurityConfiguration {
private final JwtTokenizer jwtTokenizer;
public SecurityConfiguration(JwtTokenizer jwtTokenizer) {
this.jwtTokenizer = jwtTokenizer;
}
...
public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer, HttpSecurity> {
@Override
public void configure(HttpSecurity builder) throws Exception {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login");
jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler()); // (3) ์ถ๊ฐ
jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler()); // (4) ์ถ๊ฐ
builder.addFilter(jwtAuthenticationFilter);
}
}
}
AuthenticationSuccessHandler์ AuthenticationFailureHandler ์ธํฐํ์ด์ค์ ๊ตฌํ ํด๋์ค๊ฐ ๋ค๋ฅธ Security Filter์์ ์ฌ์ฉ์ด ๋๋ค๋ฉด ApplicationContext์ Bean์ผ๋ก ๋ฑ๋กํด์ DI ๋ฐ๋ ๊ฒ ๋ง๋ค.
๐ก ํ์ง๋ง ์ผ๋ฐ์ ์ผ๋ก ์ธ์ฆ์ ์ํ Security Filter๋ง๋ค AuthenticationSuccessHandler์ AuthenticationFailureHandler์ ๊ตฌํ ํด๋์ค๋ฅผ ๊ฐ๊ฐ ์์ฑํ ๊ฒ์ด๋ฏ๋ก new
ํค์๋๋ฅผ ์ฌ์ฉํด์ ๊ฐ์ฒด๋ฅผ ์์ฑํด๋ ๋ฌด๋ฐฉ
4๏ธโฃ AuthenticationSuccessHandler ํธ์ถ
JwtAuthenticationFilter(AuthenticationSuccessHandler ํธ์ถ ์ฝ๋ ์ถ๊ฐ)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
...
...
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws ServletException, IOException {
Member member = (Member) authResult.getPrincipal();
String accessToken = delegateAccessToken(member);
String refreshToken = delegateRefreshToken(member);
response.setHeader("Authorization", "Bearer " + accessToken);
response.setHeader("Refresh", refreshToken);
this.getSuccessHandler().onAuthenticationSuccess(request, response, authResult); // (1) ์ถ๊ฐ
}
...
...
}
์๊ฒฉ์ฆ๋ช ๋ฐ ๊ฒ์ฆ ๊ตฌํ
ํด๋ผ์ด์ธํธ ์ธก์์ JWT๋ฅผ ์ด์ฉํด ์๊ฒฉ ์ฆ๋ช ์ด ํ์ํ ๋ฆฌ์์ค์ ๋ํ request ์ ์ก ์, request header๋ฅผ ํตํด ์ ๋ฌ๋ฐ์ JWT๋ฅผ ์๋ฒ ์ธก์์ ๊ฒ์ฆํ๋ ๊ธฐ๋ฅ
1๏ธโฃ JWT ๊ฒ์ฆ ํํฐ ๊ตฌํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.springboot.auth.filter;
import com.springboot.auth.jwt.JwtTokenizer;
import com.springboot.auth.utils.JwtAuthorityUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
// OncePerRequestFilter๋ฅผ ํ์ฅํด์ request ๋น ํ ๋ฒ๋ง ์คํ๋๋ Security Filter๋ฅผ ๊ตฌํํ ์ ์๋ค.
//๊ตฌํ์ฒด๋ผ์ ์์๋ฐ์์ ๊ตฌํํ ๊ฒ. -ํ ํฐ๊ณผ ๊ด๋ จ๋ ์๋น์ค, ๊ถํ์ ๊ฐ์ ธ์์ผ ํจ
public class JwtVerifiedFilter extends OncePerRequestFilter {
private final JwtTokenizer jwtTokenizer;
private final JwtAuthorityUtils authorityUtils;
public JwtVerifiedFilter(JwtTokenizer jwtTokenizer, JwtAuthorityUtils authorityUtils) {
this.jwtTokenizer = jwtTokenizer;
this.authorityUtils = authorityUtils;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
Map<String,Object> claims = verifyJws(request);
//SecurityContext์์ ์ ์ฅํด์ฃผ์ด์ผ ํจ.
setAuthenticationToContext(claims);
// Authentication ๊ฐ์ฒด๋ฅผ SecurityContext์ ์ ์ฅ
filterChain.doFilter(request,response);
// ๋ค์(Next) Security Filter๋ฅผ ํธ์ถ
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request){
// ํน์ ์กฐ๊ฑด์ ๋ถํฉํ๋ฉด(true์ด๋ฉด) ํด๋น Filter์ ๋์์ ์ํํ์ง ์๊ณ
// ๋ค์ Filter๋ก ๊ฑด๋ ๋ฐ๋๋ก ใฑํด์ค๋ค.
String authorization = request.getHeader("Athorization");
//Authorization header์ ๊ฐ์ ์ป์ ํ
return authorization == null || !authorization.startsWith("Bearer");
//Authorization header์ ๊ฐ์ด null์ด๊ฑฐ๋
//Authorization header์ ๊ฐ์ด โBearerโ๋ก ์์ํ์ง ์๋๋ค๋ฉด ํด๋น Filter์ ๋์์ ์ํํ์ง ์๋๋ก ์ ์
// JWT๊ฐ Authorization header์ ํฌํจ๋์ง ์์๋ค๋ฉด
// JWT ์๊ฒฉ์ฆ๋ช
์ด ํ์ํ์ง ์์ ๋ฆฌ์์ค์ ๋ํ ์์ฒญ์ด๋ผ๊ณ ํ๋จํ๊ณ
// ๋ค์(Next) Filter๋ก ์ฒ๋ฆฌ๋ฅผ ๋๊ธฐ๋ ๊ฒ
}
private Map<String, Object> verifyJws(HttpServletRequest request){
String jws = request.getHeader("Authorization").replace("Bearer","");
String bas64EncodedSecretKey =jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());
// JWT ์๋ช
(Signature)์ ๊ฒ์ฆํ๊ธฐ ์ํ Secret Key๋ฅผ ์ป๋๋ค.
Map<String,Object>claims = jwtTokenizer.getClaims(jws,bas64EncodedSecretKey).getBody();
// JWT์์ Claims๋ฅผ ํ์ฑ -> getClaims ์์์ ๊ฒ์ฆ๊น์ง ์ด๋ฃจ์ด์ง๋ ๊ฒ.
return claims;
}
private void setAuthenticationToContext(Map<String,Object>claims){
String username = (String) claims.get("username");
List<GrantedAuthority> authorities = authorityUtils.createAuthorities((List)claims.get("roles"));
Authentication authentication = new UsernamePasswordAuthenticationToken(username,null,authorities);
//password๋ ๋ฃ์ง ์๋๋ค.์ค์!!
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
- shouldNotFilter()
- JWT๊ฐ Authorization header์ ํฌํจ๋์ง ์์๋ค๋ฉด JWT ์๊ฒฉ์ฆ๋ช ์ด ํ์ํ์ง ์์ ๋ฆฌ์์ค์ ๋ํ ์์ฒญ์ด๋ผ๊ณ ํ๋จํ๊ณ ๋ค์(Next) Filter๋ก ์ฒ๋ฆฌ๋ฅผ ๋๊ธฐ๋ ๊ฒ
- ๋ง์ผ JWT ์๊ฒฉ ์ฆ๋ช ์ด ํ์ํ ๋ฆฌ์์ค ์์ฒญ์ธ๋ฐ ์ค์๋ก JWT๋ฅผ ํฌํจํ์ง ์์๋ค ํ๋๋ผ๋ ์ด ๊ฒฝ์ฐ์๋ Authentication์ด ์ ์์ ์ผ๋ก SecurityContext์ ์ ์ฅ๋์ง ์์ ์ํ์ด๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ Security Filter๋ฅผ ๊ฑฐ์ณ ๊ฒฐ๊ตญ Exception์ ๋์ง๊ฒ ๋ ๊ฒ
2๏ธโฃ SecurityConfiguration ์ค์ ์ ๋ฐ์ดํธ
- ์ธ์ ์ ์ฑ ์ค์ ์ถ๊ฐ
- JwtVerificationFilter ์ถ๊ฐ
SecurityConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Configuration
public class SecurityConfiguration {
private final JwtTokenizer jwtTokenizer;
private final JwtAuthorityUtils authorityUtils;
public SecurityConfiguration(JwtTokenizer jwtTokenizer, JwtAuthorityUtils authorityUtils) {
this.jwtTokenizer = jwtTokenizer;
this.authorityUtils = authorityUtils;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
http
.headers().frameOptions().sameOrigin()
.and()
.csrf().disable() //๋ฐฐํฌํ ๋๋ง ๋ณ๊ฒฝํ๋ฉด ๋จ.
.cors(withDefaults())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// ์ธ์
์ ์์ฑํ์ง ์๋๋ก ์ค
.and()
.formLogin().disable() //ํผ๋ก๊ทธ์ธ ์ฌ์ฉํ์ง ์๊ฒ๋ค.
.httpBasic().disable()
.apply(new CustomFilterConfigurer())
.and()
.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
//์ธ์ฆ์ธ๊ฐ ํ ๋ณ๊ฒฝ์์
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//Cors์ค์ ์ถ๊ฐ, corsConfigurationSource๋ฅผ ๋น์ผ๋ก ๋๋ก
@Bean
CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",configuration);
return source;
}
public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer,HttpSecurity>{
@Override
public void configure(HttpSecurity builder){
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login"); //์์๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ก๊ทธ์ธ์ด ์ ์ฉ๋จ ("/login)
jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());
jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());
JwtVerifiedFilter jwtVerifiedFilter = new JwtVerifiedFilter(jwtTokenizer,authorityUtils);
//JwtVerificationFilter์ ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ฉด์
//JwtVerificationFilter์์ ์ฌ์ฉ๋๋ ๊ฐ์ฒด๋ค์ ์์ฑ์๋ก DI๋ฅผ ํด์ค๋ค.
builder.addFilter(jwtAuthenticationFilter)
.addFilterAfter(jwtVerifiedFilter,JwtVerifiedFilter.class);
//jwtAuthenticationFilter์์ ๋ก๊ทธ์ธ ์ธ์ฆ์ ์ฑ๊ณตํ ํ ๋ฐ๊ธ๋ฐ์ JWT๊ฐ ํด๋ผ์ด์ธํธ์ request header(Authorization ํค๋)์ ํฌํจ๋์ด ์์ ๊ฒฝ์ฐ์๋ง ๋์
}
}
}
- SessionCreationPolicy์ ์ค์ ๊ฐ
- SessionCreationPolicy.ALWAYS : ํญ์ ์ธ์ ์ ์์ฑํฉ๋๋ค.
- SessionCreationPolicy.NEVER : ์ธ์ ์ ์์ฑํ์ง ์์ง๋ง ๋ง์ฝ์ ์ด๋ฏธ ์์ฑ๋ ์ธ์ ์ด ์๋ค๋ฉด ์ฌ์ฉํ๋ค.
- SessionCreationPolicy.IF_REQUIRED :ํ์ํ ๊ฒฝ์ฐ์๋ง ์ธ์ ์ ์์ฑํ๋ค.
- SessionCreationPolicy.STATELESS : ์ธ์ ์ ์์ฑํ์ง ์์ผ๋ฉฐ, SecurityContext ์ ๋ณด๋ฅผ ์ป๊ธฐ ์ํด ๊ฒฐ์ฝ ์ธ์ ์ ์ฌ์ฉํ์ง ์๋๋ค.
์๋ฒ ์ธก ๋ฆฌ์์ค์ ์ญํ (Role) ๊ธฐ๋ฐ ๊ถํ ์ ์ฉ
SecurityConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.springboot.config;
...
@Configuration
public class SecurityConfiguration {
private final JwtTokenizer jwtTokenizer;
private final JwtAuthorityUtils authorityUtils;
public SecurityConfiguration(JwtTokenizer jwtTokenizer, JwtAuthorityUtils authorityUtils) {
this.jwtTokenizer = jwtTokenizer;
this.authorityUtils = authorityUtils;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
http
.headers().frameOptions().sameOrigin()
.and()
.csrf().disable() //๋ฐฐํฌํ ๋๋ง ๋ณ๊ฒฝํ๋ฉด ๋จ.
.cors(withDefaults())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable() //ํผ๋ก๊ทธ์ธ ์ฌ์ฉํ์ง ์๊ฒ๋ค.
.httpBasic().disable()
.apply(new CustomFilterConfigurer())
.and()
// .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll())
// ์ธ์ฆ์ธ๊ฐ ๊ตฌํ ํ ์ถ๊ฐ
.authorizeHttpRequests(authorize -> authorize
.antMatchers(HttpMethod.POST, "/*/members").permitAll() //post์ ํด๋นํ๋ค๋ฉด ์ ๊ทผ ํ์ฉ
.antMatchers(HttpMethod.PATCH, "/*/members/**").hasRole("USER")
.antMatchers(HttpMethod.GET, "/*/members").hasRole("ADMIN")
.antMatchers(HttpMethod.GET, "/*/members/**").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.DELETE, "/*/members/**").hasRole("USER")
.anyRequest().permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//Cors์ค์ ์ถ๊ฐ, corsConfigurationSource๋ฅผ ๋น์ผ๋ก ๋๋ก
@Bean
CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",configuration);
return source;
}
public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer,HttpSecurity>{
@Override
public void configure(HttpSecurity builder){
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login"); //์์๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ก๊ทธ์ธ์ด ์ ์ฉ๋จ ("/login)
jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());
jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());
JwtVerifiedFilter jwtVerifiedFilter = new JwtVerifiedFilter(jwtTokenizer,authorityUtils);
builder.addFilter(jwtAuthenticationFilter)
.addFilterAfter(jwtVerifiedFilter,JwtAuthenticationFilter.class);
}
}
}
.authorizeHttpRequests(authorize -> authorize ~
: ์ ๊ทผ ๊ถํ ๋ถ์ฌ ์ค์
- .antMatchers(HttpMethod.POST, โ/*/membersโ).permitAll() : HttpMethod๊ฐ POST์ ํด๋นํ๊ณ URL์ด */members๋ก ํด๋น๋๋ค๋ฉด ๋๊ตฌ๋ ์ ๊ทผ ํ์ฉ
- .antMatchers(HttpMethod.PATCH, โ/*/members/**โ).hasRole(โUSERโ) : PATCH ์์ฒญ ์ members์ ํ์ URL์ด ์ด๋ค URL์ด ์ค๋๋ผ๋ USER ๊ถํ ๊ฐ์ก์ผ๋ฉด ์ ๊ทผ ํ์ฉ
- get ์์ฒญ์ ์ผ๋ฐ ์ฌ์ฉ์(USER)์ ๊ด๋ฆฌ์(ADMIN) ๊ถํ์ ๊ฐ์ง ์ฌ์ฉ์ ๋ชจ๋ ์ ๊ทผ์ด ๊ฐ๋ฅ
- delete ์์ฒญ์ ํด๋น ์ฌ์ฉ์๊ฐ ํํด ๊ฐ์ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์์ด์ผ ํ๋ฏ๋ก ์ผ๋ฐ ์ฌ์ฉ์(USER) ๊ถํ๋ง ๊ฐ์ง ์ฌ์ฉ์๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ๋๋ก ํ์ฉ
Leave a comment