feat: project skeleton + DB migrations V1-V12 + ga-common framework

- Gradle multi-module: ga-common, ga-core, ga-api, ga-batch, ga-admin
- Flyway V1-V12: org/product/rule/receive/ledger/settle/batch/code/menu/system + seed
- ga-common (complete):
  - model: ApiResponse, PageResponse, SearchParam, TreeNode
  - exception: ErrorCode, BizException, GlobalExceptionHandler
  - annotation + aop: @RequirePermission, @DataChangeLog, ApiLog
  - auth + security: JWT, SecurityConfig
  - mybatis: BaseMapper, EncryptTypeHandler, JsonTypeHandler, AuditInterceptor
  - code (Redis cached) / menu (RBAC tree) / file / system / notification
  - excel: SXSSF+Cursor export, SAX+batch import (1M/700K rows)
  - util: Date/Money/Mask/Encrypt/Security
This commit is contained in:
GA Pro
2026-05-09 21:29:18 +09:00
parent c716f5eb70
commit cef4e48e27
100 changed files with 4914 additions and 0 deletions
@@ -0,0 +1,93 @@
package com.ga.common.auth;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* JWT 발급/검증.
*/
@Slf4j
@Component
public class JwtTokenProvider {
private final SecretKey key;
private final long accessExpire;
private final long refreshExpire;
public JwtTokenProvider(
@Value("${ga.jwt.secret}") String secret,
@Value("${ga.jwt.access-token-expire:7200000}") long accessExpire,
@Value("${ga.jwt.refresh-token-expire:604800000}") long refreshExpire
) {
byte[] raw = secret.getBytes(StandardCharsets.UTF_8);
// 256비트 미만이면 안전하게 패딩
if (raw.length < 32) {
byte[] padded = new byte[32];
System.arraycopy(raw, 0, padded, 0, raw.length);
raw = padded;
}
this.key = Keys.hmacShaKeyFor(raw);
this.accessExpire = accessExpire;
this.refreshExpire = refreshExpire;
}
public String createAccessToken(Long userId, String loginId, List<String> roles) {
return create(userId, loginId, roles, accessExpire, "ACCESS");
}
public String createRefreshToken(Long userId, String loginId) {
return create(userId, loginId, List.of(), refreshExpire, "REFRESH");
}
private String create(Long userId, String loginId, List<String> roles, long expireMs, String type) {
Date now = new Date();
return Jwts.builder()
.subject(loginId)
.claims(Map.of(
"uid", userId,
"roles", roles,
"type", type
))
.issuedAt(now)
.expiration(new Date(now.getTime() + expireMs))
.signWith(key)
.compact();
}
public Claims parse(String token) {
try {
return Jwts.parser().verifyWith(key).build()
.parseSignedClaims(token).getPayload();
} catch (ExpiredJwtException e) {
throw new BizException(ErrorCode.TOKEN_EXPIRED);
} catch (JwtException | IllegalArgumentException e) {
throw new BizException(ErrorCode.TOKEN_INVALID);
}
}
public boolean isValid(String token) {
try {
parse(token);
return true;
} catch (Exception e) {
return false;
}
}
public long getAccessExpire() { return accessExpire; }
public long getRefreshExpire() { return refreshExpire; }
}