์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ๋ฉด ์๋ฒ๋ JWT(JSON Web Token)๋ฅผ ๋ฐ๊ธํ๊ณ , ์ด๋ฅผ ํด๋ผ์ด์ธํธ๊ฐ ์ ์ฅํ์ฌ ์ธ์ฆ์ ์ ์งํ๋ค. JWT๋ ์ผ๋ฐ์ ์ผ๋ก localStorage
๋ sessionStorage
์ ์ ์ฅํ์ง๋ง, ๋ณด์์ด ์ค์ํ ์ํฉ์์๋ HttpOnly ์ฟ ํค์ ์ ์ฅํ๋ ๊ฒ์ด ํจ์ฌ ์์ ํ๋ค. ๐ฏ
localStorage
์ JWT๋ฅผ ์ ์ฅํ๋ฉด XSS(ํฌ๋ก์ค ์ฌ์ดํธ ์คํฌ๋ฆฝํ
) ๊ณต๊ฒฉ์ ์ทจ์ฝํ ์ ์๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๋ ค๋ฉด, HttpOnly ์ฟ ํค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค. ๐ฌ
ํ์ง๋ง... HttpOnly ์ฟ ํค๋ฅผ ์ฌ์ฉํ๋ฉด XSS๋ฅผ ์์ ํ ๋ฐฉ์งํ๋ ๊ฒ์ด ์๋๋ผ, JavaScript๋ก ์ฟ ํค ์ ๊ทผ๋ง ๋ง๋๋ค๋ ์ ์ ๋ช
ํํ ํด์ผํ๋ค.
XSS ์์ฒด๋ฅผ ์ฐจ๋จํ๋ ค๋ฉด โ๏ธ : ์ฝํ ์ธ ๋ณด์ ์ ์ฑ (CSP) ์ค์ , ์ ๋ ฅ๊ฐ ๊ฒ์ฆ ๊ฐํ๊ฐ ํ์
+ ๐กCSRF ๋ฐฉ์ด๋ฅผ ์ํ ์ถ๊ฐ ์ค์
HttpOnly ์ฟ ํค๋ฅผ ์ฌ์ฉํ๋ฉด CSRF ๋ฐฉ์ด๋ ์ถฉ๋ถํ์ง ์๋ค.
์ด๋ฅผ ๋ณด์ํ๊ธฐ ์ํด CSRF ํ ํฐ๊ณผ ๊ฐ์ ์ถ๊ฐ ๋ณดํธ๋ฅผ ์ค๋ช
ํ๋ ๊ฒ์ด ์ข๋ค.
์์:
HttpOnly
์ฟ ํค๋ JavaScript์์ ์ ๊ทผํ ์ ์๋ ์ฟ ํค์ด๋ค. ์ฆ, ํด๋ผ์ด์ธํธ ์ธก ์คํฌ๋ฆฝํธ์์๋ ์ด ์ฟ ํค์ ์ ๊ทผํ ์ ์์ผ๋ฏ๋ก XSS ๊ณต๊ฒฉ์ ์๋ฐฉํ ์ ์๋ค. ์ด ๋ฐฉ์์ ๋ณด์์ ๊ฐํํ๊ณ , ์๋์ผ๋ก ์ฟ ํค๋ฅผ ์๋ฒ์ ์ ์กํ๊ธฐ ๋๋ฌธ์ ์ธ์ฆ ์ฒ๋ฆฌ๊ฐ ๊ฐํธํด์ง๋ค๋ ์ฅ์ ์ด ์กด์ฌํ๋ค. ๐
๋ก๊ทธ์ธ ์์ฒญ์ ์ฒ๋ฆฌํ ํ, ์๋ฒ๋ JWT๋ฅผ ์์ฑํ์ฌ HttpOnly ์ฟ ํค๋ก ํด๋ผ์ด์ธํธ์ ์ ์กํ๋ฉฐ ์๋์ ๊ฐ์ ์ค์ ์ ํ๋ค :
// ์ฟ ํค ์์ฑ
Cookie jwtCookie = new Cookie("token", token);
jwtCookie.setHttpOnly(true); // JavaScript์์ ์ ๊ทผ ๋ถ๊ฐ
jwtCookie.setSecure(false); // ๊ฐ๋ฐ ํ๊ฒฝ์์๋ false, ๋ฐฐํฌ ํ๊ฒฝ์์๋ true
jwtCookie.setPath("/"); // ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด์ ์ ํจ
jwtCookie.setMaxAge(7 * 24 * 60 * 60); // 7์ผ ๋์ ์ ํจ
response.addCookie(jwtCookie);
+ ๐ก HttpOnly ์ธ์ SameSite ์์ฑ ์ถ๊ฐ
SameSite ์์ฑ์ ์ฟ ํค์ CSRF(Cross-Site Request Forgery) ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ถ๊ฐ๋์ด์ผ ํ๋ค. ์์์ผํ ๊ฒ ...๋๋์๋ค ในใ
...
SameSite=Strict โ ์ฟ ํค๊ฐ ๊ฐ์ ์ฌ์ดํธ์์๋ง ์ ์ก๋จ
SameSite=Lax โ ๋๋ถ๋ถ์ ์์ฒญ์์ ์ ์ก๋์ง๋ง, ํฌ๋ก์ค ์ฌ์ดํธ ์์ฒญ์์ ์ ํ
SameSite=None โ ๋ชจ๋ ์์ฒญ์์ ํ์ฉ๋์ง๋ง, ๋ฐ๋์ Secure ์์ฑ๊ณผ ํจ๊ป ์ฌ์ฉํด์ผ ํจ
HttpOnly
์ฟ ํค๋ ํด๋ผ์ด์ธํธ ์ธก์์ ์ง์ ์ ๊ทผํ ์ ์๋ค. ํ์ง๋ง, ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ผ ๋, ์๋์ผ๋ก ์ฟ ํค๊ฐ ํฌํจ๋์ด ์ ์ก๋๋ค. ์ด๋ก ์ธํด, ์๋ฒ๋ ๋ณ๋์ ์ ์ฅ์ ์์ด ์ธ์ฆ์ ์๋์ผ๋ก ํ์ธํ ์ ์๋ค. ๐
๋๋ ํด๋ผ์ด์ธํธ ์ธก์์๋ ๋ก๊ทธ์ธ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด Pinia์ ๊ฐ์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค. ๋ก๊ทธ์ธ ํ HttpOnly ์ฟ ํค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ณ , ํ์ด์ง๊ฐ ์๋ก๊ณ ์นจ ๋์ด๋ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํ ์ ์์๋ค. ๐
const loginState = useLoginState(); // Pinia ์ํ ๊ด๋ฆฌ ์ฌ์ฉ
// ๋ก๊ทธ์ธ ์ฑ๊ณต ์, ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ๊ถํ์ ์ํ์ ์ ์ฅ
loginState.isLoggedIn = true;
loginState.adminName = decodedToken.name;
์๋๋ ์์๋ฃฐ ์ฐธ๊ณ ํ์ฌ ๊ตฌํํ ๋ก๊ทธ์ธ ํ์ด์ง์ script ๋ถ๋ถ์ด๋ค.
<script setup>
import axios from 'axios';
import { jwtDecode } from 'jwt-decode';
import { ref } from 'vue';
import { useLoginState } from '@/stores/loginState';
// ํผ ๋ฐ์ดํฐ
const formData = ref({
adminCode: '',
adminPassword: ''
});
// ์๋ฌ ๋ฉ์์ง ๊ด๋ฆฌ
const errorMessage = ref('');
const loginState = useLoginState(); // ๋ก๊ทธ์ธ ์ํ ๊ด๋ฆฌ Store
// ๋ก๊ทธ์ธ ํจ์
const loginUser = async () => {
// ํ์ ์
๋ ฅ ํ์ธ
if (!formData.value.adminCode || !formData.value.adminPassword) {
alert("์ฌ๋ฒ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.");
return;
}
try {
const response = await axios.post('http://localhost:5000/users/login', {
admin_code: formData.value.adminCode,
admin_password: formData.value.adminPassword
}, {
headers: {
'Content-Type': 'application/json',
}
});
console.log('HTTP ์๋ต ์ํ ์ฝ๋:', response.status);
console.log('์ ์ฒด ์๋ต ๋ฐ์ดํฐ:', response);
// ์๋ต ํค๋์์ ํ ํฐ ๊ฐ์ ธ์ค๊ธฐ
const token = response.headers.authorization;
if (token) {
// JWT ๋์ฝ๋ฉ
const decodedToken = jwtDecode(token);
console.log('Decoded JWT Token:', decodedToken);
// ๋ก๊ทธ์ธ ์ํ ์
๋ฐ์ดํธ
loginState.isLoggedIn = true;
loginState.adminName = decodedToken.name; // JWT์์ ์ด๋ฆ ์ถ์ถํ์ฌ ์ํ ์ค์
// ์ฑ๊ณต ๋ฉ์์ง
alert(`${decodedToken.name}๋, ํ์ํฉ๋๋ค.`);
// ๋ฉ์ธ ํ์ด์ง๋ก ์ด๋
window.location.href = '/';
} else {
errorMessage.value = '์ธ์ฆ ํ ํฐ์ด ์์ต๋๋ค. ๋ก๊ทธ์ธ ์คํจ';
alert("์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ธํด์ฃผ์ธ์.");
}
} catch (error) {
console.error('๋ก๊ทธ์ธ ์ค ์ค๋ฅ ๋ฐ์:', error);
errorMessage.value = '๋ก๊ทธ์ธ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.';
alert(errorMessage.value);
}
};
</script>
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("http://localhost:5173"); // ํด๋ผ์ด์ธํธ URL
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true); // ์ฟ ํค ์ ์ก ํ์ฉ
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
๋ก๊ทธ์์ ์์๋ ์๋ฒ์์ HttpOnly ์ฟ ํค๋ฅผ ์ญ์ ํด์ผ ํ๋ฉฐ, ํด๋ผ์ด์ธํธ์์๋ ์ฟ ํค๋ฅผ ์ง์ ์ญ์ ํ ์ ์๊ธฐ ๋๋ฌธ์, ์๋ฒ์์ ์ฟ ํค๋ฅผ ์ ๊ฑฐํ๋ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋ค.
Cookie jwtCookie = new Cookie("token", null);
jwtCookie.setHttpOnly(true);
jwtCookie.setSecure(true);
jwtCookie.setPath("/");
jwtCookie.setMaxAge(0); // ์ฟ ํค ๋ง๋ฃ
response.addCookie(jwtCookie);
๋๋ ์ฌ๊ธฐ์ refresh token์ ๋ฐํํ์ฌ ๋ ๋์ค์ ๋ด์๊ธฐ ๋๋ฌธ์ ๋ ๋์ค์์๋ ์บ์ ์ญ์ ๋ฅผ ํด์ฃผ์๋ค. ํน์ฌ๋ refresh token์ Redis์ ์ ์ฅํ๊ณ ์๋ค๋ฉด
, ๋ก๊ทธ์์ ์ ํด๋น token์ Redis์์ ์ญ์ ํด์ผ ๋ค๋ฅธ ์ธ์
์์ ํด๋น refresh token์ ์ฌ์ฉํ์ฌ ์๋ก์ด access token์ ๋ฐ๊ธ๋ฐ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์๋ค.
Refresh Token์ ํด๋ผ์ด์ธํธ์ ์ ์ฅํ์ง ์๊ณ ์๋ฒ์ ์์ ํ ์ ์ฅ์(Redis)์ ์ ์ฅ
Refresh Token์ด ์ฌ์ฉ๋๋ฉด ์ฆ์ ์ญ์ (์ผํ์ฉ)
refresh token
์ ํ ๋ฒ ์ฌ์ฉํ๊ณ ๋ง๋ฃ์ํค๋ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ด ์ข์ผ๋ฉฐ refresh token์ ๋งค๋ฒ ์๋ก ๋ฐ๊ธํ๊ณ , ์ด์ token์ ์ญ์ ํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ฌ ๋ณด์์ ๊ฐํํ ์ ์๋ค.
์๋ฅผ ๋ค์ด, ๋ก๊ทธ์์ ์ Redis์์ refresh token์ ์ญ์ ํ ํ,
์๋ก ๋ฐ๊ธ๋ token์ ์ด์ ์ ์ฌ์ฉ๋ refresh token๊ณผ ๊ด๊ณ์์ด ๋ค์ ๋ฐ๊ธ๋๋๋ก ์ค์ ํ ์ ์๋ค.
โ ๏ธ refresh token ์ผํ์ฉ ์ฒ๋ฆฌํ๋๊ฒ์ ์์ง๋ง์ !!!
Access Token์ด ๋ง๋ฃ๋จ โ 401 ์๋ฌ ๋ฐํ
ํด๋ผ์ด์ธํธ๋ Refresh Token์ ์๋ฒ๋ก ์ ์ก โ ์ Access Token ๋ฐ๊ธ
Refresh Token์ ์ฆ์ ์ญ์ ํ๊ณ ์๋ก์ด Refresh Token ๋ฐ๊ธ
JWT ์ธ์ฆ์ ๊ตฌํํ ๋ HttpOnly ์ฟ ํค๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์์ ํ๊ณ ํธ๋ฆฌํ์ง๋ง, ๋ช ๊ฐ์ง ์ค์ํ ์ฃผ์์ฌํญ์ด ์๋ค. ์ด๋ฅผ ๊ณ ๋ คํ์ง ์์ผ๋ฉด ๋ณด์์ด๋ ๊ธฐ๋ฅ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค. ์๋์ ์ฃผ์์ฌํญ์ ๋ฐ๋์ ์ฒดํฌ! โ
cookie.setSecure(true)๋ HTTPS ํ๊ฒฝ์์๋ง ์๋ํ๋ค.
์ฆ, ๋ฐฐํฌ ํ๊ฒฝ์์ secure ์์ฑ์ true๋ก ์ค์ ํ๋ ค๋ฉด ๋ฐ๋์ HTTPS๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
๊ฐ๋ฐ ํ๊ฒฝ์์๋ HTTP๋ก๋ ๋์ํ ์ ์์ง๋ง, ์ค์ ์๋น์ค์์๋ HTTPS๋ฅผ ๋ฐ๋์ ์ฌ์ฉํด์ผ ํ๋ค. HTTPS๋ ๋ฐ์ดํฐ ์ ์ก ์ค ์ํธํ๋ฅผ ๋ณด์ฅํ๋ฉฐ, ์ค๊ฐ์ ๊ณต๊ฒฉ(MITM)์ ๋ฐฉ์งํ๋ค.
๋ฐ๋ผ์, ๋ฐฐํฌ ํ๊ฒฝ์์ JWT ํ ํฐ์ HttpOnly ์ฟ ํค์ ์ ์ฅํ๋ ค๋ฉด, HTTPS ์ค์ ์ด ํ์์ด๋ค. ๐
์์:
Cookie jwtCookie = new Cookie("token", token);
jwtCookie.setHttpOnly(true); // JavaScript์์ ์ ๊ทผ ๋ถ๊ฐ
jwtCookie.setSecure(true); // HTTPS ํ๊ฒฝ์์๋ง ์ ์ฉ
jwtCookie.setPath("/");
response.addCookie(jwtCookie);
JWT๋ ๋ง๋ฃ ์๊ฐ(exp)์ ํฌํจํ ์ ์์ผ๋ฉฐ, ๋ง๋ฃ๋ ํ ํฐ์ ๊ทธ๋๋ก ์ฌ์ฉํ ๊ฒฝ์ฐ ์ธ์ฆ ์คํจ๊ฐ ๋ฐ์ํ๋ค. ๋ฐ๋ผ์, ํ ํฐ์ด ๋ง๋ฃ๋ ๊ฒฝ์ฐ ์๋ก์ด ํ ํฐ์ ๋ฐ๊ธ๋ฐ๋ ๋ก์ง์ ๊ตฌํํด์ผ ํ๋ค.
๋ง๋ฃ๋ ํ ํฐ ์ฒ๋ฆฌ ํ๋ฆ
:
ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ์์ฒญํ ๋, JWT ํ ํฐ์ด ๋ง๋ฃ๋์๋์ง ํ์ธ โก๏ธ
๋ง์ฝ ๋ง๋ฃ๋์๋ค๋ฉด โก๏ธ ํด๋ผ์ด์ธํธ๋ ์๋ก์ด ํ ํฐ์ ๋ฐ๊ธ๋ฐ๊ธฐ ์ํด ์ฌ๋ก๊ทธ์ธ์ ํ๊ฑฐ๋ โก๏ธ ๋ฆฌํ๋ ์ ํ ํฐ(refresh token)์ ์ฌ์ฉํ์ฌ ์ ํ ํฐ์ ๋ฐ๊ธ
๐ก
๋ฆฌํ๋ ์ ํ ํฐ
์ ์ฌ์ฉํ๋ฉด ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํ๋ฉด์ ์๋ก์ด JWT ํ ํฐ์ ๋ฐ๊ธ๋ฐ์ ์ ์์ผ๋ฉฐ ๋ฆฌํ๋ ์ ํ ํฐ์ ์ก์ธ์ค ํ ํฐ์ ๊ฐฑ์ ํ๋ ์ญํ ๋ง ํ๋ฉฐ, ๊ทธ ์์ฒด๋ก ์ธ์ฆ์ ๋์ ํ๋ ์ญํ ์ ํ์ง ์๋ค๋ผ๋๊ฒ์ ๊ผญ ์์๋์ โโโโ
ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์๋ก ๋ค๋ฅธ ๋๋ฉ์ธ์์ ๋์ํ ๊ฒฝ์ฐ, CORS ์ค์ ์ด ํ์ํ๋ค. ํนํ ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ์ ์ฌ์ฉํ ๋๋ credentials: true๋ฅผ ์๋ฒ์์ ์ค์ ํด์ผ ํ๋ค.
๐ฅ CORS ์ค์ ์ ์ํ ์ฃผ์ ํฌ์ธํธ ๐ฅ
Access-Control-Allow-Credentials: true:
์ฟ ํค๋ฅผ ํฌํจํ ์ธ์ฆ ์ ๋ณด๋ฅผ ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ์ ์ ๋ฌํ๋ ค๋ฉด, ์ด ํค๋๋ฅผ ๋ฐ๋์ ์ค์ !!!
Access-Control-Allow-Origin:
ํด๋ผ์ด์ธํธ์ ์ถ์ฒ(origin)๋ฅผ ์ ํํ ๋ช
์ํด์ผ ํ๋ฉฐ, * (๋ชจ๋ ์ถ์ฒ) ๋์ ์ ํํ ๋๋ฉ์ธ์ ์ค์
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("http://localhost:5173"); // ํด๋ผ์ด์ธํธ URL
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true); // ์ฟ ํค ์ ์ก ํ์ฉ
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
โจโจโจโจโจโจ withCredentials: true
Axios๋ Fetch API๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฒ๋ก ์์ฒญํ ๋, ์ฟ ํค๋ฅผ ํฌํจํ๋ ค๋ฉด withCredentials: true๋ฅผ ์ค์ ํด์ผ ํ๋ค. ๊ผญ!!!!!!!!!!!!!!!!!!!!!!!!!!!! ํด๋ผ์ด์ธํธ ์ธก์์๋ withCredentials: true๋ฅผ ์ค์ ํด์ผ HttpOnly ์ฟ ํค๊ฐ ์์ฒญ ์ ์๋์ผ๋ก ์ ์ก๋๋ค.
์์ (Axios ์ฌ์ฉ ์) :
const response = await axios.post('http://localhost:5000/users/login',
{
admin_code: formData.value.adminCode,
admin_password: formData.value.adminPassword
},
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true // ์ฟ ํค ์๋ ์ ์ก
}
);
์ด๋ ๊ฒ ์์ ์ด๋ฏธ์ง์ฒ๋ผ ๋ค๋ฅธ ํ์ด์ง๋ฅผ ๋์ด๊ฐ๋ ์ํ๊ฐ ๊ทธ๋๋ก์ ์ง๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
1. ์ฌ์ฉ์๋ `/users/login` ์๋ํฌ์ธํธ๋ก ์ธ์ฆ ์์ฒญ์ ๋ณด๋ด ๋ก๊ทธ์ธ
2. ๋ฐฑ์๋๋ JWT ํ ํฐ์ ๋ฐ๊ธํ๊ณ , ์ด๋ฅผ `HttpOnly` ์ฟ ํค์ ์ ์ฅ
3. ํ์ด์ง ์ ํ ์, Pinia ์ํ ๊ด๋ฆฌ๋ก ํ ํฐ์ ์ ์งํ๊ณ ,
`/admin/status` ์์ฒญ์ ํตํด ์ธ์ฆ ์ํ๋ฅผ ํ์ธ
4. ์ธ์ฆ ์ฑ๊ณต ์, ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ๊ถํ์ Pinia ์ํ์ ์ ์ฅ
5. `HttpOnly` ์ฟ ํค์ ์ ์ฅ๋ ํ ํฐ์ ํด๋ผ์ด์ธํธ์์ ์ ๊ทผ ๋ถ๊ฐํ๋ฏ๋ก
๊ฐ๋ฐ์ ๋๊ตฌ์์ ํ์ธํ ์ ์์
6. ์ด๋ฅผ ํตํด JWT๋ฅผ ์์ ํ๊ฒ ์ฌ์ฉํ์ฌ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์ง
console.log(localStorage.getItem('token'));
์๋ ์ด๋ ๊ฒ ๋ก์ปฌ์คํ ๋ฆฌ์ง์ ๋ด๊ณ ์ด ๋ช
๋ น์ด๋ฅผ ์ฝ์์ ์ฐ๋๋ค๋ฉด ํ ํฐ๊ฐ์ด ๋ํ๋ฌ์ง๋ง,
์ด์ ๋ ์์ ๊ฐ์ ์ฝ์๊ฐ์ ์
๋ ฅํด๋ ํ ํฐ๊ฐ ํ์ธ ๋ถ๊ฐ๋ค.
console.log(localStorage.getItem('token')); --> ํ ํฐ๊ฐ ํ์ธ ๋ช
๋ น์ด
localStorage.removeItem('token'); --> ํ ํฐ๊ฐ ์ญ์ ๋ช
๋ น์ด
HttpOnly
์ฟ ํค๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ ๋ณด์์ฑ์ ๋์ด๊ณ , ์ํ ๊ด๋ฆฌ๊ฐ ๊ฐํธํ๋ฉฐ ์ด๋ฅผ ํตํด, XSS ๊ณต๊ฒฉ์ ์๋ฐฉํ๊ณ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์์ ํ๊ฒ ์ ์งํ ์ ์๋ค. ๋ํ, ์๋ฒ ์ธก์์ ํ ํฐ์ ๊ด๋ฆฌํ๊ณ , ํด๋ผ์ด์ธํธ ์ธก์์๋ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Pinia ๋ฑ)๋ฅผ ํ์ฉํ์ฌ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ง์์ ์ผ๋ก ์ ์งํ ์ ์๋ค. ๐ก๏ธโจ
๋๋ ์ฌ์ค... ์ด๊ฑธ ์์ฑํ๋ฉด์ ๋ ๋ง์ด ์์ ๊ณผ!!! ๊ณต๋ถ๊ฐ ํ์ํ๊ฒ ๊ฐ๋ค ใ ใ .. ๊ทธ๋ฅ ์ ๋ณด์ฐจ ์ ๋๊ฒ์ด๋, ํ์คํ ์ ๋ณด๊ฐ ์๋์ฌ๋ ์ด์ฌ๋์ ์ด๋ ๊ตฌ๋ .. ๋ผ๊ณ ๋ด์คฌ์ผ๋ฉด ํ๋ค ๐ญ
ํ .. ๊ทผ๋ฐ httponly์ฟ ํค๋ก aws ๋ฐฐํฌ๋ฅผ ํ๋ ๋์ค์ ์ค๋ฅ๊ฐ ๊ณ์ ๋ฌ๋๋ฐ ๋ด๊ฐ CI/CD๋ถ์ผ๋ฅผ ๊ณต๋ถ๋ฅผ ์ ๋๋ก ์ํด๋ด์ .. ์ฃผ์ด์ง ๋ฐฐํฌ ๊ธฐ๊ฐ์ด ์์๊ธฐ ๋๋ฌธ์ ์ด๋ ดํ์ด ์ด๋ค ์ค๋ฅ์ธ์ง๋ ํ์๋ค๊ณผ ํจ๊ป ์์๋์ง๋ง ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ฐพ์ง ๋ชปํ๊ณ just jwtํ ํฐ ์ํธํ ๋ฐฉ๋ฒ์ผ๋ก ๋ฐฐํฌ ํ์๋ค .. ๐ญ๐ญ๐ญ
์ด ๋ถ๋ถ์์ ์ค๋ฅ๊ฐ ๋ฌ์ด์ 1์ผ์ ๋ ์๊ฐ์ด ๋๋ ์ด ๋์๋๋ฐ ๊ดํ ๋ณด์ ์ด์ฉ๊ตฌํ๋ฉด์ ํ์ฉํด๋ณด์๊ณ ์ค๋ ํ๋ .. ์ถ๋ค.. ๋ฏธ์ํ ๋ง์์ด ํฌ๊ธฐ๋ ํ๋ฉด์ ๋๊น์ง ๊ฐ์ด ํด๊ฒฐ๋ฐฉ์์ ์ฐพ์์ค ํ์๋ค ๋๋ถ์ ์ฃผ๋ ์ด (์กฐ๊ธ๋ง)? ๋ค์๋๊ฒ ๊ฐ๊ธฐ๋.. ๐ฅ
์ฌ์ค ๋ด๊ฐ ๋ฐฐํฌ๊น์ง ์๊ฐ์ ํ๊ณ ๋ ๊ณต๋ถํ๊ณ .. ๊ตฌํํ๋๋ผ๋ฉด... ใ ใ ์๊ณ ๊ฐ ๋ ํ์ํ ๋ฐ.. ์์ง๋ ๋ฏธ์ํ๋ฉด์ ๋์ค์ ํผ์ ํด๊ฒฐ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๋๋ก ํ๊ฒ ๋ค. ์์ผ๋ก๋ ๊ณต๋ถ ๋ ์ด์ฌํ ํ๊ณ .. ๊ฐ๋ฐํ๊ธฐ . ์ฝ์. ๐คผโโ๏ธ๐คผโโ๏ธ๐คผโโ๏ธ๐คผโโ๏ธ๐คผโโ๏ธ๐คผโโ๏ธ ๐ญ๐ญ๐ญ๐ญ
( ๋ค๋ค ๋๋ฌด ๊ณ ๋ง์.. ์ด์์ )
์ด๋ฒ ํ๋ก์ ํธ์์๋ SHA-512 ํด์ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ๋ ์ํธํ ๋ฐฉ์์ ์ฌ์ฉํ๋๋ฐ ๋ค์์๋ ์ํธํ ๋ฐฉ์์ ์ข๋ ๊ณต๋ถํด๋ณด๊ณ ์ถ๋ค.
์ฐธ๊ณ ๋ฌธํ : https://gamhongshi.tistory.com/28