- ๊ณต์ ์ฌ์ดํธ https://jwt.io/
- ์ฐธ๊ณ https://mangkyu.tistory.com/56
- JWT ๊ตฌํ ๋ฐ๋ก๊ฐ๊ธฐ โผ
- JWT(Json Web Token) : Json ํฌ๋งท์ ์ด์ฉํ์ฌ ์ฌ์ฉ์์ ๋ํ ์์ฑ์ ์ ์ฅํ๋ Claim ๊ธฐ๋ฐ์ Web Token
- ํ ํฐ ์์ฒด๋ฅผ ์ ๋ณด๋ก ์ฌ์ฉํ๋ Self-Contained ๋ฐฉ์์ผ๋ก ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ์ ๋ฌ
- ์ฃผ๋ก ํ์ ์ธ์ฆ์ด๋ ์ ๋ณด ์ ๋ฌ์ ์ฌ์ฉ๋จ
- JWT๋ ์๋์ ๋ก์ง์ ๋ฐ๋ผ์ ์ฒ๋ฆฌ๋จ
- ์ ํ๋ฆฌ์ผ์ด์
์ด ์คํ๋ ๋, JWT๋ฅผ static ๋ณ์์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅํ๊ฒ ๋จ
- static ๋ณ์์ ์ ์ฅ๋๋ ์ด์ ๋ HTTP ํต์ ์ ํ ๋๋ง๋ค JWT๋ฅผ HTTP ํค๋์ ๋ด์์ ๋ณด๋ด์ผ ํ๋๋ฐ, ์ด๋ฅผ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์์ ๊ณ์ ๋ถ๋ฌ์ค๋ฉด ์ค๋ฒํค๋๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ
- ํด๋ผ์ด์ธํธ์์ JWT๋ฅผ ํฌํจํด ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๋ ํ๊ฐ๋ JWT์ธ์ง๋ฅผ ๊ฒ์ฌ
- ๋ํ ๋ก๊ทธ์์์ ํ ๊ฒฝ์ฐ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅ๋ JWT ๋ฐ์ดํฐ๋ฅผ ์ ๊ฑฐ
- (์ค์ ์๋น์ค์ ๊ฒฝ์ฐ์๋ ๋ก๊ทธ์์ ์, ์ฌ์ฉํ๋ ํ ํฐ์ blacklist๋ผ๋ DB ํ
์ด๋ธ์ ๋ฃ์ด ํด๋น ํ ํฐ์ ์ ๊ทผ์ ๋ง๋ ์์
์ ํด์ฃผ์ด์ผ ํจ)
1-1. ์๋ฒ(์ธ์
) ๊ธฐ๋ฐ์ ์ธ์ฆ ์์คํ
- ๊ธฐ์กด์ ์ธ์ฆ ์์คํ
์ ์๋ฒ ๊ธฐ๋ฐ์ ์ธ์ฆ ๋ฐฉ์์ผ๋ก, ์๋ฒ ์ธก์์ ์ฌ์ฉ์๋ค์ ์ ๋ณด๋ฅผ ๊ธฐ์ตํ๊ณ ์์ด์ผ ํจ
- ์ฌ์ฉ์๋ค์ ์ ๋ณด๋ฅผ ๊ธฐ์ตํ๊ธฐ ์ํด์๋ ์ธ์
์ ์ ์งํด์ผ ํจ > ๋ฉ๋ชจ๋ฆฌ๋ ๋์คํฌ ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฑ์ ํตํด ๊ด๋ฆฌ
- ์๋ฒ ๊ธฐ๋ฐ์ ์ธ์ฆ ์์คํ
์ ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์์ฒญ์ ๋ฐ์ผ๋ฉด, ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ๊ณ์ํด์ ์ ์งํ๊ณ ์ด ์ ๋ณด๋ฅผ ์๋น์ค์ ์ด์ฉ > ์ด๋ฌํ ์๋ฒ๋ฅผ Sateful ์๋ฒ๋ผ๊ณ ํจ
- ex. ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ํ๋ฉด, ์ธ์
์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํด๋๊ณ ์๋น์ค๋ฅผ ์ ๊ณตํ ๋ ์ฌ์ฉ
1-1-1. ์๋ฒ ๊ธฐ๋ฐ ์ธ์ฆ ์์คํ
์ ๋ฌธ์ ์
- ์ธ์
- ์ฌ์ฉ์๊ฐ ์ธ์ฆ์ ํ ๋, ์๋ฒ๋ ์ด๋ฌํ ์ ๋ณด๋ฅผ ์ ์ฅํด์ผ ํ๊ณ ์ด๋ฅผ ์ธ์
(Session)์ด๋ผ๊ณ ํจ
- ๋๋ถ๋ถ์ ๊ฒฝ์ฐ์๋ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ๋๋ฐ, ๋ก๊ทธ์ธ ์ค์ธ ์ฌ์ฉ์๊ฐ ๋์ด๋ ๊ฒฝ์ฐ์๋ ์๋ฒ์ RAM์ ๋ถํ๊ฐ ๊ฑธ๋ฆฌ๊ฒ ๋จ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ์ ํ๊ธฐ๋ ํ๋, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฌด๋ฆฌ๋ฅผ ์ค ์ ์์
- ํ์ฅ์ฑ
- ์ฌ์ฉ์๊ฐ ๋์ด๋๊ฒ ๋๋ฉด ๋ ๋ง์ ํธ๋ํฝ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ฌ๋ฌ ํ๋ก์ธ์ค๋ฅผ ๋๋ฆฌ๊ฑฐ๋ ์ปดํจํฐ๋ฅผ ์ถ๊ฐํ๋ ๋ฑ ์๋ฒ๋ฅผ ํ์ฅํด์ผ ํจ
- ์ธ์
์ ์ฌ์ฉํ๋ค๋ฉด ์ธ์
์ ๋ถ์ฐ์ํค๋ ์์คํ
์ ์ค๊ณํด์ผ ํจ
- CORS(Cross-Origin Resource Sharing)
- ์น ์ดํ๋ฆฌ์ผ์ด์
์์ ์ธ์
์ ๊ด๋ฆฌํ ๋ ์์ฃผ ์ฌ์ฉ๋๋ ์ฟ ํค๋ ๋จ์ผ ๋๋ฉ์ธ ๋ฐ ์๋ธ ๋๋ฉ์ธ์์๋ง ์๋ํ๋๋ก ์ค๊ณ๋์ด ์์ > ์ฟ ํค๋ฅผ ์ฌ๋ฌ ๋๋ฉ์ธ์์ ๊ด๋ฆฌํ๋ ๊ฒ์ ๋ฒ๊ฑฐ๋ก์
1-2. ํ ํฐ ๊ธฐ๋ฐ์ ์ธ์ฆ ์์คํ
- ์ธ์ฆ๋ฐ์ ์ฌ์ฉ์๋ค์๊ฒ ํ ํฐ์ ๋ฐ๊ธํ๊ณ , ์๋ฒ์ ์์ฒญ์ ํ ๋ ํค๋์ ํ ํฐ์ ํจ๊ป ๋ณด๋ด๋๋ก ํ์ฌ ์ ํจ์ฑ ๊ฒ์ฌ ์งํ
- ์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์๋ฒ๋ ์ธ์
์ ์ ์งํ์ง ์๊ณ ํด๋ผ์ด์ธํธ ์ธก์์ ๋ค์ด์ค๋ ์์ฒญ๋ง์ผ๋ก ์์
์ ์ฒ๋ฆฌ
- ์๋ฒ ๊ธฐ๋ฐ์ ์ธ์ฆ ์์คํ
๊ณผ ๋ฌ๋ฆฌ ์ํ๋ฅผ ์ ์งํ์ง ์์ผ๋ฏ๋ก Statelessํ ๊ตฌ์กฐ
- ์ด๋ฌํ ์ธ์ฆ ๋ฐฉ์์ ํตํด ์๋ง์ ๋ฌธ์ ์ ํด๊ฒฐ ๊ฐ๋ฅ (ex. ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ด ๋์ด์๋์ง ์๋์ด์๋์ง ์ ๊ฒฝ์ฐ์ง ์๊ณ ์์ฝ๊ฒ ์์คํ
์ ํ์ฅํ ์ ์์)
1-2-1. ํ ํฐ ๊ธฐ๋ฐ์ ์ธ์ฆ ์์คํ
์ ์๋ ๊ณผ์
- ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ก ์ฌ์ฉ์ ๋ก๊ทธ์ธ
- ์๋ฒ ์ธก์์ ํด๋น ์ ๋ณด๋ฅผ ๊ฒ์ฆ
- ์ ๋ณด๊ฐ ์ ํํ๋ค๋ฉด ์๋ฒ ์ธก์์ ์ฌ์ฉ์์๊ฒ Signed ํ ํฐ ๋ฐ๊ธ (Signed๋ ํด๋น ํ ํฐ์ด ์๋ฒ์์ ์ ์์ ์ผ๋ก ๋ฐ๊ธ๋ ํ ํฐ์์ ์ฆ๋ช
ํ๋ Signature๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ ๊ฒ)
- ํด๋ผ์ด์ธํธ ์ธก์์ ์ ๋ฌ๋ฐ์ ํ ํฐ์ ์ ์ฅํด๋๊ณ , ์๋ฒ์ ์์ฒญ์ ํ ๋๋ง๋ค ํด๋น ํ ํฐ์ ์๋ฒ์ ํจ๊ป ์ ๋ฌ (์ด๋ Http ์์ฒญ ํค๋์ ํ ํฐ์ ํฌํจ์ํด)
- ์๋ฒ๋ ํ ํฐ์ ๊ฒ์ฆ, ์์ฒญ์ ์๋ต
1-2-2. ํ ํฐ ๊ธฐ๋ฐ์ ์ธ์ฆ ์์คํ
์ ์ฅ์
- ๋ฌด์ํ์ฑ(Stateless) & ํ์ฅ์ฑ(Scalability)
- ํ ํฐ์ ํด๋ผ์ด์ธํธ ์ธก์ ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ ์๋ฒ๋ ์์ ํ Stateless
- ํด๋ผ์ด์ธํธ์ ์๋ฒ์ ์ฐ๊ฒฐ๊ณ ๋ฆฌ๊ฐ ์๊ธฐ ๋๋ฌธ์ ํ์ฅํ๊ธฐ์ ๋งค์ฐ ์ ํฉ
- ๋ง์ฝ ์ฌ์ฉ์ ์ ๋ณด๊ฐ ์๋ฒ ์ธก ์ธ์
์ ์ ์ฅ๋ ๊ฒฝ์ฐ์ ์๋ฒ๋ฅผ ํ์ฅํ์ฌ ๋ถ์ฐ์ฒ๋ฆฌ ํ๋ค๋ฉด, ํด๋น ์ฌ์ฉ์๋ ์ฒ์ ๋ก๊ทธ์ธ ํ์๋ ์๋ฒ์๋ง ์์ฒญ์ ๋ฐ๋๋ก ์ค์ ์ ํด์ฃผ์ด์ผ ํ
- ํ ํฐ์ ์ฌ์ฉํ๋ค๋ฉด ์ด๋ ํ ์๋ฒ๋ก ์์ฒญ์ด ์๋ ์๊ด์ด ์์
- ๋ณด์์ฑ
- ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ผ ๋ ์ฟ ํค๋ฅผ ์ ๋ฌํ์ง ์๊ฒ ๋๋ฏ๋ก, ์ฟ ํค ์ฌ์ฉ์ ์ํ ์ทจ์ฝ์ ์ด ์ฌ๋ผ์ง
- ํ ํฐ ํ๊ฒฝ์ ์ทจ์ฝ์ ์ด ์กด์ฌํ ์ ์์ผ๋ฏ๋ก ์ด์ ๋๋นํด์ผ ํจ
- ํ์ฅ์ฑ(Extensibility)
- ์์คํ
์ ํ์ฅ์ฑ์ ์๋ฏธํ๋ Scalability์ ๋ฌ๋ฆฌ Extensibility๋ ๋ก๊ทธ์ธ ์ ๋ณด๊ฐ ์ฌ์ฉ๋๋ ๋ถ์ผ์ ํ์ ์ ์๋ฏธ
- ํ ํฐ ๊ธฐ๋ฐ์ ์ธ์ฆ ์์คํ
์์๋ ํ ํฐ์ ์ ํ์ ์ธ ๊ถํ๋ง ๋ถ์ฌํ์ฌ ๋ฐ๊ธํ ์ ์์
- OAuth์ ๊ฒฝ์ฐ Facebook, Google ๋ฑ๊ณผ ๊ฐ์ ์์
๊ณ์ ์ ์ด์ฉํ์ฌ ๋ค๋ฅธ ์น์๋น์ค์์๋ ๋ก๊ทธ์ธ์ ํ ์ ์์
- ์ฌ๋ฌ ํ๋ซํผ ๋ฐ ๋๋ฉ์ธ
- ์๋ฒ ๊ธฐ๋ฐ ์ธ์ฆ ์์คํ
์ ๋ฌธ์ ์ ์ค ํ๋์ธ CORS๋ฅผ ํด๊ฒฐํ ์ ์์
- ์ ํ๋ฆฌ์ผ์ด์
๊ณผ ์๋น์ค์ ๊ท๋ชจ๊ฐ ์ปค์ง๋ฉด ์ฌ๋ฌ ๋๋ฐ์ด์ค๋ฅผ ํธํ์ํค๊ณ ๋ ๋ง์ ์ข
๋ฅ์ ์๋น์ค๋ฅผ ์ ๊ณตํ๊ฒ ๋จ
- ํ ํฐ์ ์ฌ์ฉํ๋ค๋ฉด ์ด๋ค ๋๋ฐ์ด์ค, ์ด๋ค ๋๋ฉ์ธ์์๋ ํ ํฐ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ ํ์ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์์
- ์ด๋ฐ ๊ตฌ์กฐ๋ฅผ ํตํด assests ํ์ผ(Image, html, css, js ๋ฑ)์ ๋ชจ๋ CDN์์ ์ ๊ณตํ๊ณ , ์๋ฒ ์ธก์์๋ API๋ง ๋ค๋ฃจ๋๋ก ์ค๊ณ ๊ฐ๋ฅ
- Header, Payload, Signature์ ์ธ ๋ถ๋ถ
- Json ํํ์ธ ๊ฐ ๋ถ๋ถ์ Base64๋ก ์ธ์ฝ๋ฉ ๋์ด ํํ๋จ
- ๊ฐ๊ฐ์ ๋ถ๋ถ์ ์ด์ด ์ฃผ๊ธฐ ์ํด . ๊ตฌ๋ถ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ๋ถ
- ์ถ๊ฐ๋ก Base64๋ ์ํธํ๋ ๋ฌธ์์ด์ด ์๋๊ณ , ๊ฐ์ ๋ฌธ์์ด์ ๋ํด ํญ์ ๊ฐ์ ์ธ์ฝ๋ฉ ๋ฌธ์์ด์ ๋ฐํ
2-1. Header(ํค๋)
- ํ ํฐ์ ํค๋๋ typ๊ณผ alg ๋ ๊ฐ์ง ์ ๋ณด๋ก ๊ตฌ์ฑ
- alg๋ ํค๋(Header)๋ฅผ ์ํธํ ํ๋ ๊ฒ์ด ์๋๊ณ , Signature๋ฅผ ํด์ฑํ๊ธฐ ์ํ ์๊ณ ๋ฆฌ์ฆ์ ์ง์ ํ๋ ๊ฒ
- typ : ํ ํฐ์ ํ์
์ ์ง์ ex) JWT
- alg : ์๊ณ ๋ฆฌ์ฆ ๋ฐฉ์์ ์ง์ ํ๋ฉฐ, ์๋ช
(Signature) ๋ฐ ํ ํฐ ๊ฒ์ฆ์ ์ฌ์ฉ ex) HS256(SHA256) ๋๋ RSA
{ "alg": "HS256", "typ": JWT }
2-2. PayLoad(ํ์ด๋ก๋)
- ํ ํฐ์ ํ์ด๋ก๋์๋ ํ ํฐ์์ ์ฌ์ฉํ ์ ๋ณด์ ์กฐ๊ฐ๋ค์ธ ํด๋ ์(Claim)์ด ๋ด๊ฒจ ์์
- ํด๋ ์์ ์ด 3๊ฐ์ง๋ก ๋๋์ด์ง๋ฉฐ, Json(Key/Value) ํํ๋ก ๋ค์์ ์ ๋ณด๋ฅผ ๋ฃ์ ์ ์์
2-2-1. ๋ฑ๋ก๋ ํด๋ ์(Registered Claim)
- ๋ฑ๋ก๋ ํด๋ ์์ ํ ํฐ ์ ๋ณด๋ฅผ ํํํ๊ธฐ ์ํด ์ด๋ฏธ ์ ํด์ง ์ข
๋ฅ์ ๋ฐ์ดํฐ๋ค๋ก, ๋ชจ๋ ์ ํ์ ์ผ๋ก ์์ฑ์ด ๊ฐ๋ฅํ๋ฉฐ ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅ
- ๋ํ JWT๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ํ๊ธฐ ์ํด key๋ ๋ชจ๋ ๊ธธ์ด 3์ String
- ์ฌ๊ธฐ์ subject๋ก๋ uniqueํ ๊ฐ์ ์ฌ์ฉํ๋๋ฐ, ์ฌ์ฉ์ ์ด๋ฉ์ผ์ ์ฃผ๋ก ์ฌ์ฉ
- iss : ํ ํฐ ๋ฐ๊ธ์(issuer)
- sub : ํ ํฐ ์ ๋ชฉ(subject)
- aud : ํ ํฐ ๋์์(audience)
- exp : ํ ํฐ ๋ง๋ฃ ์๊ฐ(expiration), NumericDate ํ์์ผ๋ก ๋์ด ์์ด์ผ ํจ ex) 1480849147370
- nbf : ํ ํฐ ํ์ฑ ๋ ์ง(not before), ์ด ๋ ์ด ์ง๋๊ธฐ ์ ์ ํ ํฐ์ ํ์ฑํ๋์ง ์์
- iat : ํ ํฐ ๋ฐ๊ธ ์๊ฐ(issued at), ํ ํฐ ๋ฐ๊ธ ์ดํ์ ๊ฒฝ๊ณผ ์๊ฐ์ ์ ์ ์์
- jti : JWT ํ ํฐ ์๋ณ์(JWT ID), ์ค๋ณต ๋ฐฉ์ง๋ฅผ ์ํด ์ฌ์ฉํ๋ฉฐ, ์ผํ์ฉ ํ ํฐ(Access Token) ๋ฑ์ ์ฌ์ฉ
2-2-2. ๊ณต๊ฐ ํด๋ ์(Public Claim)
- ๊ณต๊ฐ ํด๋ ์์ ์ฌ์ฉ์ ์ ์ ํด๋ ์์ผ๋ก, ๊ณต๊ฐ์ฉ ์ ๋ณด๋ฅผ ์ํด ์ฌ์ฉ๋จ
- ์ถฉ๋ ๋ฐฉ์ง๋ฅผ ์ํด URI ํฌ๋งท์ ์ด์ฉํ๋ฉฐ, ์์๋ ์๋์ ๊ฐ์
{ "https://mangkyu.tistory.com": true }
2-2-3. ๋น๊ณต๊ฐ ํด๋ ์(Private Claim)
- ๋น๊ณต๊ฐ ํด๋ ์์ ์ฌ์ฉ์ ์ ์ ํด๋ ์์ผ๋ก, ์๋ฒ์ ํด๋ผ์ด์ธํธ ์ฌ์ด์ ์์๋ก ์ง์ ํ ์ ๋ณด๋ฅผ ์ ์ฅ
- ์์๋ ์๋์ ๊ฐ์
- ์๋ช
(Signature)์ ํ ํฐ์ ์ธ์ฝ๋ฉํ๊ฑฐ๋ ์ ํจ์ฑ ๊ฒ์ฆ์ ํ ๋ ์ฌ์ฉํ๋ ๊ณ ์ ํ ์ํธํ ์ฝ๋
- ์์์ ๋ง๋ ํค๋(Header)์ ํ์ด๋ก๋(Payload)์ ๊ฐ์ ๊ฐ๊ฐ BASE64๋ก ์ธ์ฝ๋ฉํ๊ณ , ์ธ์ฝ๋ฉํ ๊ฐ์ ๋น๋ฐ ํค๋ฅผ ์ด์ฉํด ํค๋(Header)์์ ์ ์ํ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ํด์ฑ์ ํ๊ณ , ์ด ๊ฐ์ ๋ค์ BASE64๋ก ์ธ์ฝ๋ฉํ์ฌ ์์ฑ
- ์์ฑ๋ JWT์ ์์
- ์์ฑ๋ ํ ํฐ์ HTTP ํต์ ์ ํ ๋ Authorization์ด๋ผ๋ key์ value๋ก ์ฌ์ฉ๋จ
- ์ผ๋ฐ์ ์ผ๋ก value์๋ Bearer์ด ์์ ๋ถ์ฌ์ง
{ "Authorization": "Bearer {์์ฑ๋ ํ ํฐ ๊ฐ}", }
2-4. JWT ๋จ์ ๋ฐ ๊ณ ๋ ค์ฌํญ
- Self-contained : ํ ํฐ ์์ฒด์ ์ ๋ณด๋ฅผ ๋ด๊ณ ์์ผ๋ฏ๋ก ์๋ ์ ๊ฒ์ด ๋ ์ ์์
- ํ ํฐ ๊ธธ์ด : ํ ํฐ์ ํ์ด๋ก๋(Payload)์ 3์ข
๋ฅ์ ํด๋ ์์ ์ ์ฅํ๊ธฐ ๋๋ฌธ์, ์ ๋ณด๊ฐ ๋ง์์ง์๋ก ํ ํฐ์ ๊ธธ์ด๊ฐ ๋์ด๋ ๋คํธ์ํฌ์ ๋ถํ๋ฅผ ์ค ์ ์์
- Payload ์ธ์ฝ๋ฉ : ํ์ด๋ก๋(Payload) ์์ฒด๋ ์ํธํ ๋ ๊ฒ์ด ์๋๋ผ, BASE64๋ก ์ธ์ฝ๋ฉ ๋ ๊ฒ
- ์ค๊ฐ์ Payload๋ฅผ ํ์ทจํ์ฌ ๋์ฝ๋ฉํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ณผ ์ ์์ผ๋ฏ๋ก, JWE๋ก ์ํธํํ๊ฑฐ๋ Payload์ ์ค์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ง ์์์ผ ํจ
- Stateless : JWT๋ ์ํ๋ฅผ ์ ์ฅํ์ง ์๊ธฐ ๋๋ฌธ์ ํ๋ฒ ๋ง๋ค์ด์ง๋ฉด ์ ์ด๊ฐ ๋ถ๊ฐ๋ฅ
- ์ฆ, ํ ํฐ์ ์์๋ก ์ญ์ ํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํ๋ฏ๋ก ํ ํฐ ๋ง๋ฃ ์๊ฐ์ ๊ผญ ๋ฃ์ด์ฃผ์ด์ผ ํจ
- Tore Token : ํ ํฐ์ ํด๋ผ์ด์ธํธ ์ธก์์ ๊ด๋ฆฌํด์ผ ํ๊ธฐ ๋๋ฌธ์, ํ ํฐ์ ์ ์ฅํด์ผ ํจ
3. JWT ๊ตฌํ - Spring Security ์ฒ๋ฆฌ ๊ณผ์ 1
dependencies {
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'org.mariadb.jdbc:mariadb-java-client'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
- ์ ์ ์์์ ์ ๊ณตํ๋ ํด๋์ค ์์ฑ ๋ฐ ์ค์
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/static/", "classpath:/public/", "classpath:/"
, "classpath:/resources/", "classpath:/META-INF/resources/", "classpath:/META-INF/resources/webjars/" };
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// /์ ํด๋นํ๋ url mapping์ /common/test๋ก forwardํ๋ค.
registry.addViewController( "/" ).setViewName( "forward:/index" );
// ์ฐ์ ์์๋ฅผ ๊ฐ์ฅ ๋๊ฒ ์ก๋๋ค.
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
}
}
- SpringSecurity์ ๋ํ ๊ธฐ๋ณธ์ ์ธ ์ค์ ๋ค์ ์ถ๊ฐ
- SpringSecurity์ ๋ํ ์ค์ ํด๋์ค
- configure ๋ฉ์๋๋ฅผ ํตํด ์ ์ ์์๋ค์ ๋ํด์๋ Security๋ฅผ ์ ์ฉํ์ง ์์์ ์ถ๊ฐ
- configure ๋ฉ์๋๋ฅผ ํตํด ์ด๋ค ์์ฒญ์ ๋ํด์๋ ๋ก๊ทธ์ธ์ ์๊ตฌํ๊ณ , ์ด๋ค ์์ฒญ์ ๋ํด์ ๋ก๊ทธ์ธ์ ์๊ตฌํ์ง ์์์ง ์ค์
- form ๊ธฐ๋ฐ์ ๋ก๊ทธ์ธ์ ๋นํ์ฑํ
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// ์ ์ ์์์ ๋ํด์๋ Security ์ค์ ์ ์ ์ฉํ์ง ์์.
@Override
public void configure(WebSecurity web) {
web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
// ํ ํฐ์ ํ์ฉํ๋ ๊ฒฝ์ฐ ๋ชจ๋ ์์ฒญ์ ๋ํด ์ ๊ทผ์ด ๊ฐ๋ฅํ๋๋ก ํจ
.anyRequest().permitAll()
.and()
// ํ ํฐ์ ํ์ฉํ๋ฉด ์ธ์
์ด ํ์ ์์ผ๋ฏ๋ก STATELESS๋ก ์ค์ ํ์ฌ Session์ ์ฌ์ฉํ์ง ์์
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// form ๊ธฐ๋ฐ์ ๋ก๊ทธ์ธ์ ๋ํด ๋นํ์ฑํ
.formLogin().disable();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
- ์์ด๋, ๋น๋ฐ๋ฒํธ ์
๋ ฅ ํ ์ฌ์ฉ์ ๋ก๊ทธ์ธ ์์ฒญ
- ๋ก๊ทธ์ธ API๋ฅผ ํธ์ถ, Json์ผ๋ก ์ฌ์ฉ์์ ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณด๋ด๋ ์ํฉ์ผ๋ก ๊ฐ์
3-3. UserPasswordAuthenticationToken ๋ฐ๊ธ
- ์ ์ก์ด ์ค๋ฉด AuthenticationFilter๋ก ์์ฒญ์ด ๋จผ์ ์ค๊ฒ ๋๊ณ , ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก UserPasswordAuthenticationToken์ ๋ฐ๊ธํด์ฃผ์ด์ผ ํจ
- ์์ ์ ์ํด์ ํ๋ก ํธ์๋, ๋ฐฑ์๋์์ ์์ด๋์ ํจ์ค์๋์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํด์ฃผ๋ ๊ฒ์ด ์ข์
- ํด๋น Filter๋ฅผ ๊ตฌํํ๋ฉด ์๋์ ๊ฐ์
@Log4j2
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public CustomAuthenticationFilter(final AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
@Override
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException{
final UsernamePasswordAuthenticationToken authRequest;
try {
final User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
authRequest = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPw());
} catch (IOException exception) {
throw new InputNotFoundException();
}
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
@Entity
@Table(name = "USER")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends Common implements Serializable {
@Column(nullable = false, unique = true, length = 50)
private String email;
@Setter
@Column(nullable = false)
private String pw;
@Setter
@Column(nullable = false, length = 50)
@Enumerated(EnumType.STRING)
private UserRole role;
}
- ์์ด๋, ๋น๋ฐ๋ฒํธ ๊ฐ์ด ์ ๋๋ก ์ ๋ฌ๋์ง ์์ ๊ฒฝ์ฐ ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ํ InputNotFoundException ์์ฑ
public class InputNotFoundException extends RuntimeException {
public InputNotFoundException(){
super();
}
}
- Filter ์ ์ฉ
- UsernamePasswordAuthenticationFilter ํํฐ ์ด์ ์ ์ ์ฉ์์ผ์ผ ํจ
- ๊ทธ๋ฆฌ๊ณ ํด๋น CustomAuthenticationFilter๊ฐ ์ํ๋ ํ์ ์ฒ๋ฆฌ๋ Handler ์ญ์ Bean์ผ๋ก ๋ฑ๋กํ๊ณ CustomAuthenticationFilter์ ํธ๋ค๋ฌ๋ก ์ถ๊ฐํด์ฃผ์ด์ผ ํจ
- ํด๋น ์ฝ๋๋ค์ WebSecurityConfig์ ์๋์ ๊ฐ์ด ์ถ๊ฐ
@Log4j2
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) {
final User user = ((MyUserDetails) authentication.getPrincipal()).getUser();
final String token = TokenUtils.generateJwtToken(user);
response.addHeader(AuthConstants.AUTH_HEADER, AuthConstants.TOKEN_TYPE + " " + token);
}
}
- CustomLoginSuccessHandler๋ AuthenticationProvider๋ฅผ ํตํด ์ธ์ฆ์ด ์ฑ๊ณต๋ ๊ฒฝ์ฐ ์ฒ๋ฆฌ๋จ
- ์ธ์ฆ๊ณผ ๊ด๋ จํด ์์ฃผ ์ฌ์ฉ๋๋ ์์๋ ์๋์ AuthConstants ํด๋์ค์ ์ ์
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class AuthConstants {
public static final String AUTH_HEADER = "Authorization";
public static final String TOKEN_TYPE = "BEARER";
}
- ๋ก๊ทธ์ธ์ด ์ฑ๊ณตํ๋ฉด TokenUtils๋ฅผ ํตํด ํ ํฐ์ ์์ฑํ๊ณ , response์ ์ด๋ฅผ ์ถ๊ฐํ์ฌ ๋ฐํ
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// ์ ์ ์์์ ๋ํด์๋ Security ์ค์ ์ ์ ์ฉํ์ง ์์
@Override
public void configure(WebSecurity web) {
web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
// ํ ํฐ์ ํ์ฉํ๋ ๊ฒฝ์ฐ ๋ชจ๋ ์์ฒญ์ ๋ํด ์ ๊ทผ์ด ๊ฐ๋ฅํ๋๋ก ํจ
.anyRequest().permitAll()
.and()
// ํ ํฐ์ ํ์ฉํ๋ฉด ์ธ์
์ด ํ์ ์์ผ๋ฏ๋ก STATELESS๋ก ์ค์ ํ์ฌ Session์ ์ฌ์ฉํ์ง ์์
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// form ๊ธฐ๋ฐ์ ๋ก๊ทธ์ธ์ ๋ํด ๋นํ์ฑํ
.formLogin().disable()
.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager());
customAuthenticationFilter.setFilterProcessesUrl("/user/login");
customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler());
customAuthenticationFilter.afterPropertiesSet();
return customAuthenticationFilter;
}
@Bean
public CustomLoginSuccessHandler customLoginSuccessHandler() {
return new CustomLoginSuccessHandler();
}
}
- CustomAuthenticationFilter๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํ๋ ๊ณผ์ ์์ UserName, UserPassword ํ๋ผ๋ฏธํฐ ์ค์ ๊ฐ๋ฅ
- ์ด๋ฌํ ๊ณผ์ ์ ๊ฑฐ์น๋ฉด UsernamePasswordToken์ด ๋ฐ๊ธ๋๊ฒ ๋จ
3-4. UsernamePasswordToken์ Authentication Manager์๊ฒ ์ ๋ฌ
- AuthenticationFilter๋ ์์ฑํ UsernamePasswordToken์ AuthenticationManager์๊ฒ ์ ๋ฌ
- AuthenticationManager๋ ์ค์ ๋ก ์ธ์ฆ์ ์ฒ๋ฆฌํ ์ฌ๋ฌ ๊ฐ์ AuthenticationProvider๋ฅผ ๊ฐ์ง๊ณ ์์
3-5. UsernamePasswordToken์ Authentication Provider์๊ฒ ์ ๋ฌ
- AuthenticationManager๋ ์ ๋ฌ๋ฐ์ UsernamePasswordToken์ ์์ฐจ์ ์ผ๋ก AuthenticaionProvider๋ค์๊ฒ ์ ๋ฌํ์ฌ ์ค์ ์ธ์ฆ์ ๊ณผ์ ์ ์ํํด์ผ ํจ
- ์ค์ ์ธ์ฆ์ ๋ํ ๋ถ๋ถ์ authenticate ํจ์์ ์์ฑ์ ํด์ฃผ์ด์ผ ํจ
- SpringSecurity์์๋ Username์ผ๋ก DB์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋ค์์, ๋น๋ฐ๋ฒํธ์ ์ผ์น ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํ๋ ๋ฐฉ์์ผ๋ก ์๋
- ๋๋ฌธ์ ๋จผ์ UsernamePasswordToken ํ ํฐ์ผ๋ก๋ถํฐ ์์ด๋๋ฅผ ์กฐํํด์ผ ํจ
- ๊ทธ ์ฝ๋๋ ์๋์ ๊ฐ์
@RequiredArgsConstructor
@Log4j2
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
// AuthenticaionFilter์์ ์์ฑ๋ ํ ํฐ์ผ๋ก๋ถํฐ ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ์กฐํํจ
final String email = token.getName();
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
3-6. UserDetailsService๋ก ์กฐํํ ์์ด๋๋ฅผ ์ ๋ฌ
- AuthenticationProvider์์ ์์ด๋๋ฅผ ์กฐํํ์์ผ๋ฉด, UserDetailsService๋ก๋ถํฐ ์์ด๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์กฐํํด์ผ ํจ
- UserDetailsService๋ ์ธํฐํ์ด์ค์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ implementsํ ํด๋์ค๋ฅผ ์์ฑ
- ์ค์ ๋ฐํ๊ฐ์ ์์ฑํ๋ ๋ถ๋ถ์ 7๋ฒ ํ์ธ
@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Override
public MyUserDetails loadUserByUsername(String email) {
}
}
- User์ ๊ด๋ จ๋ SQL์ ์ฒ๋ฆฌํ๋ JpaRepository๋ฅผ ๊ตฌํํ UserRepository
@Repository
public interface UserRepository extends JpaRepository <User, Long> {
User findByEmailAndPw(String email, String pw);
Optional<User> findByEmail(String email);
}
3-7. ์์ด๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก DB์์ ๋ฐ์ดํฐ ์กฐํ
- ์ ๋ฌ๋ฐ์ ์์ด๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก DB์์ ์กฐํํ๋ ๊ตฌํ์ฒด๋ User VO
- UserDetailsService์ ๋ฐํ๊ฐ์ UserDetails ์ธํฐํ์ด์ค์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ implementsํ์ฌ ๊ตฌํํ MyUserDetails๋ฅผ ์๋์ ๊ฐ์ด ์์ฑ
@RequiredArgsConstructor
@Getter
public class MyUserDetails implements UserDetails {
@Delegate
private final User user;
private final Collection<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override public String getPassword() {
return user.getPw();
}
@Override public String getUsername() {
return user.getEmail();
}
@Override public boolean isAccountNonExpired() {
return user.getIsEnable();
}
@Override public boolean isAccountNonLocked() {
return user.getIsEnable();
}
@Override public boolean isCredentialsNonExpired() {
return user.getIsEnable();
}
@Override public boolean isEnabled() {
return user.getIsEnable();
}
}
- ์์ ์์ ์์๋ UserRepository๋ก๋ถํฐ ์กฐํํ ๊ฒฐ๊ณผ๋ฅผ Optional๋ก ๋ฐํํ๊ณ ์๊ธฐ ๋๋ฌธ์ map ํจ์๋ฅผ ์ด์ฉํด์ ์๋ก์ด UserDetails ๊ฐ์ฒด๋ก ์์ฑํ์ฌ ๋ฐํํ๊ณ ์์
3-8. ์ธ์ฆ ์ฒ๋ฆฌ ํ ์ธ์ฆ๋ ํ ํฐ์ AuthenticationManager์๊ฒ ๋ฐํ
- CustomAuthenticationProvider์์ UserDetailsService๋ฅผ ํตํด ์กฐํํ ์ ๋ณด์ ์
๋ ฅ๋ฐ์ ๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ๋์ง ํ์ธํ์ฌ, ์ผ์นํ๋ค๋ฉด ์ธ์ฆ๋ ํ ํฐ์ ์์ฑํ์ฌ ๋ฐํํด์ฃผ์ด์ผ ํจ
- DB์ ์ ์ฅ๋ ์ฌ์ฉ์ ๋น๋ฐ๋ฒํธ๋ ์ํธํ๊ฐ ๋์ด์๊ธฐ ๋๋ฌธ์, ์
๋ ฅ์ผ๋ก๋ถํฐ ๋ค์ด์จ ๋น๋ฐ๋ฒํธ๋ฅผ PasswordEncoder๋ฅผ ํตํด ์ํธํํ์ฌ DB์์ ์กฐํํ ์ฌ์ฉ์์ ๋น๋ฐ๋ฒํธํ ๋งค์นญ๋๋์ง ํ์ธ
- ๋ง์ฝ ๋น๋ฐ๋ฒํธ๊ฐ ๋งค์นญ๋์ง ์๋ ๊ฒฝ์ฐ์๋ BadCredentialsException์ ๋ฐ์์์ผ ์ฒ๋ฆฌ
@RequiredArgsConstructor
@Log4j2
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
private final BCryptPasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
// AuthenticaionFilter์์ ์์ฑ๋ ํ ํฐ์ผ๋ก๋ถํฐ ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ์กฐํํจ
final String userEmail = token.getName();
final String userPw = (String) token.getCredentials();
// UserDetailsService๋ฅผ ํตํด DB์์ ์์ด๋๋ก ์ฌ์ฉ์ ์กฐํ
final MyUserDetails userDetails = (MyUserDetails) userDetailsService.loadUserByUsername(userEmail);
if (!passwordEncoder.matches(userPw, userDetails.getPassword())) {
throw new BadCredentialsException(userDetails.getUsername() + "Invalid password");
}
return new UsernamePasswordAuthenticationToken(userDetails, userPw, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
- ์์ ๊ฐ์ด ์์ฑ๋ CustomAuthenticaionProvider๋ฅผ Bean์ผ๋ก ๋ฑ๋กํด์ฃผ์ด์ผ ํจ > WebSecurityConfig์ ์๋์ ๊ฐ์ด ์์ฑ
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
// ์ ์ ์์์ ๋ํด์๋ Security ์ค์ ์ ์ ์ฉํ์ง ์์.
@Override
public void configure(WebSecurity web) {
web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
// ํ ํฐ์ ํ์ฉํ๋ ๊ฒฝ์ฐ ๋ชจ๋ ์์ฒญ์ ๋ํด ์ ๊ทผ์ด ๊ฐ๋ฅํ๋๋ก ํจ
.anyRequest().permitAll()
.and()
// ํ ํฐ์ ํ์ฉํ๋ฉด ์ธ์
์ด ํ์ ์์ผ๋ฏ๋ก STATELESS๋ก ์ค์ ํ์ฌ Session์ ์ฌ์ฉํ์ง ์์
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// form ๊ธฐ๋ฐ์ ๋ก๊ทธ์ธ์ ๋ํด ๋นํ์ฑํ
.formLogin() .disable()
.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager());
customAuthenticationFilter.setFilterProcessesUrl("/user/login");
customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler());
customAuthenticationFilter.afterPropertiesSet();
return customAuthenticationFilter;
}
@Bean
public CustomLoginSuccessHandler customLoginSuccessHandler() {
return new CustomLoginSuccessHandler();
}
@Bean
public CustomAuthenticationProvider customAuthenticationProvider() {
return new CustomAuthenticationProvider(userDetailsService, bCryptPasswordEncoder());
}
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider());
}
}
3-9. ์ธ์ฆ๋ ํ ํฐ์ AuthenticationFilter์๊ฒ ์ ๋ฌ
- AuthenticaitonProvider์์ ์ธ์ฆ์ด ์๋ฃ๋ UsernamePasswordAuthenticationToken์ AuthenticationFilter๋ก ๋ฐํํ๊ณ
- AuthenticationFilter์์๋ LoginSuccessHandler๋ก ์ ๋ฌ
3-10. ์ธ์ฆ๋ ํ ํฐ์ ๊ธฐ๋ฐ์ผ๋ก JWT ๋ฐ๊ธ
- LoginSuccessHandler๋ก ๋์ด์จ ์์ฒญ์ /user/loginSuccess๋ก redirect ๋จ
- ์ ๋ฌ๋ฐ์ Authentication ์ ๋ณด๋ฅผ ํ์ฉํด Json Web Token์ ์์ฑํด์ฃผ์ด์ผ ํ๋๋ฐ, ํ ํฐ๊ณผ ๊ด๋ จ๋ ์์ฒญ์ ์ฒ๋ฆฌํ๋ TokenUtils๋ฅผ ์๋์ ๊ฐ์ด ๋ง๋ค์ด์ค ์ ์์
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class TokenUtils {
private static final String secretKey = "ThisIsA_SecretKeyForJwtExample";
public static String generateJwtToken(User user) {
JwtBuilder builder = Jwts.builder()
.setSubject(user.getEmail())
.setHeader(createHeader())
.setClaims(createClaims(user))
.setExpiration(createExpireDateForOneYear())
.signWith(SignatureAlgorithm.HS256, createSigningKey());
return builder.compact();
}
public static boolean isValidToken(String token) {
try {
Claims claims = getClaimsFormToken(token);
log.info("expireTime :" + claims.getExpiration());
log.info("email :" + claims.get("email"));
log.info("role :" + claims.get("role"));
return true;
} catch (ExpiredJwtException exception) {
log.error("Token Expired");
return false;
} catch (JwtException exception) {
log.error("Token Tampered");
return false;
} catch (NullPointerException exception) {
log.error("Token is null");
return false;
}
}
public static String getTokenFromHeader(String header) {
return header.split(" ")[1];
}
private static Date createExpireDateForOneYear() {
// ํ ํฐ ๋ง๋ฃ์๊ฐ์ 30์ผ์ผ๋ก ์ค์
Calendar c = Calendar.getInstance();
c.add(Calendar.DATE, 30);
return c.getTime();
}
private static Map<String, Object> createHeader() {
Map<String, Object> header = new HashMap<>();
header.put("typ", "JWT");
header.put("alg", "HS256");
header.put("regDate", System.currentTimeMillis());
return header;
}
private static Map<String, Object> createClaims(User user) {
// ๊ณต๊ฐ ํด๋ ์์ ์ฌ์ฉ์์ ์ด๋ฆ๊ณผ ์ด๋ฉ์ผ์ ์ค์ ํ์ฌ ์ ๋ณด๋ฅผ ์กฐํํ ์ ์์
Map<String, Object> claims = new HashMap<>();
claims.put("email", user.getEmail());
claims.put("role", user.getRole());
return claims;
}
private static Key createSigningKey() {
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(secretKey);
return new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS256.getJcaName());
}
private static Claims getClaimsFormToken(String token) {
return Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(secretKey))
.parseClaimsJws(token).getBody();
}
private static String getUserEmailFromToken(String token) {
Claims claims = getClaimsFormToken(token);
return (String) claims.get("email");
}
private static UserRole getRoleFromToken(String token) {
Claims claims = getClaimsFormToken(token);
return (UserRole) claims.get("role");
}
}
- ์ธ์ฆ์ด ์ฑ๊ณต๋๊ณ ๋๋ฉด CustomLoginSuccessHandler์์ Token์ด ์์ฑ๋๊ณ , ์์ฑ๋ ํ ํฐ์ ๋ฐํํ๊ฒ ๋จ
4. JWT ๊ตฌํ - Spring Security ์ฒ๋ฆฌ ๊ณผ์ 2