๐ฆ๋ฉ์์ด์ฌ์์ฒ๋ผ ๋ฐฑ์๋ ๋ถํธ์บ ํ 13๊ธฐ ๐ฆ
TIL ํ๊ณ - [59]์ผ์ฐจ
๐59์ผ์ฐจ์๋ Spring Security์์ ์ธ์ ์ ์ค์ ํ๋ ๋ฐฉ๋ฒ๊ณผ ์ธ์ ์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ธ JWT์ ๋ํด ์ค์ตํ ์ ์์๋ค.
ํ์ต ๋ชฉํ : JWT์ ๊ฐ๋ ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํด์ ๋ฐฐ์ฐ๊ณ ์ธ์ฝ๋ฉ, ๋์ฝ๋ฉ ๊ณผ์ ์ค์ต
ํ์ต ๊ณผ์ : ํ๊ณ ๋ฅผ ํตํด ์์ฑ
CustomUserDetailsService
- Spring Security์ ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋ UserDetailsService ์ธํฐํ์ด์ค๋ฅผ ์ง์ ์ปค์คํ
ํด์
๊ตฌํํ ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด ๋ก๋ ํด๋์ค
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/myinfo","/signup","/userreg", "/userreg_role","/loginform","/").permitAll()
.requestMatchers("/welcome","/shop/**").hasRole("USER")
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
- .authorizeHttpRequests( … ) : ์ธ์ฆํ์ ๊ฒฝ์ฐ์ Http ์์ฒญ์ ๋ํ ์ค์ ๋ถ๋ถ์ ๋ด๋น
- .requestMatchers() : ํน์ ์์ฒญ ํ์ด์ง๋ค์
- .permitAll() : ์ง์ ํ ํ์ด์ง๋ค์ ํ์ฉ
- .hasRole() : ํน์ ๊ถํ์ด ํ์ด์ง์ ์ ๊ทผํ๋ ๊ฒ์ ํ์ฉ
- . anyRequest().authenticated() : ๊ทธ ์ธ ํ์ด์ง๋ค์ ๋ํด์๋ “์ธ์ฆ”์ด ๋์ด์ผ๋ง ๋ณด์ฌ์ค
- CsrfFilter : CSRF๊ณต๊ฒฉ์ผ๋ก๋ถํฐ ๋ณดํธ
- UsernamePasswordAuthenticationFilter : ํผ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ
- BasicAuthenticationFilter : HTTP ๊ธฐ๋ณธ ์ธ์ฆ ์ฒ๋ฆฌ
- AuthorizationFilter : ์์ฒญ์ ๊ถํ ๋ถ์ฌ ์ฒ๋ฆฌ
์ธ์ ๋งค๋์ง๋จผํธ Session Management
- ๋ก๊ทธ์ธ ํ ์ํฌ๋ฆฟ๋ชจ๋๋ก ๊ฐ์ URL์ ์ ๊ทผํ๋ฉด ์ด์ ์ ๋ก๊ทธ์ธํ๋ ์ ๋ณด๊ฐ
๊ทธ๋๋ก ๊ฐ์ ธ์์ ธ์ ๋ณด์ฌ์ง๊ณ ์๋ ๊ฒ์ ํ์ธ ๊ฐ๋ฅ - ๋ณด์์ ์ํด ์ธ์ ์ ์ค์ ํ์ฌ ๋ก๊ทธ์ธ ํ์ฉ์ ์ค์ ํด์ค ์ ์์
http
.formLogin(form ->form
.loginPage("/loginform")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/welcome")
)
.logout(logout ->logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
)
.sessionManagement(session -> session
.maximumSessions(1) // ๋์ ์ ์ ๊ฐ๋ฅํ ์ธ์
์ ์ค์
)
.userDetailsService(customUserDetailsService);
- ๊ธฐ์กด http ์ฝ๋์ Session Management ์์ฑ์ ๋ฃ์ด์ ์ธ์
์ ์ค์
- .maximumSessions(1) : ๋์ ์ ์ ๊ฐ๋ฅํ ์ธ์
์ ์ค์
ex. 3์ด๋ฉด 3๊ฐ ์ด์ ์ธ์ ๋ถํฐ๋ ๋ก๊ทธ์ธ ์ ์ง๋ฅผ ํ์ง ์์ - .maxSessionsPreventsLogin(false) : ๋์์ ๋ก๊ทธ์ธ์ด ๋ค์ด์์๋ ๋๊ตฐ๊ฐ๋ฅผ ์ฐจ๋จํด์ผํ ๊ฒ
๊ธฐ๋ณธ๊ฐ์ false → // ์ฒซ๋ฒ์งธ ๋ก๊ทธ์ธ๋ ์ฌ์ฉ์๊ฐ ์ฐจ๋จ๋จ
true๋ก ๋ฐ๊พธ๊ฒ๋๋ฉด → ๋๋ฒ์งธ ๋ก๊ทธ์ธ ์ฌ์ฉ์๋ถํฐ ์ฐจ๋จ๋จ
Spring Security ์ธ์
- Spring Security๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ธ์ฆ, ์ธ๊ฐ๋ฅผ ๊ด๋ฆฌํ๊ณ ์ธ์ฆ์ ํ์ํ ์ ๋ณด๋ ์ฃผ๋ก ์ธ์ ์ ์ ์ฅ
- ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ๋ฉด ์ฌ์ฉ์์ ์ธ์ฆ์ํ, ์ฌ์ฉ์ ๊ถํ์ ๋ณด๊ฐ ์ธ์ ์ ์ ์ฅ๋๋ ๊ฒ
- ์ธ์
์ ์ ์ฅ๋ ์ ๋ณด๋ก ์๋ฒ๊ฐ ์ฌ์ฉ์๋ฅผ ์๋ณํ๊ณ ์ธ์ฆ์ํ๋ฅผ ์ ์งํ ๋ ์ฌ์ฉ
ex. jsessionid๊ฐ ์ฟ ํค๋ก ์ ์ฅ๋์ด์์๋ ์๋ฒ๋ ์ธ์ ์ ์๋ณํ๊ณ
์ธ์ ์ ์ ์ฅ๋ ์ ๋ณด๋ฅผ ์ด์ฉํ์ฌ ์ฌ์ฉ์๋ฅผ ์ธ์ฆํ๊ณ ๊ด๋ฆฌ - ์ธ์ ์ ์ ์ฅ๋๋ ์ฃผ์ ํด๋์ค : SecurityContext (ํ์ฌ ์ฌ์ฉ์์ ์ธ์ฆ์ ๋ณด์ ๊ถํ์ ๋ณด๋ฅผ ๋ด์)
- SecurityContext๋ SecurityContextHolder๋ฅผ ํตํด ์ ๊ทผํ๊ณ ๊ด๋ฆฌ
JWT
- ๋ฌธ์์ด๋ก์จ JSON๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ณด๋ฅผ ์ ๋ฌ
- Json Web Token(=JWT) : ์ธ์ฆ์ ํ์ํ ์ ๋ณด๋ค์ Token์ ๋ด์ ์ํธํ์์ผ ์ฌ์ฉํ๋ ํ ํฐ
- JWT์์ฒด๋ ์ธ์ฆ/์ธ๊ฐ์๋ ๊ด๋ จ์ด ์์
- ์ฆ JSON ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ณ ๋์งํธ ์๋ช ์ ํตํด ๊ฒ์ฆํ๋ค.
- JWT ๊ตฌ์ฑ์์ : Header(ํค๋) + Payload(ํ์ด๋ก๋ : ์ค์ ์ ์ฅํด์ผํ ์ ๋ณด) + Signature(์๋ช
: ๊ฒ์ฆํ๋ ์ฉ๋)
- JWT๋ ์ธ์ ์ ์ ์ฅํ๋ ๊ฒ์ด ์๋๋ผ, ์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจํ ํ ํฐ์ ์ ๊ณตํ๋ ๋ฐฉ์
- ์ฆ, JWT ์์ฒด๊ฐ ์ธ์
์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๊ฒ์ ์๋์ง๋ง, ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํ๋ ๋ฐ ํ์ฉ
โก๏ธJWT๋ ํด๋ผ์ด์ธํธ์ ์ ์ฅ๋๋ฉฐ, ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์
JWT Header
- JWT ํ์ ๊ณผ ํด์ฑ ์๊ณ ๋ฆฌ์ฆ ์ ๋ณด๊ฐ ๋ด๊น
JWT Payload
- ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ผ๋ก ์ค์ ๋ก ์ ์กํ ๋ฐ์ดํฐ๊ฐ ๋ด๊น
โญํด๋ ์(claim)์ด๋ผ ๋ถ๋ฆฌ๋ ์ ๋ณด๋ค์ด ํฌํจ๋จ - ํด๋ ์์ 3๊ฐ์ง ์ ํ
- Registered claims : ํน์ ํ ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ์ฌ์ ์ ์ ์๋ ํด๋ ์๋ค (๋ฐํ์, ๋ง๋ฃ์๊ฐ, ์ฃผ์ฒด)
- Public claims : ์ฌ์ฉ์ ์ ์ ํด๋ ์์ผ๋ก ์ถฉ๋์ ํผํ๊ธฐ ์ํด URIํ์์ผ๋ก ์์ฑ
- Private claims : ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉํ ์ ์๋ ํด๋ ์
JWT Signature
- Header, Payload์ ๋ด์ฉ์ ์ธ์ฝ๋ฉ
- ๋น๋ฐํค๋ฅผ ์ฌ์ฉํ์ฌ ์๋ช : ๋ฉ์์ง๊ฐ ๋ณ๊ฒฝ๋์ง ์์์์ ํ์ธ, ๊ฒ์ฆํ๋๋ฐ ์ฌ์ฉ
JWT ์๋๋ฐฉ์
1. ์ฌ์ฉ์๋ก๊ทธ์ธ
2. ์๋ฒ์ JWT ์์ฑ
3. ์ฌ์ฉ์์๊ฒ ๋ฐํ
4. JWT๋ ํด๋ผ์ด์ธํธ์ ์ ์ฅ์์ ์ ์ฅ
5. ํด๋ผ์ด์ธํธ๋ JWT๋ฅผ ์๋ฒ์ ์ ๋ฌ
6. ์๋ฒ๋ JWT์ ์๋ช
์ ํตํด ํด๋น ํ ํฐ์ด ์ ํจํ์ง ๊ฒ์ฆํ๊ณ ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญํ ์์
์ ์ํ
JWT ํ ํฐ ์ข ๋ฅ
- Access Token : ์๊ฐ์ ์งง๊ฒ ๋ง๋ฆ (* 30๋ถ ์ ๋ : ์งง๊ฒ ์ค์ ํ ์๋ก ๋ณด์์ ์ข์ง๋ง ์๋ฒ์ ๋ถํ ๊ฐ๋ฅ์ฑ ๋์์ง)
- Refresh Token : ์๊ฐ์ ๊ธธ๊ฒ ๋ง๋ฆ (* ํ๋ฌ, 1๋ ๋ฑ๋ฑ : DB์ ์ ์ฅํด์ ๊ด๋ฆฌํ๊ฒํจ)
์ผ๋ฐ์ ์ผ๋ก๋ ๋ฆฌํ๋ ์ ํ ํฐ์ ๊ฐ์ง๊ณ ์๋ค๊ฐ ํน์ ์๊ฐ๋ง๋ค ์ก์ธ์ค ํ ํฐ์ ๋ฐ๊ธ์ํค๋ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ
ex. ์ด๋ฉ์ผ๋ก “ํ์ฌ ์ด๋ ์์น์์ ๋ก๊ทธ์ธ ๋์์ต๋๋ค. ๋ณธ์ธ์ด ๋ง์ผ์๋ฉด ์, ์๋์๋ฉด ์๋์ค๋ฅผ ์ ํํ์ธ์” ๋ฐ์์๋
์๋์ค๋ฅผ ์ ํํ๊ฒ๋๋ฉด ์๋ฒ๊ฐ ๊ฐ๊ณ ์๋ Refresh Token ์ญ์
โRefresh Token์ ์ญ์ ํ๋ ์ด์
1. ๋ณด์ ๊ฐํ
โก๏ธRefresh Token์ ์๋ก์ด Access Token์ ๋ฐ๊ธํ ์ ์๋ ์ค์ํ ํค์ด๋ฏ๋ก
ํ์ทจ๋์์๊ฒฝ์ฐ ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์ฅ๊ธฐ๊ฐ ๋ก๊ทธ์ธํ๋ ๊ฒ์ ๋ฐฉ์ง
๋ฐ๋ผ์ ๋ณธ์ธ์ด ์๋๋ผ๋ฉด Refresh Token์ ์ฆ์ ์ญ์ ํ์ฌ ๋ ์ด์ ์๋ก์ด Access Token์ด ๋ฐ๊ธ๋์ง ์๋๋ก ํจ
2. ์ฌ๋ก๊ทธ์ธ ์ ๋
โก๏ธRefresh Token์ด ์ญ์ ๋๋ฉด ์ฌ์ฉ์๋ ๋ค์ ๋ก๊ทธ์ธํด์ผ ํ๋ฏ๋ก, ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์ง์์ ์ผ๋ก ์์คํ
์ ์ ๊ทผํ๋ ๊ฑธ ๋ฐฉ์ง
JWT ์ฅ๋จ์
- ์ฅ์ : ๊ฐ๋จํ๊ณ ๋ถ์ฐ ํ๊ฒฝ ์ง์ ๊ฐ๋ฅ
- ๋จ์ : ์ ๋ณด๋ฅผ ํฌํจํ๋ฉด์ ํฌ๊ธฐ๊ฐ ์ปค์ง ์ ์๊ณ ๋ณด์๋ฌธ์ ๋ฐ์ ๊ฐ๋ฅ์ฑ (=ํ ํฐํ์ทจ๊ฐ๋ฅ์ฑ)
ํผ ๋ก๊ทธ์ธ ๊ณผ์ ์์ ์ธ์
1. ํด๋ผ์ด์ธํธ๊ฐ ์น์์ "ํผ ์ ๋ณด์ ๋ฐ๋ผ" ๋ก๊ทธ์ธ
2. ์ธ์
์ ์ ๋ณด๊ฐ ์ ์ฅ
3.์ธ์
์ ๋ณด๋ ์๋ฒ๊ฐ ๊ด๋ฆฌํจ
โ์๋ฒ๊ฐ ์ฌ๋ฌ๋ ์ผ๋์ ๋ฌธ์ ์
์ฌ์ฉ์๊ฐ ํผ์ ๋ก๊ทธ์ธํ ์ ๋ณด๊ฐ ์๋ฒ์ ์ ์ฅ๋๋ ค๊ณ ํ ๋ ์ฌ์ฉ์์ ์์ฒญ์
์ฌ๋ฌ ์๋ฒ๋ก ๋ถ์ฐ์ํค๋ ์ญํ ์ “๋ก๋ ๋ฐธ๋ฐ์”๊ฐ ๋ด๋นํจ
ํผ ๋ก๊ทธ์ธ ๋ฐฉ์์์๋ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ๋ฉด ์ธ์ (Session) ์ ์์ฑํ๊ณ , ์ด ์ธ์ ์ ๋ณด๋ฅผ ์๋ฒ์์ ๊ด๋ฆฌ
๋ก๋ ๋ฐธ๋ฐ์ (Load Balancer) : ์ฌ๋ฌ ๋์ ์๋ฒ๊ฐ ์์ ๋ ์์ฒญ์ ๋ถ์ฐ์์ผ์ฃผ๋ ์ญํ
โ ํด๊ฒฐ ๋ฐฉ๋ฒ : ์ธ์ ๊ด๋ฆฌ ๋ฐฉ๋ฒ 3๊ฐ์ง
1. Sticky Session (๊ณ ์ ์ธ์
) ์ฌ์ฉ
โก๏ธ๋ก๋ ๋ฐธ๋ฐ์๊ฐ ํ ๋ฒ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ์์ฒญ์ ํญ์ ๊ฐ์ ์๋ฒ๋ก ๋ณด๋ด๋ ๋ฐฉ์
โก๏ธํน์ ์๋ฒ์ ์ฅ์ ๊ฐ ๋ฐ์ํ๋ฉด ์ธ์
์ด ์ฌ๋ผ์ง๋ ๋จ์
2. ์ธ์
์ ์ธ๋ถ ์ ์ฅ์์ ์ ์ฅ (DB ๋๋ Redis ํ์ฉ)
โก๏ธ์ฌ๋ฌ ์๋ฒ์์ ์ธ์
์ ๊ณต์ ํ ์ ์๋๋ก, ์ธ์
์ Redis, Memcached, DB ๋ฑ์ ์ ์ฅ
โก๏ธ์๋ฒ๊ฐ ๋ฌ๋ผ์ ธ๋ ์ธ์
์ ๋ณด๊ฐ ์ ์ง๋๋ฏ๋ก ์์ ์
3. JWT (JSON Web Token) ์ฌ์ฉ
โก๏ธ์๋ฒ์ ์ธ์
์ ์ ์ฅํ๋ ๋์ , JWT๋ฅผ ํ์ฉํ์ฌ ํด๋ผ์ด์ธํธ ์ธก์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์งํ ์ ์์
โก๏ธ์๋ฒ ๊ฐ ์ธ์
๋๊ธฐํ ๋ฌธ์ ๊ฐ ์ฌ๋ผ์ง๊ณ , ๋ก๋ ๋ฐธ๋ฐ์ฑ์ด ์์ ๋ก์์ง\
์ ๋ฆฌํ์๋ฉด
์๋ฒ๊ฐ ์ฌ๋ฌ ๋์ผ ๊ฒฝ์ฐ, ์ธ์
๋ฐ์ดํฐ๊ฐ ๊ฐ ์๋ฒ์ ๊ฐ๋ณ์ ์ผ๋ก ์ ์ฅ๋๋ฉด ์ธ์
๋ถ์ผ์น ๋ฌธ์ ๊ฐ ๋ฐ์ ๊ฐ๋ฅ
์ด๋ ๋ก๋ ๋ฐธ๋ฐ์๋ ์์ฒญ์ ๋ถ์ฐ์ํฌ ๋ฟ, ์ธ์
์ ์ง์ ๊ด๋ฆฌํ์ง๋ ์์ผ๋ฏ๋ก JWT ๊ฐ์ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํด์ผํจ
JWT๋ ์ธ์ ์ ์๋ฒ๊ฐ ๊ด๋ฆฌํ์ง ์๊ณ , ํ ํฐ์ ํด๋ผ์ด์ธํธ์์ ๊ด๋ฆฌํ๋๋ก ํ๋ ๊ธฐ์ ์
๋ณด์์ ์ทจ์ฝํ ์ ์์ด ์ธ์
์ ํด๋ผ์ด์ธํธ๊ฐ ์๋ ์๋ฒ๊ฐ ๊ฐ์ก๋ ๊ธฐ์กด์ ๋ฐฉ๋ฒ์์
ํด๋ผ์ด์ธํธ ์ธก์ “๋ณด์๋ ์ํ๋ก” ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๊ฒ์ด JWT ๊ธฐ์ ์ธ ๊ฒ
- ์ด ์ธ์ฝ๋ฉ๋ ์ ๋ณด๋ฅผ “๋์ฝ๋ฉ De-coding”์ผ๋ก ํ์ด๋ด์ด ๋ค์ ๊ฐ์ ์ป์ด๋ผ ์ ์์ ๊ฒ
- JWT ์์ฒด๋ ์ํธํ๋์ง ์์ผ๋ฉฐ, ๋๊ตฌ๋ ๋์ฝ๋ฉํ์ฌ ๋ด์ฉ์ ๋ณผ ์ ์์ผ๋ฏ๋ก ์ค์ํ ์ ๋ณด(ex. ๋น๋ฐ๋ฒํธ)๋
JWT์ ํฌํจํ์ง ์๋ ๊ฒ์ด ์์น - JWT์๋ ์๋ช
(Signature) ์ด ํฌํจ๋์ด ์์ด,
๋น๋ฐํค(Secret Key) ๋๋ ๊ณต๊ฐํค/๊ฐ์ธํค(Public/Private Key) ๋ก ์๋ช ์ ๊ฒ์ฆ - ์ด๋ฌํ ์๋ช
์ผ๋ก JWT๊ฐ ์์กฐ๋์ง ์์์์ ํ์ธํ๋๋ฐ ์ฆ, JWT์ ๋ด์ฉ์ ๋ณ๊ฒฝํ๋ ค๋ฉด
์๋ช (Signature)๋ ํจ๊ป ๋ณ๊ฒฝํด์ผ ํ๋ฏ๋ก, ๋น๋ฐํค ์์ด ์์กฐ๋ ๋ถ๊ฐ๋ฅํจ (๋น๋ฐํค๋ ์๋ฒ๊ฐ ๊ฐ์ง๊ณ ์์)
์ธ์ ๊ณต์ ์ ๋จ์
- ์ฌ๋ฌ ์ธ๋ถ ์ ์ฅ์์์ ๊ฐ์ ธ์ค๋๋ก ๊ตฌํ์ด ๋์ด์์ผ๋ฉด ์ธ์
๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ณผ์ ์์
๋คํธ์ํฌ ์ง์ฐ์ด ๋ฐ์ํ ์ ์์ - ์ธ๋ถ ์ ์ฅ์์ ์ธ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ฒ๋๋ฉด ๋ณด์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ์์
- ์ฑ๋ฅ ๋ฌธ์ ๋ก ๋งค์ฐ ๋น ๋ฅธ ์ธ๋ฉ๋ชจ๋ฆฌ ์ธ์
์คํ ์ด์ ๋นํด ์ธ๋ถ ์ ์ฅ์๋ ๋๋ฆด ์ ์์
๋ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ ์ธ์ ์ DB์ ๋ถํ๋ก ์ฑ๋ฅ ์ ํ ๊ฐ๋ฅ์ฑ์ด ์์
JWT์์ ์๋ฐ ์ฌ์ฉ
- application.yml์ ๋น๋ฐ ํค, ๋ฆฌํ๋ ์ ํค ์ถ๊ฐ (32๋นํธ ์ ๋ ๊ธธ์ด๋ก ์ค์ )
- SignatureAlgorithm ์์ฑ
- ์ซ์๊ฐ ๋ง์์๋ก ๋ ๋ง์ ๋นํธ ์๋ฅผ ์ฐจ์งํ ์ ์๊ฒ๋จ
public static void main(String[] args) {
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jwt = Jwts.builder()
.setIssuer("juunb-app") // ์ด๋์ ์ฌ์ฉํ๋ ๊ฒ์ธ์ง ์ค์
.setSubject("juunb123")
.setExpiration(new Date(System.currentTimeMillis() + 36000)) // ๋ง๋ฃ ์๊ฐ ์ค์ (ms ๊ธฐ์ค)
.claim("role", "ADMIN")
.signWith(secretKey) // SIGN์ ๋ง๋ฆ (secretKey๋ฅผ ์ด์ฉํด์ ๋ง๋ฆ)
.compact();
System.out.println(jwt); // ํ ํฐ ์ถ๋ ฅํด๋ณด๊ธฐ
}
์ถ๋ ฅ : ํ ํฐ {eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqdXVuYi1hcHAiLCJzdWIiOiJqdXVuYjEyMyIsImV4cCI6MTc0MTE1MzczMSwicm9sZSI6IkFETUlOIn0.Tw6kHrKKc2BmJCrZ3Cks4FTSwDewEnlDCKa2wv0XLBo}
- ํ ํฐ์ jwt.io์ ๊ฐ์ ํ์ด์ง๋ฅผ ํตํด ๋์ฝ๋ฉํด๋ณด๋ฉด ์ค์ ํด๋จ๋ ์ ๋ณด๋ค์ด ์ถ๋ ฅ๋๊ณ ์๋ ๊ฒ์ ํ์ธ (payload ๋ถ๋ถ)
Decoding ๋์ฝ๋ฉ
- ๋์ฝ๋ฉ ํ๋ ๊ณผ์ ์ ์ง์ ์์ฑํ ์๋ ์์
// JWT ํ์ฑ, ๊ฒ์ฆ ํํธ
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt)
.getBody();
log.info("[Decoding Result] - Expiration Time : {}", claims.getExpiration());
log.info("[Decoding Result] - Subject : {}", claims.getSubject());
log.info("[Decoding Result] - Role : {}", claims.getAudience());
SecretKey๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ 2๊ฐ์ง
// 1. ์๊ณ ๋ฆฌ์ฆ์ผ๋ก secretKey๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ
// SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 2. ์ฌ์ฉ์ ์ ์ secretKey๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ - ์ ์ ํ SecretKey(32๋ฐ์ดํธ ์ด์)๋ฅผ ์๋ ์์ฑ
String secret = "abcdefghijklmnopqrstuvwxzy123456789012"; // ์ต์ 32๋ฐ์ดํธ ์ ์ง
byte[] bytes = secret.getBytes(StandardCharsets.UTF_8);
SecretKey secretKey = Keys.hmacShaKeyFor(bytes);
1. ์๊ณ ๋ฆฌ์ฆ์ผ๋ก SecretKey๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ
โก๏ธ32๋ฐ์ดํธ(256๋นํธ) ์ด์์ ํค๋ฅผ ๋ด๋ถ์ ์ผ๋ก ์์์ ์ค์
โก๏ธSecretKey๋ฅผ ์๋์ผ๋ก ์์ฑํ๊ธฐ ๋๋ฌธ์ ํธ๋ฆฌํ์ง๋ง, ๊ณ ์ ๋ ๊ฐ์ ์ ์งํ๊ธฐ ์ด๋ ค์
โก๏ธ๋จ์ : SecretKey๊ฐ ์คํํ ๋๋ง๋ค ๋ฌ๋ผ์ง ์ ์์
- JWT ์๋ช
์ ๊ฒ์ฆ ์ ๊ฐ์ SecretKey๊ฐ ํ์ํ๋ฏ๋ก, ์ผ์ ํ ํค๋ฅผ ์ ์งํด์ผ ํจ
2. ์ฌ์ฉ์ ์ ์ SecretKey๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ
โก๏ธSecretKey๋ฅผ ์ง์ ์ ์ํ ๋ฌธ์์ด์ ์ฌ์ฉํ์ฌ ์์ฑ
โก๏ธ๊ณ ์ ๋ ํค๋ฅผ ์ฌ์ฉํ๋ฏ๋ก, ์๋ฒ๊ฐ ์ฌ์์๋์ด๋ ๊ฐ์ SecretKey๋ก JWT๋ฅผ ๊ฒ์ฆ ๊ฐ๋ฅ
โก๏ธ์ฅ์ : application.yml ๋๋ ํ๊ฒฝ ๋ณ์(.env)์ ์ ์ฅํ์ฌ ๊ด๋ฆฌ ๊ฐ๋ฅ
์๋ฒ๊ฐ ์ฌ์์๋๋๋ผ๋ ๊ฐ์ SecretKey๋ฅผ ์ฌ์ฉํ์ฌ JWT ์๋ช
์ ๊ฒ์ฆํ ์ ์์
โก๏ธ๋จ์ : SecretKey๋ฅผ ์ง์ ๊ด๋ฆฌํด์ผ ํจ (๋ณด์ ์ทจ์ฝ)
ํ๊ฒฝ๋ณ์ ์ถ๊ฐ application.yml (SecretKey, RefreshKey)
jwt:
secretKey: 12345678901234567890123456789012
refreshKey: 12345678901234567890123456789012
JwtTokenizer ํด๋์ค
- JWT ํ ํฐ์ ๊ด๋ฆฌํ ํด๋์ค, JWT ํ ํฐ์ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ๊ธฐ ์ํ Util ํด๋์ค
// JWTํ ํฐ์ ํธํ๊ฒ ์ฌ์ฉํ๊ธฐ ์ํ Util ํด๋์ค
@Component
public class JwtTokenizer {
private final byte[] accessSecret;
private final byte[] refreshSecret;
// ์ด accessSecret๊ณผ refreshSecret์ ํ๊ฒฝ๋ณ์ ํ์ผ (application.yml)์์ ๊บผ๋ด์ ์ฌ์ฉํ ๊ฒ
public JwtTokenizer(@Value("${jwt.secretKey}") String accessSecret, @Value("${jwt.refreshKey}") String refreshSecret) {
this.accessSecret = accessSecret.getBytes(StandardCharsets.UTF_8); // String -> byte๋ก ๋ณํ
this.refreshSecret = refreshSecret.getBytes(StandardCharsets.UTF_8); // String -> byte๋ก ๋ณํ
}
}
- @Value("${jwt.secretKey}") String accessSecret
โก๏ธ@Value ๋ฅผ ์ฌ์ฉํ์ฌ ํ๊ฒฝ๋ณ์ ํ์ผ (application.yml)์ ์๋ ๊ฐ์ ์ ๊ทผ ($ { } ํ์ฉ) - getBytes(StandardCharsets.UTF_8);
โก๏ธString -> byte๋ก ๋ณํ
JwtTokenizer - createToken() ๋ฉ์๋ ์ถ๊ฐ
// ํ ํฐ์ ํฌํจํ ์ ๋ณด๋ฅผ ํจ๊ป ๋ง๋ฆ
private String createToken(Long id, String email, String name,
String username, List<String> roles,
Long expire, byte[] secretKeys){
Claims claims = Jwts.claims().setSubject(email);
// ํ์ํ ์ ๋ณด๋ค ์ ์ฅ
claims.put("username", username);
claims.put("name", name);
claims.put("userId", id);
claims.put("roles", roles);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(new Date().getTime()+expire))
.signWith(getSigningKey(secretKeys))
.compact();
}
- โ@Component ์ฌ์ฉ ์ด์
โก๏ธJwtTokenizer ํด๋์ค๋ฅผ Spring์ด ์๋์ผ๋ก ์์ฑํ๊ณ ๊ด๋ฆฌํ๋๋ก ํจ
JWT ๊ด๋ จ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๊ณตํต ์ ํธ ํด๋์ค๋ก์จ ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฌ์ฉ๋ ์ ์๋๋ก ํจ
(= ๋งค๋ฒ ๊ฐ์ฒด๋ฅผ ์์ฑํ์ง ์๊ณ , Spring์ด ํ ๋ฒ๋ง ์์ฑํ๊ณ ๊ด๋ฆฌ) - Claims claims = Jwts.claims().setSubject(email);
โก๏ธid, name ๋ฑ ์ํ๋ ๊ฒ์ ๋ฃ์ด์ฃผ๋ฉด ๋จ
โก๏ธ๊ฐ๊ธ์ ์ด๋ฉด ๊ณ ์ ํ ์๋ณ์๊ฐ (์ค๋ณต X)์ ๋ฃ์ด์ฃผ๋ ๊ฒ์ด ์ข์
ex. ํด๋น Subject์ ํค ๊ฐ (๋ง์ฝ ์ด๋ฉ์ผ ์ธ์ฆ ์๋น์ค๋ฉด ์ด๋ฉ์ผ์ ๋ฃ์ด์ค)
โก๏ธClaims: JWT์ ํ์ด๋ก๋(payload) ๋ถ๋ถ์ ์๋ฏธ
setSubject(email): ํ ํฐ์ ์ฃผ์ (Subject)๋ฅผ ์ด๋ฉ์ผ๋ก ์ค์ - .setExpiration(new Date(new Date().getTime()+expire))
expire : 1000ms (1 sec) >> 1000 * 60 * 60 = 1 hour - .signWith(getSigningKey(secretKeys))
private static Key getSigningKey(byte[] secretKey){
return Keys.hmacShaKeyFor(secretKey);
}
- ์ ์ํ ๋ฉ์๋ getSigninKey() ๋ฅผ ์ด์ฉํด ์ํ
- JWT์ ์๋ช
์ ์ํ SecretKey ๊ฐ์ฒด ์์ฑ
createToken()์์ ์ฌ์ฉํ์ฌ JWT์ Signature(์๋ช ) ๋ถ๋ถ์ ์์ฑ - signWith() ๋ฉ์๋๋ Key ๊ฐ์ฒด๊ฐ ํ์ํ๋ฏ๋ก,→ ๋ฐ์ดํธ ๋ฐฐ์ด(byte[]) → SecretKey ๊ฐ์ฒด๋ก ๋ณํ
- ์ฆ, getSigningKey() ๋ฉ์๋ : ์๋ช ์ ์ํ SecretKey ๊ฐ์ฒด๋ฅผ ๋ฐํ
์ ๋ฆฌํ์๋ฉด
1. SecretKey๋ฅผ application.yml์์ ๊ฐ์ ธ์ ์ ์ฅ
2. createToken()์์ JWT์ Payload(์ฌ์ฉ์ ์ ๋ณด) ์ค์
3. JWT์ ๋ฐ๊ธ ์๊ฐ, ๋ง๋ฃ ์๊ฐ ์ค์
4. getSigningKey()๋ฅผ ์ฌ์ฉํด SecretKey ๊ฐ์ฒด ์์ฑ
5. JWT์ ์๋ช
(Signature) ์ถ๊ฐ
6. ๋ง์ง๋ง.compact()๋ฅผ ํตํด JWT ๋ฌธ์์ด ๋ฐํ
Access Token, Refresh Token ์ฌ์ฌ์ฉ
public static final Long ACCESS_TOKEN_EXPIRE_COUNT = 1000L * 60 * 30; // ์ก์ธ์ค ํ ํฐ ์ ์ง์๊ฐ 30๋ถ
public static final Long REFRESH_TOKEN_EXPIRE_COUNT = 1000L * 60 * 60 * 24 * 7; // ์ก์ธ์ค ํ ํฐ ์ ์ง์๊ฐ 7์ผ
- ์ฅ์ : ํ ํฐ ๋ง๋ฃ ์๊ฐ์ด ๊ณ ์ ๋์ด ์์ด ๋งค๋ฒ ๋ค๋ฅด๊ฒ ๋ณํ์ง ์์
Access Token, Refresh Token ์์ฑ ๋ฉ์๋
// ์ก์ธ์ค ํ ํฐ ์์ฑ
public String createAccessToken(Long id, String email, String name,
String username, List<String> roles){
return createToken(id, email, name, username, roles, ACCESS_TOKEN_EXPIRE_COUNT, accessSecret);
}
// ๋ฆฌํ๋ ์ ํ ํฐ ์์ฑ
public String createRefreshToken(Long id, String email, String name,
String username, List<String> roles){
return createToken(id, email, name, username, roles, REFRESH_TOKEN_EXPIRE_COUNT, refreshSecret);
}
- ๋ ๋ฉ์๋๋ createToken() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ JWT๋ฅผ ์์ฑ
- Access Token(AT) : API ์์ฒญ์ ๋ณด๋ผ ๋ ์ธ์ฆ ์ฉ๋๋ก ์ฌ์ฉ (์งง์ ์๊ฐ ์ ์ง)
- Refresh Token(RT) : Access Token์ด ๋ง๋ฃ๋์์ ๋ ์๋ก ๋ฐ๊ธ๋ฐ๋ ์ฉ๋๋ก ์ฌ์ฉ (๋ ๊ธด ์๊ฐ ์ ์ง)
- createToken() ๋ฉ์๋์์๋
โก๏ธํ ํฐ ์์ฑ์ ํต์ฌ ๋ก์ง์ ๋ด๋น
โก๏ธ๊ณตํต์ ์ธ JWT ์์ฑ ๊ณผ์ ์ ์บก์ํํ์ฌ ์ฝ๋ ์ค๋ณต์ ์ค์
Main ํ ์คํธ
public static void main(String[] args) {
JwtTokenizer jwtTokenizer = new JwtTokenizer(
"12345678901234567890123456789012",
"12345678901234567890123456789012");
String accessToken = jwtTokenizer.createAccessToken(1L, "test@test.com", "test", "testuser", Arrays.asList("ROLE_USER"));
log.info("[ACCESS TOKEN] : {}", accessToken);
}
โก๏ธ์ถ๋ ฅ : [ACCESS TOKEN] : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0Q……
๐ํ๊ณ ๊ฒฐ๊ณผ :
์ด๋ฒ ํ๊ณ ์์๋ ์ธ์
์ ๋ํ ๊ฐ๋
์ ๋ค์ ํ์ตํ๊ณ Spring Security์์์ ์ ์ฉ๊ณผ JWT ์ฌ์ฉ์ ๋ค์ํ ์์ ๋ก ์ ํ ์ ์์๋ค.
- Session Management๋ฅผ ํตํ ์ธ์
์ค์
- JWT๋ก AccessToken, Refresh Token ์์ฑ
๋๋ ์ :
์ด๋ฉ์ผ๋ก ์ ์ก๋๋ "๋ก๊ทธ์ธ ํ์ธ ๋ฉ์ผ"๊ณผ ๊ธฐ์กด ๋ธ๋ผ์ฐ์ ์์ "์ ํญ"์ ์ด์์๋๋ ๋ก๊ทธ์ธ์ด ์ง์๋์๋ ๊ฒ์ด
์ธ์
๊ณผ ๊ด๋ จ์ด ์๋ค๋ ๊ฒ์ ์ ์ ์์๋ค.
์ํฌ๋ฆฟ๋ชจ๋์์๋ ์ธ์
์ด ์ ์ง๋๋ ๊ฒ์ ํ์ธํ ์ ์์๊ณ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Session Management๋ก ์ธ์
์ ์ค์ ํ ์ ์์๋ค.
ํฅํ ๊ณํ :
- JwtTokenizer์ ํ ํฐ ์์ฑ ๊ณผ์ ํ์ต
- REFRESH TOKEN ์์ฑํด๋ณด๊ธฐ