[SSO] 추가

This commit is contained in:
2026-03-03 15:16:59 +09:00
parent 0cf29d4f52
commit f2fe4f10c1
18 changed files with 363 additions and 243 deletions
+2
View File
@@ -38,3 +38,5 @@ out/
### Claude Code ###
.claude/
AGENTS.md
-162
View File
@@ -1,162 +0,0 @@
# CLAUDE.md - alist API 프로젝트
## Claude 작업 규칙 (중요!)
### 코드 수정 시 반드시 따를 것
1. **절대로 바로 파일을 수정하지 말 것**
2. **먼저 수정 방향과 계획을 설명**
3. **긴 소스 코드를 보여주기 전에 먼저 물어보기**
- "코드 보여드릴까요?" 또는 "직접 수정하시겠어요?"
- 사용자가 원하면 코드 보여주기 (사용자가 직접 수정할 수도 있도록)
4. **파일 작성/수정은 사용자가 요청할 때만**
### 작업 순서 예시
```
1. "CORS 설정을 yml로 분리하겠습니다."
2. "수정 내용:"
- CorsProperties.java 생성 필요
- CorsConfig.java 수정 필요
- application-local.yaml 수정 필요
3. "코드 보여드릴까요? 아니면 직접 수정하시겠어요?"
4. (사용자가 "보여줘"라고 하면) 코드 보여주기
5. (사용자가 "작성해줘"라고 하면) 파일 수정 진행
```
### 주의사항
- **짧은 설명(수정 위치, 수정 방향)은 바로 보여줘도 됨**
- **전체 메서드/클래스 같은 긴 코드는 반드시 물어본 후 보여주기**
- **화면이 너무 길어지는 것 방지**
## 프로젝트 개요
- **프로젝트명**: alist API
- **그룹**: com.alist
- **포트**: 8106
- **빌드 결과물**: `api.jar`
## 기술 스택
- **Java**: 21
- **Framework**: Spring Boot 3.5.10
- **빌드 도구**: Gradle
- **DB**: MariaDB
- **ORM**: MyBatis (mapper XML: `classpath:mapper/**/*.xml`)
- **인증**: JWT (jjwt 0.11.5) + Spring Security
- **API 문서**: Swagger (springdoc-openapi 2.6.0)
- **기타**: Lombok, Validation, Actuator, log4jdbc
## 패키지 구조
```
com.alist.api
├── common
│ ├── response/ # ApiResponse, ApiResponseCode
│ └── utils/ # 공통 유틸리티
├── config
│ ├── jwt/ # JWT 필터, 핸들러, Provider
│ ├── exception/ # GlobalExceptionHandler
│ ├── properties/ # JwtProperties 등
│ ├── SecurityConfig.java
│ └── OpenApiConfig.java
└── modules
└── {도메인}/ # Controller, Service, Mapper, DTO
```
## 프로파일
| 프로파일 | 설명 |
|---------|------|
| `local` | 로컬 개발 환경 |
| `pjt` | 프로젝트(개발) 환경 |
## 보안 구조
- **Swagger**: `/v3/api-docs/**`, `/swagger-ui/**` → HTTP Basic 인증 (InMemory)
- **API**: JWT Bearer 토큰 인증 (Stateless)
- **공개 경로**: `/`, `/actuator/health`, `/auth/**`, `/api/user/signup`
## 응답 코드 규칙
`ApiResponseCode` enum으로 관리. 주요 코드:
| 코드 | 메시지 | HTTP Status | 용도 |
|------|--------|-------------|------|
| `CODE_200` | 성공 | 200 OK | 일반 성공 |
| `CODE_400` | 잘못된 요청 | 400 Bad Request | 일반 클라이언트 오류 |
| `CODE_401` | 인증 필요 합니다. | 401 Unauthorized | 인증 없음 |
| `CODE_403` | 접근 권한 필요 합니다. | 403 Forbidden | 권한 없음 |
| `CODE_404` | 페이지를 찾을 수 없습니다. | 404 Not Found | 리소스 없음 |
| `CODE_405` | 잘못된 요청입니다. 요청 방식을 확인해 주세요. | 405 Method Not Allowed | 메서드 불일치 |
| `CODE_500` | 요청을 처리하는 중 오류가 발생했습니다. | 500 Internal Server Error | 서버 오류 |
| `CODE_2001` | {0} 정보 조회에 성공하였습니다. | 200 OK | 단건 조회 성공 |
| `CODE_2002` | {0} 등록 되었습니다. | 201 Created | 등록 성공 |
| `CODE_2003` | 조회된 정보가 없습니다. | 200 OK | 조회 결과 없음 |
| `CODE_2004` | 중복된 {0} 정보 입니다. | 409 Conflict | 중복 데이터 |
| `CODE_4001` | 입력값을 확인해주세요. | 400 Bad Request | `@Valid` / 바인딩 / 타입오류 / JSON 파싱 실패 |
| `CODE_4003` | 필수 요청 파라미터가 누락되었습니다. | 400 Bad Request | 필수 파라미터 누락 |
- `{0}` 자리에 대상명 삽입 (예: `CODE_2001` → "회원 정보 조회에 성공하였습니다.")
## 빌드 및 실행
```bash
# 빌드
./gradlew bootJar
# 로컬 실행
./gradlew bootRun --args='--spring.profiles.active=local'
# JAR 실행
java -jar build/libs/api.jar --spring.profiles.active=local
```
## Swagger 접속
- URL: `http://localhost:8106/swagger-ui.html`
- 인증: `swagger.login.id` / `swagger.login.password` (환경별 yaml에 설정)
## 코드 작성 규칙
- 응답은 `ApiResponse<T>` 래퍼 사용
- 응답 코드는 `ApiResponseCode` enum 사용
- MyBatis Mapper XML은 `src/main/resources/mapper/` 하위에 작성
- 카멜케이스 자동 변환 활성화 (`map-underscore-to-camel-case: true`)
- 새 모듈 추가 시: `modules/{moduleName}/` 하위에 Controller, Service, Mapper, dto/, vo/ 구조로 생성
- Mapper XML은 `resources/mapper/{moduleName}/` 에 위치
## MyBatis 규칙
- Mapper XML 위치: `src/main/resources/mapper/**/*.xml`
- `map-underscore-to-camel-case: true` 설정 → DB 컬럼 `user_idx` → Java 필드 `userIdx` 자동 매핑
- Mapper 인터페이스와 XML의 namespace, id 반드시 일치시킬 것
- VO: DB 조회 결과 매핑용 / DTO: 서비스 레이어 간 데이터 전달용 / Form: 컨트롤러 입력 검증용
## CI/CD
- **Jenkins**: `Jenkinsfile.pjt`
- **Docker**: `Dockerfile`
- **배포 스크립트**: `deploy/` 디렉토리
## 개발 서버 (pjt)
### 도메인
- **API**: `api-alist.pjt.kr`
- **파일(업로드)**: `file-alist.pjt.kr`
- **Swagger**: `https://api-alist.pjt.kr/swagger-ui/index.html`
### Docker
- **레지스트리**: `registry.pjt.kr`
- **이미지**: `registry.pjt.kr/alist/api`
- **컨테이너명**: `alist-api`
- **포트**: `127.0.0.1:8106->8106/tcp`
### 서버 디렉토리 (`/srv/project/alist/`)
```
/srv/project/alist/
├── compose/ # docker-compose 파일
├── data/ # 데이터
├── env/ # 환경변수 파일
├── logs/ # 로그
├── scripts/ # 배포/운영 스크립트
└── uploads/ # 업로드 파일 (file-alist.pjt.kr 루트)
```
### Nginx
- `file-alist.pjt.kr``/srv/project/alist/uploads` (정적 파일 서빙)
- HTTP(80) → HTTPS(301) 리다이렉트
- SSL: Let's Encrypt
- 직접 접근 차단 (`allow 127.0.0.1; deny all;`)
### 파일 업로드
- 업로드 저장 경로: `/srv/project/alist/uploads/`
- 업로드 파일 접근 URL: `https://file-alist.pjt.kr/{파일경로}`
+4
View File
@@ -56,6 +56,10 @@ dependencies {
// health
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// Redis (SSO 세션 공유)
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.session:spring-session-data-redis'
}
tasks.named('test') {
@@ -0,0 +1,33 @@
package com.alist.api.common.utils;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseCookie;
public class SessionUtil {
// 쿠키 삭제
public static void expireCookie(HttpServletResponse response, String name, String cookieDomain, boolean cookieSecure) {
ResponseCookie cookie = ResponseCookie.from(name, "")
.domain(cookieDomain)
.path("/")
.httpOnly(true)
.secure(cookieSecure)
.sameSite("Lax")
.maxAge(0)
.build();
response.addHeader("Set-Cookie", cookie.toString());
}
// 쿠키 입력
public static void addTokenCookie(HttpServletResponse response, String name, String value, String cookieDomain, boolean cookieSecure, long maxAgeSeconds) {
ResponseCookie cookie = ResponseCookie.from(name, value)
.domain(cookieDomain)
.path("/")
.httpOnly(true)
.secure(cookieSecure)
.sameSite("Lax")
.maxAge(maxAgeSeconds)
.build();
response.addHeader("Set-Cookie", cookie.toString());
}
}
@@ -0,0 +1,37 @@
package com.alist.api.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 7200) // 2시간
public class RedisSessionConfig {
@Value("${cookie.secure}")
private boolean cookieSecure;
@Value("${cookie.domain}")
private String cookieDomain;
@Value("${cookie.name}")
private String cookieName;
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
// SSO를 위한 쿠키 설정
serializer.setCookieName(cookieName);
serializer.setDomainName(cookieDomain); // 모든 서브도메인에서 공유
serializer.setCookiePath("/");
serializer.setUseSecureCookie(cookieSecure); // 환경별 설정 (local: false, pjt: true)
serializer.setUseHttpOnlyCookie(true); // XSS 방지
serializer.setSameSite("Lax"); // CSRF 방지
return serializer;
}
}
@@ -55,7 +55,7 @@ public class SecurityConfig {
http
.cors(Customizer.withDefaults())
.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// SSO를 위해 Spring Session 사용 (sessionManagement 설정 제거하여 기본값 사용)
.exceptionHandling(ex -> ex
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler())
@@ -24,9 +24,7 @@ public class DynamicCorsFilter extends OncePerRequestFilter {
private final CorsAllowedOriginsCache corsCache;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String origin = request.getHeader("Origin");
@@ -22,11 +22,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String token = resolveToken(request);
@@ -48,14 +44,28 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
}
private String resolveToken(HttpServletRequest request) {
// 1) Authorization: Bearer xxx 우선
String bearer = request.getHeader(HttpHeaders.AUTHORIZATION);
if (bearer == null) return null;
// "Bearer " 뒤 토큰만 추출
if (bearer.startsWith("Bearer ")) {
if (bearer != null && bearer.startsWith("Bearer ")) {
String token = bearer.substring(7).trim();
return token.isEmpty() ? null : token;
if (!token.isEmpty()) {
return token;
}
}
// 2) 쿠키 fallback (브라우저용)
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("accessToken".equals(cookie.getName())) {
String token = cookie.getValue();
if (token != null && !token.isBlank()) {
return token.trim();
}
}
}
}
return null;
}
}
@@ -57,7 +57,7 @@ public class JwtTokenProvider {
/* 리프레시 토큰 생성 */
public String createRefreshToken(long userTokenIdx) {
Instant now = Instant.now();
Instant expiry = now.plusSeconds(jwtProperties.getAccessTokenValiditySeconds());
Instant expiry = now.plusSeconds(jwtProperties.getRefreshTokenValiditySeconds());
return Jwts.builder()
.setSubject(String.valueOf(userTokenIdx))
@@ -1,13 +1,17 @@
package com.alist.api.modules.auth;
import com.alist.api.common.utils.SessionUtil;
import com.alist.api.modules.auth.dto.TestLoginDto;
import com.alist.api.modules.auth.dto.TokenDto;
import com.alist.api.modules.auth.service.TestAuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import com.alist.api.common.response.ApiResponse;
import com.alist.api.common.response.ApiResponseCode;
@@ -16,14 +20,35 @@ import com.alist.api.modules.auth.form.TokenForm;
import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@Tag(name = "00. 엑세스 토큰 발급", description = "강제 엑세스 토큰 발급 (JSON 전달, 테스트용)")
@RestController
@RequestMapping("/auth")
public class AuthController {
private final JwtTokenProvider jwtTokenProvider;
public AuthController(JwtTokenProvider jwtTokenProvider) {
@Value("${cookie.secure}")
private boolean cookieSecure;
@Value("${cookie.domain}")
private String cookieDomain;
@Value("${cookie.name}")
private String cookieName;
@Value("${jwt.access-token-validity-seconds}")
private long accessTokenValiditySeconds;
@Value("${jwt.refresh-token-validity-seconds}")
private long refreshTokenValiditySeconds;
private final JwtTokenProvider jwtTokenProvider;
private final TestAuthService testAuthService;
public AuthController(JwtTokenProvider jwtTokenProvider, TestAuthService testAuthService) {
this.jwtTokenProvider = jwtTokenProvider;
this.testAuthService = testAuthService;
}
@Operation(
@@ -37,4 +62,77 @@ public class AuthController {
return ApiResponse.entity(tokenDto, ApiResponseCode.CODE_2001, "엑세스 토큰");
}
@Operation(
summary = "리프레시 토큰으로 엑세스 토큰 재발급",
description = "리프레시 토큰으로 엑세스 토큰 재발급"
)
@PostMapping("/refresh")
public ResponseEntity<ApiResponse<Map<String, Object>>> refresh(@CookieValue(name = "refreshToken", required = false) String refreshToken, HttpServletResponse response) {
TestLoginDto testLogin = testAuthService.refreshByToken(refreshToken);
if (testLogin == null) {
return ApiResponse.entity(Map.of("refreshed", false), ApiResponseCode.CODE_401);
}
SessionUtil.addTokenCookie(response, "accessToken", testLogin.getAccessToken(), cookieDomain, cookieSecure, accessTokenValiditySeconds);
SessionUtil.addTokenCookie(response, "refreshToken", testLogin.getRefreshToken(), cookieDomain, cookieSecure, refreshTokenValiditySeconds);
return ApiResponse.entity(Map.of("refreshed", true), ApiResponseCode.CODE_200);
}
@Operation(
summary = "SSO 로그인 상태 확인",
description = "SSO 인증 받은 후 현재 세션이 있는지 확인"
)
@GetMapping("/loginChecked")
public ResponseEntity<ApiResponse<Map<String, Object>>> loginChecked(HttpServletRequest request) {
Map<String, Object> sessionInfo = new HashMap<>();
HttpSession session = request.getSession(false); // 새 세션 생성 금지
if (session == null) {
sessionInfo.put("loggedIn", false);
return ApiResponse.entity(sessionInfo, ApiResponseCode.CODE_200);
}
Integer userIdx = (Integer) session.getAttribute("userIdx");
if (userIdx == null) {
sessionInfo.put("loggedIn", false);
return ApiResponse.entity(sessionInfo, ApiResponseCode.CODE_200);
}
sessionInfo.put("loggedIn", true);
sessionInfo.put("userIdx", userIdx);
sessionInfo.put("userTokenIdx", session.getAttribute("userTokenIdx"));
sessionInfo.put("userRole", session.getAttribute("userRole"));
sessionInfo.put("userId", session.getAttribute("userId"));
sessionInfo.put("sessionId", session.getId());
return ApiResponse.entity(sessionInfo, ApiResponseCode.CODE_200);
}
@Operation(
summary = "로그아웃",
description = "로그아웃시 redis 에 세션 삭제 합니다."
)
@PostMapping("/logout")
public ResponseEntity<ApiResponse<Map<String, Object>>> logout(HttpServletRequest request, HttpServletResponse response, @CookieValue(name = "refreshToken", required = false) String refreshToken) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
testAuthService.clearRefreshToken(refreshToken);
SessionUtil.expireCookie(response, cookieName, cookieDomain, cookieSecure);
SessionUtil.expireCookie(response, "accessToken", cookieDomain, cookieSecure);
SessionUtil.expireCookie(response, "refreshToken", cookieDomain, cookieSecure);
Map<String, Object> result = new HashMap<>();
result.put("message", "로그아웃되었습니다.");
return ApiResponse.entity(result, ApiResponseCode.CODE_200);
}
}
@@ -2,31 +2,37 @@ package com.alist.api.modules.auth;
import com.alist.api.common.response.ApiResponse;
import com.alist.api.common.response.ApiResponseCode;
import com.alist.api.common.utils.SessionUtil;
import com.alist.api.modules.auth.dto.TestLoginDto;
import com.alist.api.modules.auth.form.TestApikeyLoginForm;
import com.alist.api.modules.auth.form.TestLoginForm;
import com.alist.api.modules.auth.service.TestAuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
@Tag(name = "01. 테스트 인증", description = "테스트 사용자 로그인 및 API Key 인증 관련 API")
@RestController
@RequestMapping("/test")
public class TestAuthController {
private final TestAuthService testAuthService;
@Value("${cookie.secure}")
private boolean cookieSecure;
@Value("${cookie.domain}")
private String cookieDomain;
@Value("${jwt.access-token-validity-seconds}")
private long accessTokenValiditySeconds;
@Value("${jwt.refresh-token-validity-seconds}")
private long refreshTokenValiditySeconds;
public TestAuthController(TestAuthService testAuthService) {
this.testAuthService = testAuthService;
}
@@ -36,30 +42,24 @@ public class TestAuthController {
description = "아이디/비밀번호로 로그인하고 HttpOnly 쿠키로 토큰을 전달합니다. (웹 브라우저용)"
)
@PostMapping("/testLogin")
public ResponseEntity<ApiResponse<TestLoginDto>> testUserLogin(
@Valid @RequestBody TestLoginForm testLoginForm,
HttpServletResponse response) {
public ResponseEntity<ApiResponse<TestLoginDto>> testUserLogin(@Valid @RequestBody TestLoginForm testLoginForm, HttpServletResponse response, HttpSession session) {
TestLoginDto testLoginDto = testAuthService.selectTestLogin(testLoginForm.testLoginDto());
if (testLoginDto.getResultCode() == 2003) {
return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2003);
}
// SSO를 위한 세션에 사용자 정보 저장
session.setAttribute("userIdx", testLoginDto.getUserIdx());
session.setAttribute("userTokenIdx", testLoginDto.getUserTokenIdx());
session.setAttribute("userId", testLoginDto.getId());
session.setAttribute("userRole", "USER");
// accessToken 쿠키 설정
Cookie accessCookie = new Cookie("accessToken", testLoginDto.getAccessToken());
accessCookie.setHttpOnly(true); // JS 접근 차단 (XSS 방지)
accessCookie.setSecure(cookieSecure); // HTTPS 전송 (환경별 설정)
accessCookie.setPath("/");
accessCookie.setMaxAge(3600); // 1시간
response.addCookie(accessCookie);
SessionUtil.addTokenCookie(response, "accessToken", testLoginDto.getAccessToken(), cookieDomain, cookieSecure, accessTokenValiditySeconds);
// refreshToken 쿠키 설정
Cookie refreshCookie = new Cookie("refreshToken", testLoginDto.getRefreshToken());
refreshCookie.setHttpOnly(true);
refreshCookie.setSecure(cookieSecure); // HTTPS 전송 (환경별 설정)
refreshCookie.setPath("/");
refreshCookie.setMaxAge(7 * 24 * 3600); // 7일
response.addCookie(refreshCookie);
SessionUtil.addTokenCookie(response, "refreshToken", testLoginDto.getRefreshToken(), cookieDomain, cookieSecure, refreshTokenValiditySeconds);
// JSON 응답에서는 토큰 제거 (쿠키로만 전달)
testLoginDto.setAccessToken(null);
@@ -4,6 +4,7 @@ import com.alist.api.modules.auth.dto.TestLoginDto;
import com.alist.api.modules.auth.vo.TestLoginTokenVo;
import com.alist.api.modules.auth.vo.TestLoginVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface TestAuthMapper {
@@ -14,4 +15,10 @@ public interface TestAuthMapper {
TestLoginTokenVo selectTestUserTokenByUserApiKey(TestLoginDto testLoginDto);
void updateRefreshToken(TestLoginDto testLoginDto);
TestLoginTokenVo selectTestUserTokenByUserTokenIdx(@Param("userTokenIdx") int userTokenIdx);
void clearRefreshTokenByUserTokenIdx(@Param("userTokenIdx") int userTokenIdx);
void updateTestLoginByLastAt(TestLoginDto testLoginDto);
}
@@ -28,60 +28,106 @@ public class TestAuthService {
}
// id login
public TestLoginDto selectTestLogin(TestLoginDto loginDto) {
TestLoginVo testLoginVo = testAuthMapper.selectTestLogin(loginDto);
public TestLoginDto selectTestLogin(TestLoginDto testLoginDto) {
TestLoginVo testLoginVo = testAuthMapper.selectTestLogin(testLoginDto);
if (testLoginVo != null) {
if (passwordEncoder.matches(loginDto.getPassword(), testLoginVo.getPassword())) {
if (passwordEncoder.matches(testLoginDto.getPassword(), testLoginVo.getPassword())) {
loginDto.setUserIdx(testLoginVo.getUserIdx());
testLoginDto.setUserIdx(testLoginVo.getUserIdx());
TestLoginTokenVo testLoginTokenVo = testAuthMapper.selectTestUserTokenByUserId(loginDto);
TestLoginTokenVo testLoginTokenVo = testAuthMapper.selectTestUserTokenByUserId(testLoginDto);
if (testLoginTokenVo != null) {
loginDto.setAccessToken(jwtTokenProvider.createAccessToken(testLoginTokenVo.getUserTokenIdx(), testLoginTokenVo.getUserRole()));
loginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(testLoginTokenVo.getUserTokenIdx()));
testLoginDto.setAccessToken(jwtTokenProvider.createAccessToken(testLoginTokenVo.getUserTokenIdx(), testLoginTokenVo.getUserRole()));
testLoginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(testLoginTokenVo.getUserTokenIdx()));
loginDto.setUserTokenIdx(testLoginTokenVo.getUserTokenIdx());
testLoginDto.setUserTokenIdx(testLoginTokenVo.getUserTokenIdx());
Instant now = Instant.now();
Instant expiresAt = now.plusSeconds(jwtProperties.getRefreshTokenValiditySeconds());
loginDto.setExpiresAt(expiresAt);
testLoginDto.setExpiresAt(expiresAt);
testAuthMapper.updateRefreshToken(loginDto);
testAuthMapper.updateRefreshToken(testLoginDto);
loginDto.setResultCode(2001);
// 마지막 로그인 시간 추가
testAuthMapper.updateTestLoginByLastAt(testLoginDto);
testLoginDto.setResultCode(2001);
} else {
loginDto.setResultCode(2003);
testLoginDto.setResultCode(2003);
}
} else {
loginDto.setResultCode(2003);
testLoginDto.setResultCode(2003);
}
} else {
loginDto.setResultCode(2003);
testLoginDto.setResultCode(2003);
}
return loginDto;
return testLoginDto;
}
// api key login
public TestLoginDto selectTestUserApiKeyLogin(TestLoginDto loginDto) {
if (loginDto.getUserApiKey() != null) {
TestLoginTokenVo testLoginTokenVo = testAuthMapper.selectTestUserTokenByUserApiKey(loginDto);
public TestLoginDto selectTestUserApiKeyLogin(TestLoginDto testLoginDto) {
if (testLoginDto.getUserApiKey() != null) {
TestLoginTokenVo testLoginTokenVo = testAuthMapper.selectTestUserTokenByUserApiKey(testLoginDto);
if (testLoginTokenVo != null) {
loginDto.setAccessToken(jwtTokenProvider.createAccessToken(testLoginTokenVo.getUserTokenIdx(), testLoginTokenVo.getUserRole()));
loginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(testLoginTokenVo.getUserTokenIdx()));
testLoginDto.setAccessToken(jwtTokenProvider.createAccessToken(testLoginTokenVo.getUserTokenIdx(), testLoginTokenVo.getUserRole()));
testLoginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(testLoginTokenVo.getUserTokenIdx()));
loginDto.setResultCode(2001);
testLoginDto.setResultCode(2001);
} else {
loginDto.setResultCode(2003);
testLoginDto.setResultCode(2003);
}
} else {
loginDto.setResultCode(2003);
testLoginDto.setResultCode(2003);
}
return loginDto;
return testLoginDto;
}
public TestLoginDto refreshByToken(String refreshToken) {
if (refreshToken == null || !jwtTokenProvider.validateToken(refreshToken)) {
return null;
}
int userTokenIdx;
try {
userTokenIdx = Integer.parseInt(jwtTokenProvider.getUserId(refreshToken));
} catch (NumberFormatException e) {
return null;
}
TestLoginTokenVo tokenVo = testAuthMapper.selectTestUserTokenByUserTokenIdx(userTokenIdx);
if (tokenVo == null || tokenVo.getRefreshToken() == null) {
return null;
}
if (!refreshToken.equals(tokenVo.getRefreshToken())) {
return null;
}
TestLoginDto testLogin = new TestLoginDto();
testLogin.setUserTokenIdx(userTokenIdx);
testLogin.setAccessToken(jwtTokenProvider.createAccessToken(tokenVo.getUserTokenIdx(), tokenVo.getUserRole()));
testLogin.setRefreshToken(jwtTokenProvider.createRefreshToken(tokenVo.getUserTokenIdx()));
testLogin.setExpiresAt(Instant.now().plusSeconds(jwtProperties.getRefreshTokenValiditySeconds()));
testAuthMapper.updateRefreshToken(testLogin);
return testLogin;
}
public void clearRefreshToken(String refreshToken) {
if (refreshToken == null || !jwtTokenProvider.validateToken(refreshToken)) {
return;
}
try {
int userTokenIdx = Integer.parseInt(jwtTokenProvider.getUserId(refreshToken));
testAuthMapper.clearRefreshTokenByUserTokenIdx(userTokenIdx);
} catch (NumberFormatException ignored) {
}
}
}
+15 -1
View File
@@ -12,6 +12,18 @@ spring:
connection-timeout: 10000
idle-timeout: 30000
data:
redis:
host: 121.160.234.222
port: 3001
password: 1qaz2wsx!@
session:
store-type: redis
timeout: 7200 # 2시간 (초 단위)
redis:
namespace: spring:session:alist
logging:
config: classpath:logback-local.xml
@@ -21,7 +33,9 @@ jwt:
refresh-token-validity-seconds: 2592000
cookie:
secure: false # 로컬 개발 환경 (HTTP)
secure: true # 로컬 개발 환경 (HTTP)
domain: pjt.kr
name: ALIST_SESSION
swagger:
login:
+14
View File
@@ -14,6 +14,18 @@ spring:
connection-timeout: 10000
idle-timeout: 30000
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}
session:
store-type: redis
timeout: 7200 # 2시간 (초 단위)
redis:
namespace: spring:session:alist
logging:
config: classpath:logback-pjt.xml
@@ -24,6 +36,8 @@ jwt:
cookie:
secure: true # 프로젝트 환경 (HTTPS)
domain: pjt.kr
name: ALIST_SESSION
swagger:
login:
@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alist.api.modules.auth.mapper.TestCorsMapper">
<select id="selectTestCorsAllowedList" resultType="com.alist.api.modules.auth.vo.TestCorsOriginVo">
/* TestCorsMapper.selectTestCorsAllowedList */
SELECT allowed_origin
@@ -11,5 +9,4 @@
WHERE del_yn = 1
ORDER BY cors_idx
</select>
</mapper>
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alist.api.modules.auth.mapper.TestAuthMapper">
<update id="updateRefreshToken">
/*TestLoginMapper.selectTestLogin*/
update test_user_token
set refresh_token = #{refreshToken}
, expires_at = #{expiresAt}
, updated_at = now()
, expires_at = #{expiresAt}
, updated_at = now()
where user_token_idx = #{userTokenIdx}
</update>
@@ -35,9 +33,35 @@
<select id="selectTestUserTokenByUserApiKey" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
/*TestLoginMapper.selectTestUserTokenByUserApiKey*/
select user_token_idx
, user_role
, refresh_token
, user_role
, refresh_token
from test_user_token
where user_api_key = #{userApiKey}
</select>
<select id="selectTestUserTokenByUserTokenIdx" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
/*TestLoginMapper.selectTestUserTokenByUserTokenIdx*/
select user_token_idx
, user_role
, refresh_token
from test_user_token
where user_token_idx = #{userTokenIdx}
</select>
<update id="clearRefreshTokenByUserTokenIdx">
/*TestLoginMapper.clearRefreshTokenByUserTokenIdx*/
update test_user_token
set refresh_token = null
, expires_at = null
, updated_at = now()
where user_token_idx = #{userTokenIdx}
</update>
<update id="updateTestLoginByLastAt">
/*TestLoginMapper.updateTestLoginByLastAt*/
update test_user
set last_login_at = now()
, update_at = now()
where user_idx = #{userIdx}
</update>
</mapper>
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alist.api.modules.testUser.mapper.TestUserMapper">
<insert id="insertTestUserSignup" useGeneratedKeys="true" keyProperty="userIdx">