์ถ์ฒ : ๋ด์ผ๋ฐฐ์์บ ํ
๋ชฉํ
์ธ์ฆ/์ธ๊ฐ์ ๋ํด ์ดํด
ํ์๊ฐ์ /๋ก๊ทธ์ธ/๋ก๊ทธ์์์ ๊ตฌํํ๊ณ ํ๋ฆ์ ์ดํด
ํ ํ๋ก์ ํธ๋ฅผ ํตํด ๊ตฌํํ ์ธ์ฆ/์ธ๊ฐ ๊ธฐ๋ฅ์ ํ๊ณ๋ฅผ ์ดํด
1. ์ธ์ฆ/ ์ธ๊ฐ
์คํ๋ง์์ ์ธ์ฆ/์ธ๊ฐ๋ฅผ ๊ด๋ฆฌํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ : ์คํ๋ง ์ํ๋ฆฌํฐ
์ธ์ฆ์ด๋? ํด๋น ์ ์ ๊ฐ ์ค์ ์ ์ ์ธ์ง ์ธ์ฆํ๋ ๊ฒ
์ธ๊ฐ๋? ํด๋น ์ ์ ๊ฐ ํน์ ๋ฆฌ์์ค์ ์ ๊ทผ์ด ๊ฐ๋ฅํ์ง ํ๊ฐ๋ฅผ ํ์ธํ๋ ๊ฒ
1) ์ธ์ฆ์ ๋ฐฉ์ : ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ธ์ฆ
-> ์ผ๋ฐ์ ์ผ๋ก ์๋ฒ - ํด๋ผ์ด์ธํธ ๊ตฌ์กฐ, ํ์ํ ๊ฒฝ์ฐ์๋ง ์ฐ๊ฒฐ๋๋ค
-> Http ํ๋กํ ์ฝ : ๋น์ฐ๊ฒฐ์ฑ ๋ฌด์ํ๋ก ํต์
๋น์ฐ๊ฒฐ์ฑ : ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ๋์ด ์์ง ์์ ~ ๋ฆฌ์์ค ์ ์ฝ๋ชฉ์
๋ฌด์ํ : ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ์ ์ฅํ์ง ์์ ~ ์์ฒญ์๋ง ์๋ต, ๊ทธ ์์ฌ๋ ์์ง ๋ชปํ๋ค.
~ ์ฟ ํค - ์ธ์ ๋ฐฉ์ : ์๋ฒ๊ฐ ํน์ ์ ์ ๊ฐ ๋ก๊ทธ์ธ๋์๋ค๋ ์ํ๋ฅผ ์ ์ฅํ๋ ๋ฐฉ์
~ JWT ๊ธฐ๋ฐ ์ธ์ฆ
JWT : ์ธ์ฆ์ ํ์ํ ์ ๋ณด๋ฅผ ์ํธํ์ํจ ํ ํฐ
*User API ์ค๊ณ
๊ธฐ๋ฅ | ๋ฉ์๋ | URL | ์์ฒญ | ์๋ต |
ํ์๊ฐ์ ํ์ด์ง | GET | /api/user/signup | signup.html | |
ํ์๊ฐ์ | POST | /api/user/signup | POST Form ํ๊ทธ { "username" : String, ”password” : String, ”email : String, ”admin” : boolean, ”adminToken" : String } |
redirect:/api/user/login |
๋ก๊ทธ์ธ ํ์ด์ง | GET | /api/user/login | login.html | |
๋ก๊ทธ์ธ | POST | /api/user/login | POST Form ํ๊ทธ { "username" : String, ”password” : String } |
redirect:/api/shop |
User ์ํฐํฐ
UserRoleEnum : ์ญํ ๋ณ ๊ถํ ์ค์
UserRepository ~JPARepository ์์
SignupRequestDto : ํ์๊ฐ์ ์ ์ ๋ ฅํ๋ ์ ๋ณด๋ฅผ ๋ฐ๋ Dto
LoginRequestDto : ๋ก๊ทธ์ธ ์ ์ ๋ ฅํ๋ ์ ๋ณด๋ฅผ ๋ฐ๋ Dto
ํ์๊ฐ์ ๊ธฐ๋ฅ
UserController : ๋ก๊ทธ์ธ/ ํ์๊ฐ์ ๊ธฐ๋ฅ ์ถ๊ฐ -> ๊ด๋ฆฌ์ ํ ํฐ ์ ์ธ -> ํ์๊ฐ์ ๋ฉ์๋ ์์ฑ ~ ์์ฑ์๋ฃ ์ ๋ก๊ทธ์ธ ํ์ด์ง ๋ฐํ
UserService : ํ์ ์ค๋ณตํ์ธ ๋ฐ ์ฌ์ฉ์ ์ญํ ํ์ธํ๋ signup ๋ฉ์๋ ์์ฑ ~ signupRequestDto์์ ๋ฐ์ user ์ ๋ณด๋ฅผ UserRepository์ ์ ์ฅ
UserRepository : ์ด๋ฆ์ผ๋ก ์ค๋ณตํ์์ ํ์ธํ๊ธฐ ์ํ Optional ํ์ findByName(String username) ๋ฉ์๋
๋ก๊ทธ์ธ ๊ธฐ๋ฅ
UserController : UserRequestDto์์ ์ ๋ณด๋ฅผ ๋ฐ์ UserService login ๋ฉ์๋๋ก ์ฐ๊ฒฐํ๋ ์ปจํธ๋กค๋ฌ. ๋ก๊ทธ์ธ ์ ๋ฉ์ธํ์ด์ง ๋ฐํํ๋ ๋ฉ์๋ ์์ฑ
Service : username ๋ฐ password๋ฅผ ํตํด ์ฌ์ฉ์ ํ์ธํ๋ ๋ฉ์๋
2. JWT๋ฅผ ์ด์ฉํ ์ธ์ฆ/์ธ๊ฐ
๋ก๊ทธ์ธ ์ฑ๊ณต ์ ํ ํฐ ๋ฐ๊ธ
๋ก๊ทธ์์
๋ก๊ทธ์ธ ํ ์ ์ ๋ง ๊ด์ฌ์ํ ๋ฑ๋ก, ์กฐํ, ์ต์ ๊ฐ ๋ฑ๋ก ๊ฐ๋ฅ
ADMIN ๊ณ์ ์ ๋ชจ๋ ์ํ ์กฐํ ๊ฐ๋ฅ
~ ๋ก๊ทธ์ธํ ์ ์ ๊ฐ ๋ฑ๋กํ ๊ด์ฌ์ํ๋ง ๋ณด์ฌ์ผํ๋๋ฐ ๋ฑ๋กํ ๋ชจ๋ ๊ด์ฌ์ํ์ด ๋ณด์ด๋ ๋ฌธ์ : ๊ด์ฌ์ํ์ ๊ฐ์ ธ์ฌ ๋๋ง๋ค ์ฌ์ฉ์ ํ์ธ์ ํ๋ค๋ฉด ๋ณด์์ํ ๋ ธ์ถ
Http ํ๋กํ ์ฝ์ ์ํ๋ฅผ ์ ์ฅํ์ง ์๋๋ค. ์ฟ ํค์ ์ธ์ ์ ์ด๋ฐ ์ํ๊ฐ ์ ์ฅ๋ ์ํ๋ก ์์ ์ ์๋๋ก ์ ์ง์ํค๋ ๋ฐฉ๋ฒ
์ฟ ํค : ํด๋ผ์ด์ธํธ์ ์ ์ฅ๋ ๋ชฉ์ ์ผ๋ก ์์ฑํ ์์ ์ ๋ณด๋ฅผ ๋ด์ ํ์ผ
์ธ์ : ์๋ฒ์์ ์ผ์ ์๊ฐ๋์ ํด๋ผ์ด์ธํธ ์ํ๋ฅผ ์ ์งํ๊ธฐ ์ํด ์ฌ์ฉ
์๋ฒ์์ ํด๋ผ์ด์ธํธ ๋ณ๋ก ์ ์ผ๋ฌด์ดํ '์ธ์ ID'๋ฅผ ๋ถ์ฌํ ํ ํด๋ผ์ด์ธํธ ๋ณ ํ์ํ ์ ๋ณด๋ฅผ ์๋ฒ์ ์ ์ฅ
์๋ฒ์์ ์์ฑํ '์ธ์ ID'๋ ํด๋ผ์ด์ธํธ์ ์ฟ ํค๊ฐ('์ธ์ ์ฟ ํค')์ผ๋ก ์ ์ฅ๋์ด ํด๋ผ์ด์ธํธ ์๋ณ์ ์ฌ์ฉ๋๋ค
- ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ 1๋ฒ ์์ฒญ
- ์๋ฒ๊ฐ ์ธ์
ID ๋ฅผ ์์ฑํ๊ณ , ์๋ต ํค๋์ ์ ๋ฌ
- ์ธ์ ID ํํ: "SESSIONID = 12A345"
- ํด๋ผ์ด์ธํธ๊ฐ ์ฟ ํค๋ฅผ ์ ์ฅ ('์ธ์ ์ฟ ํค')
- ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ 2๋ฒ ์์ฒญ
- ์ฟ ํค๊ฐ (์ธ์ ID) ํฌํจํ์ฌ ์์ฒญ
- ์๋ฒ๊ฐ ์ธ์ ID ๋ฅผ ํ์ธํ๊ณ , 1๋ฒ ์์ฒญ๊ณผ ๊ฐ์ ํด๋ผ์ด์ธํธ์์ ์ธ์ง
์ฟ ํค | ์ธ์ | |
์ค๋ช | ํด๋ผ์ด์ธํธ์ ์ ์ฅ๋ ๋ชฉ์ ์ผ๋ก ์์ฑํ ์์ ์ ๋ณด๋ฅผ ๋ด์ ํ์ผ | ์๋ฒ์์ ์ผ์ ์๊ฐ ๋์ ํด๋ผ์ด์ธํธ ์ํ๋ฅผ ์ ์งํ๊ธฐ ์ํด ์ฌ์ฉ |
์ ์ฅ ์์น | ํด๋ผ์ด์ธํธ(์น ๋ธ๋ผ์ฐ์ ) | ์น ์๋ฒ |
์ฌ์ฉ ์ | ์ฌ์ดํธ ํ์ ์ '์ค๋ ๋ค์๋ณด์ง ์๊ธฐ' ์ ๋ณด ์ ์ฅ | ๋ก๊ทธ์ธ ์ ๋ณด ์ ์ฅ |
๋ง๋ฃ ์์ | ์ฟ ํค๋ฅผ ์ ์ฅํ ๋ ๋ง๋ฃ ์์ ์ค์ ๊ฐ๋ฅ (๋ธ๋ผ์ฐ์ ์ข ๋ฃ ํ์๋ ์ ์ง ๊ฐ๋ฅ) |
์กฐ๊ฑด ์ค ํ๋๋ฅผ ๋ง์กฑํ ๊ฒฝ์ฐ ๋ง๋ฃ 1. ๋ธ๋ผ์ฐ์ ์ข ๋ฃ 2. ํด๋ผ์ด์ธํธ ๋ก๊ทธ์์ 3. ์๋ฒ๊ฐ ์ค์ ํ ์ ์ง๊ธฐ๊ฐ๊น์ง ํด๋น ํด๋ผ์ด์ธํธ์ ์ฌ์์ฒญ์ด ์๋ ๊ฒฝ์ฐ |
์ฉ๋ ์ ํ | ๋ธ๋ผ์ฐ์ ๋ณ๋ก ์์ด (ํฌ๋กฌ) ๋๋ฉ์ธ ๋น 180๊ฐ (ํฌ๋กฌ) ์ฟ ํค ํ๋๋น 4kb |
๊ฐ์ ์ ํ ์์ (์น ์๋ฒ์ ์ธ์ ์ ์ฅ์ ํฌ๊ธฐ๊น์ง ์ ์ฅ) |
๋ณด์ | ์ทจ์ฝ : ํด๋ผ์ด์ธํธ ์ชฝ์์ ์ฟ ํค ์ ๋ณด๋ฅผ ์ฝ๊ฒ ๋ณ๊ฒฝ, ์ญ์ ๋ฐ ๊ฐ๋ก์ฑ๊ธฐ ๊ฐ๋ฅ | ๋น๊ต์ ์์ : ์๋ฒ์ ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ ์๋์ ์ผ๋ก ์์ |
JWT : Json ํฌ๋งท์ ์ฌ์ฉํด ์ฌ์ฉ์์ ์์ฑ์ ์ ์ฅํ๋ ์น ํ ํฐ ~ ์ ์ฅ๋ ์ฟ ํค
JWT๊ฐ ํ์ํ ์ด์ !
์๋ฒ๊ฐ 2๋ ์ด์์ธ ๊ฒฝ์ฐ ๋ก๋๋ฐธ๋ฐ์๋ฅผ ํตํด ์ ์์ด ์ด๋ฃจ์ด์ง๋๋ฐ, ์ฟ ํค๋ฅผ ํตํฉ ๊ด๋ฆฌํ์ง ์์ผ๋ฉด ํผ์ ์ด ์๊น
ํด๊ฒฐ๋ฐฉ๋ฒ
- JWT ์ฅ/๋จ์
์ฅ์
= ๋์ ์ ์์๊ฐ ๋ง์ ๋ ์๋ฒ ์ธก ๋ถํ ๋ฎ์ถค
= Client, Sever ๊ฐ ๋ค๋ฅธ ๋๋ฉ์ธ์ ์ฌ์ฉํ ๋ ์ฌ์ฉํ๊ธฐ ์ฝ๋ค
์) ์นด์นด์ค OAuth2 ๋ก๊ทธ์ธ ์ JWT Token ์ฌ์ฉ
๋จ์
๊ตฌํ์ ๋ณต์ก๋ ์ฆ๊ฐ
=J WT ์ ๋ด๋ ๋ด์ฉ์ด ์ปค์ง ์๋ก ๋คํธ์ํฌ ๋น์ฉ ์ฆ๊ฐ (ํด๋ผ์ด์ธํธ → ์๋ฒ)
= ๊ธฐ ์์ฑ๋ JWT ๋ฅผ ์ผ๋ถ๋ง ๋ง๋ฃ์ํฌ ๋ฐฉ๋ฒ์ด ์์
~ ๋ธ๋ผ์ฐ์ ์์ ์ญ์ ํ๊ฑฐ๋ ์ค์ ๋ ๊ธฐํ์ ๋ง๋ฃ๋๋ ๋ฑ์ด ์๋๋ฉด ๊ณ์ ์ ์ง๋จ
= Secret key ์ ์ถ ์ JWT ์กฐ์ ๊ฐ๋ฅ
JWT ํ๋ฆ
1. ๋ก๊ทธ์ธ ์ฑ๊ณต ->
๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ Secret Key ํตํด JWT๋ก ์ํธํ -> JWT๋ฅผ ํด๋ผ์ด์ธํธ๋ก ์ ๋ฌ
**Authorization: BEARER** <JWT> <!-- ์๋ต Header์ ์๋ ํํ๋ก JWT ์ ๋ฌ -->
ex)
**Authorization: BEARER** eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzcGFydGEiLCJVU0VSTkFNRSI6IuultO2DhOydtCIsIlVTRVJfUk9MRSI6IlJPTEVfVVNFUiIsIkVYUCI6MTYxODU1Mzg5OH0.9WTrWxCWx3YvaKZG14khp21fjkU1VjZV4e9VEf05Hok
-> ํด๋ผ์ด์ธํธ์์ JWT ์ ์ฅ(์ฟ ํค, ๋ก์ปฌ ์ ์ฅ์ ๋ฑ)
2. ํด๋ผ์ด์ธํธ์ JWT ์ธ์ฆ ->
JWT๋ฅผ API ์์ฒญ ์๋ง๋ค Header์ ํฌํจ ->
์๋ฒ : ํด๋ผ์ด์ธํธ๊ฐ ์ ๋ฌํ JWT๋ฅผ Secret Key๋ฅผ ์ฌ์ฉํด ์์กฐ๊ฒ์ฆ + ์ ํจ๊ธฐ๊ฐ ๊ฒ์ฌ ->
๊ฒ์ฆ ์ฑ๊ณต์ : JWT์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ ํ์ธ
JWT ๊ตฌ์กฐ
์ฝ๊ฒ ํ๋ฌธ์ผ๋ก ๋ณตํธํ ๊ฐ๋ฅ, Secret Key๊ฐ ์์ด์ผ๋ง ์์ ์ด ๊ฐ๋ฅ ~ JWT๋ ReadOnly ๋ฐ์ดํฐ
JWT ๊ตฌํ ์ค์ต
dependencies์ JWT depedency ์ถ๊ฐ
application.properties์ JWT ์ํฌ๋ฆฟ ํค ์ถ๊ฐ ~ Base64๋ก ์ธ์ฝ๋ฉํ ๊ฐ
ํ ํฐ ์์ฑ์ ํ์ํ ๊ฐ
// Header KEY ๊ฐ
public static final String AUTHORIZATION_HEADER = "Authorization";
// ์ฌ์ฉ์ ๊ถํ ๊ฐ์ KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token ์๋ณ์ ~ ํ ํฐ ์์ ๋ถ์ธ๋ค
private static final String BEARER_PREFIX = "Bearer ";
// ํ ํฐ ๋ง๋ฃ์๊ฐ
private static final long TOKEN_TIME = 60 * 60 * 1000L;
@Value("${jwt.secret.key}")
private String secretKey; // appelication.properties์ ๊ฐ์ ๊ฐ์ ธ์จ๋ค
private Key key; // token์ ๋ง๋ค ๋ ๋ฃ์ด์ค ํค ๊ฐ
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@PostConstruct // ๊ตฌ๊ธ๋ง ํด๋ณด๊ธฐ
public void init() { // ๊ฐ์ฒด ์์ฑ์ ์ด๊ธฐํ ํจ์
byte[] bytes = Base64.getDecoder().decode(secretKey); // ๋์ฝ๋ฉํ ๋ฐ์ดํธ๋ฅผ ๋ฃ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
key = Keys.hmacShaKeyFor(bytes);
}
// header ํ ํฐ์ ๊ฐ์ ธ์ค๊ธฐ
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(7);
// AUTHORIZATION.header๋ฅผ ๊ฐ์ ธ์จ ๋ค ํ ํฐ๊ณผ ๊ด๋ จ์๋ ์ 7์๋ฆฌ ๊ธ์๋ฅผ ๋นผ๊ณ ๋ฐํ
}
return null;
}
// ํ ํฐ ์์ฑ
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // ๊ณต๊ฐ, ๊ณต๊ฐ ๋ถ๋ถ์ ์ด๋ฆ์ ๋ฃ๋๋ค
.claim(AUTHORIZATION_KEY, role)
// ์ฌ์ฉ์ ๊ถํ, ๊ถํ์ ๊ฐ์ ธ์ฌ ๋๋ Auth ํค๋ฅผ ์ฌ์ฉํด์ผํ๋ค.
.setExpiration(new Date(date.getTime() + TOKEN_TIME))
// ์ ํจ๊ธฐ๊ฐ ์ค์ , ์ง๊ธ์ผ๋ก๋ถํฐ ์ธ์ ๊น์ง
.setIssuedAt(date)
// ์ธ์ ๋ฐ๊ธํ๋์ง
.signWith(key, signatureAlgorithm)
// Key ๊ฐ์ฒด์ ์ด๋ค ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ํธํํ ์ง ๊ฒฐ์ ํ๋ ๊ฒ ~ ์ค์ต : HS256
.compact();
// String ํ์
์ JWT ํ ํฐ์ผ๋ก ๋ฐํ๋๋ค.
}
// ํ ํฐ ๊ฒ์ฆ
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException e) {
log.info("Invalid JWT signature, ์ ํจํ์ง ์๋ JWT ์๋ช
์
๋๋ค.");
} catch (ExpiredJwtException e) {
log.info("Expired JWT token, ๋ง๋ฃ๋ JWT token ์
๋๋ค.");
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token, ์ง์๋์ง ์๋ JWT ํ ํฐ ์
๋๋ค.");
} catch (IllegalArgumentException e) {
log.info("JWT claims is empty, ์๋ชป๋ JWT ํ ํฐ ์
๋๋ค.");
}
return false;
}
// ํ ํฐ์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
// ์ธ์ฆํ ํ ํฐ์์ ์ฌ์ฉ์ ์ ๋ณด body๋ฅผ ๊ฐ์ ธ์จ๋ค
// ์์์ ํ ํฐ ๊ฒ์ฆ์ ํ๊ธฐ๋๋ฌธ์ ๋ณ๋์ try catch๋ฅผ ์ถ๊ฐํ์ง ์์ ํํ
}
JWT ์ ์ฉ
User API ์ค๊ณ ๋ณ๊ฒฝ
๊ธฐ๋ฅ | ๋ฉ์๋ | URL | ์์ฒญ | ์๋ต |
๋ก๊ทธ์ธ | POST | api/user/login | { "username" : String, ”password” : String } ajaxํ์์ผ๋ก ๋ณด๋ |
Header Authorization : Bearer <JWT> success |
์ฟ ํค ์ ์ฅ๋๋ ๋ชจ์ต ์ค์ต
์์ฑ๋ JWT ๋์ฝ๋ฉ ์ค์ต
+ ์ฃผ์ : JWT Payload์ ๋น๋ฐ๋ฒํธ ๋ฑ์ ์ ๋ณด๋ ๋ฃ์ผ๋ฉด ์๋๋ค. ~ JWT์ ๋๊ตฌ๋ ๋ณตํธํํ ์ ์๊ธฐ ๋๋ฌธ์
JWT๋ฅผ ์ฌ์ฉํ ๊ด์ฌ์ํ ์กฐํํ๊ธฐ
User์ Product ๊ฐ ์ฐ๊ด๊ด๊ณ ์ค์ ์ด ํ์ํ๋ค
๊ด์ฌ์ํ ์กฐํํ ๋ ํ ํฐ ๋ณด๋ด๊ธฐ
๊ด์ฌ์ํ ์ถ๊ฐํ ๋ ํ ํฐ ๋ณด๋ด๊ธฐ
๊ด์ฌ์ํ ์ต์ ๊ฐ ์ถ๊ฐํ ๋ ํ ํฐ ๋ณด๋ด๊ธฐ
'Spring๐' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
OAuth2 (0) | 2022.12.27 |
---|---|
์คํ๋ง ์๋ จ : Project MySelectShop (3) (0) | 2022.12.15 |
์คํ๋ง ์๋ จ : Project MySelectShop (1) (0) | 2022.12.13 |
T ์์นด๋ฐ๋ฏธ : JPA ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์ด (2) (1) | 2022.12.13 |
T ์์นด๋ฐ๋ฏธ : JPA ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์ด (1) (0) | 2022.12.13 |