[api] swagger 셋팅중
- 로그인 추가
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"mcp__jetbrains-file__EditFile"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,93 +1,154 @@
|
|||||||
# api
|
# alist API
|
||||||
|
|
||||||
|
Spring Boot 기반의 RESTful API 서버
|
||||||
|
|
||||||
|
## 프로젝트 정보
|
||||||
|
|
||||||
## Getting started
|
- **그룹**: com.alist
|
||||||
|
- **포트**: 8106
|
||||||
|
- **빌드 결과물**: `api.jar`
|
||||||
|
- **GitLab**: https://gitlab.pjt.kr/alist/api
|
||||||
|
|
||||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
## 기술 스택
|
||||||
|
|
||||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
- **Java**: 21
|
||||||
|
- **Framework**: Spring Boot 3.5.10
|
||||||
|
- **빌드 도구**: Gradle
|
||||||
|
- **데이터베이스**: MariaDB
|
||||||
|
- **ORM**: MyBatis
|
||||||
|
- **인증**: JWT (jjwt 0.11.5) + Spring Security
|
||||||
|
- **API 문서**: Swagger (springdoc-openapi 2.6.0)
|
||||||
|
- **기타**: Lombok, Validation, Actuator, log4jdbc
|
||||||
|
|
||||||
## Add your files
|
## 주요 기능
|
||||||
|
|
||||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
- JWT 기반 인증/인가
|
||||||
- [ ] [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
|
- MyBatis XML Mapper를 통한 데이터 액세스
|
||||||
|
- 표준화된 API 응답 (`ApiResponse<T>`)
|
||||||
|
- Swagger API 문서 (HTTP Basic 인증)
|
||||||
|
- 파일 업로드/다운로드
|
||||||
|
- Actuator 헬스 체크
|
||||||
|
|
||||||
```
|
## 빌드 및 실행
|
||||||
cd existing_repo
|
|
||||||
git remote add origin https://gitlab.pjt.kr/alist/api.git
|
### 빌드
|
||||||
git branch -M main
|
```bash
|
||||||
git push -uf origin main
|
./gradlew bootJar
|
||||||
```
|
```
|
||||||
|
|
||||||
## Integrate with your tools
|
### 로컬 실행
|
||||||
|
```bash
|
||||||
|
./gradlew bootRun --args='--spring.profiles.active=local'
|
||||||
|
```
|
||||||
|
|
||||||
- [ ] [Set up project integrations](https://gitlab.pjt.kr/alist/api/-/settings/integrations)
|
### JAR 실행
|
||||||
|
```bash
|
||||||
|
java -jar build/libs/api.jar --spring.profiles.active=local
|
||||||
|
```
|
||||||
|
|
||||||
## Collaborate with your team
|
## 환경 설정
|
||||||
|
|
||||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
### 프로파일
|
||||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
|
||||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
|
||||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
|
||||||
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
|
|
||||||
|
|
||||||
## Test and Deploy
|
| 프로파일 | 설명 | 설정 파일 |
|
||||||
|
|---------|------|----------|
|
||||||
|
| `local` | 로컬 개발 환경 | `application-local.yml` |
|
||||||
|
| `pjt` | 프로젝트(개발) 환경 | `application-pjt.yml` |
|
||||||
|
|
||||||
Use the built-in continuous integration in GitLab.
|
### 환경별 접속 정보
|
||||||
|
|
||||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
|
#### 로컬 (local)
|
||||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
- **API**: http://localhost:8106
|
||||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
- **Swagger**: http://localhost:8106/swagger-ui.html
|
||||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
|
||||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
|
||||||
|
|
||||||
***
|
#### 개발 서버 (pjt)
|
||||||
|
- **API**: https://api-alist.pjt.kr
|
||||||
|
- **파일**: https://file-alist.pjt.kr
|
||||||
|
- **Swagger**: https://api-alist.pjt.kr/swagger-ui/index.html
|
||||||
|
|
||||||
# Editing this README
|
## API 문서
|
||||||
|
|
||||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
Swagger UI를 통해 API 문서를 확인할 수 있습니다.
|
||||||
|
|
||||||
## Suggestions for a good README
|
- **접속**: `/swagger-ui.html`
|
||||||
|
- **인증**: HTTP Basic (환경별 yaml에 설정된 ID/PW 사용)
|
||||||
|
|
||||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
## 패키지 구조
|
||||||
|
|
||||||
## Name
|
```
|
||||||
Choose a self-explaining name for your project.
|
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
|
||||||
|
```
|
||||||
|
|
||||||
## Description
|
## 보안
|
||||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
|
||||||
|
|
||||||
## Badges
|
### 인증 방식
|
||||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
- **Swagger**: HTTP Basic 인증 (InMemory)
|
||||||
|
- **API**: JWT Bearer 토큰 인증 (Stateless)
|
||||||
|
|
||||||
## Visuals
|
### 공개 경로
|
||||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
- `/` - 루트
|
||||||
|
- `/actuator/health` - 헬스 체크
|
||||||
|
- `/auth/**` - 인증 관련 API
|
||||||
|
- `/api/user/signup` - 회원가입
|
||||||
|
|
||||||
## Installation
|
## 응답 코드
|
||||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
|
||||||
|
|
||||||
## Usage
|
API 응답은 `ApiResponseCode` enum으로 표준화되어 있습니다.
|
||||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
|
||||||
|
|
||||||
## Support
|
### 주요 응답 코드
|
||||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
|
||||||
|
|
||||||
## Roadmap
|
| 코드 | 메시지 | HTTP Status | 용도 |
|
||||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
|------|--------|-------------|------|
|
||||||
|
| `CODE_200` | 성공 | 200 OK | 일반 성공 |
|
||||||
|
| `CODE_2001` | {0} 정보 조회에 성공하였습니다. | 200 OK | 단건 조회 성공 |
|
||||||
|
| `CODE_2002` | {0} 등록 되었습니다. | 201 Created | 등록 성공 |
|
||||||
|
| `CODE_2003` | 조회된 정보가 없습니다. | 200 OK | 조회 결과 없음 |
|
||||||
|
| `CODE_2004` | 중복된 {0} 정보 입니다. | 409 Conflict | 중복 데이터 |
|
||||||
|
| `CODE_4001` | 입력값을 확인해주세요. | 400 Bad Request | Validation 오류 |
|
||||||
|
| `CODE_4003` | 필수 요청 파라미터가 누락되었습니다. | 400 Bad Request | 필수 파라미터 누락 |
|
||||||
|
|
||||||
## Contributing
|
## 배포
|
||||||
State if you are open to contributions and what your requirements are for accepting them.
|
|
||||||
|
|
||||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
### Docker
|
||||||
|
```bash
|
||||||
|
# 이미지 빌드
|
||||||
|
docker build -t registry.pjt.kr/alist/api .
|
||||||
|
|
||||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
# 컨테이너 실행
|
||||||
|
docker run -d -p 8106:8106 --name alist-api registry.pjt.kr/alist/api
|
||||||
|
```
|
||||||
|
|
||||||
## Authors and acknowledgment
|
### CI/CD
|
||||||
Show your appreciation to those who have contributed to the project.
|
- **Jenkins**: `Jenkinsfile.pjt`
|
||||||
|
- **Docker Registry**: registry.pjt.kr
|
||||||
|
- **배포 스크립트**: `deploy/` 디렉토리
|
||||||
|
|
||||||
## License
|
## 개발 가이드
|
||||||
For open source projects, say how it is licensed.
|
|
||||||
|
|
||||||
## Project status
|
상세한 개발 가이드는 [CLAUDE.md](./CLAUDE.md)를 참고하세요.
|
||||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
|
||||||
|
### 코드 작성 규칙
|
||||||
|
- 응답은 `ApiResponse<T>` 래퍼 사용
|
||||||
|
- 응답 코드는 `ApiResponseCode` enum 사용
|
||||||
|
- MyBatis Mapper XML은 `src/main/resources/mapper/` 하위에 작성
|
||||||
|
- 새 모듈 추가 시: `modules/{moduleName}/` 구조로 생성
|
||||||
|
|
||||||
|
### MyBatis 규칙
|
||||||
|
- Mapper XML 위치: `src/main/resources/mapper/**/*.xml`
|
||||||
|
- 카멜케이스 자동 변환 활성화 (`map-underscore-to-camel-case: true`)
|
||||||
|
- Mapper 인터페이스와 XML의 namespace, id 일치 필수
|
||||||
|
|
||||||
|
## 라이선스
|
||||||
|
|
||||||
|
Proprietary
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public class SecurityConfig {
|
|||||||
.accessDeniedHandler(new JwtAccessDeniedHandler())
|
.accessDeniedHandler(new JwtAccessDeniedHandler())
|
||||||
)
|
)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers("/", "/actuator/health", "/auth/**", "/api/testUser/signup").permitAll()
|
.requestMatchers("/", "/actuator/health", "/auth/**", "/test/**").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.alist.api.config.jwt;
|
|||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.Cookie;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.alist.api.modules.auth;
|
package com.alist.api.modules.auth;
|
||||||
|
|
||||||
|
import com.alist.api.modules.auth.dto.TokenDto;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -13,6 +16,7 @@ import com.alist.api.modules.auth.form.TokenForm;
|
|||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
@Tag(name = "00. 엑세스 토큰 발급", description = "강제 엑세스 토큰 발급 (JSON 전달, 테스트용)")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
@@ -22,10 +26,15 @@ public class AuthController {
|
|||||||
this.jwtTokenProvider = jwtTokenProvider;
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "엑세스 토큰 발급",
|
||||||
|
description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)"
|
||||||
|
)
|
||||||
@PostMapping("/token")
|
@PostMapping("/token")
|
||||||
public ResponseEntity<ApiResponse<String>> token(@Valid @RequestBody TokenForm tokenForm) {
|
public ResponseEntity<ApiResponse<TokenDto>> token(@Valid @RequestBody TokenForm tokenForm) {
|
||||||
String accessToken = jwtTokenProvider.createToken(tokenForm.getUserId());
|
TokenDto tokenDto = new TokenDto();
|
||||||
|
tokenDto.setAccessToken(jwtTokenProvider.createToken(tokenForm.tokenDto().getId()));
|
||||||
|
|
||||||
return ApiResponse.entity(accessToken, ApiResponseCode.CODE_2001, "엑세스 토큰");
|
return ApiResponse.entity(tokenDto, ApiResponseCode.CODE_2001, "엑세스 토큰");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.alist.api.modules.auth;
|
||||||
|
|
||||||
|
import com.alist.api.common.response.ApiResponse;
|
||||||
|
import com.alist.api.common.response.ApiResponseCode;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
@Tag(name = "98. 테스트 인증", description = "테스트 사용자 로그인 및 API Key 인증 관련 API")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/test")
|
||||||
|
public class TestAuthController {
|
||||||
|
private final TestAuthService testAuthService;
|
||||||
|
|
||||||
|
@Value("${cookie.secure}")
|
||||||
|
private boolean cookieSecure;
|
||||||
|
|
||||||
|
public TestAuthController(TestAuthService testAuthService) {
|
||||||
|
this.testAuthService = testAuthService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "테스트 사용자 로그인",
|
||||||
|
description = "아이디/비밀번호로 로그인하고 HttpOnly 쿠키로 토큰을 전달합니다. (웹 브라우저용)"
|
||||||
|
)
|
||||||
|
@PostMapping("/testLogin")
|
||||||
|
public ResponseEntity<ApiResponse<TestLoginDto>> testUserLogin(
|
||||||
|
@Valid @RequestBody TestLoginForm testLoginForm,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
TestLoginDto testLoginDto = testAuthService.selectTestLogin(testLoginForm.testLoginDto());
|
||||||
|
|
||||||
|
if (testLoginDto.getResultCode() == 2003) {
|
||||||
|
return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2003);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// JSON 응답에서는 토큰 제거 (쿠키로만 전달)
|
||||||
|
testLoginDto.setAccessToken(null);
|
||||||
|
testLoginDto.setRefreshToken(null);
|
||||||
|
|
||||||
|
return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2001, "로그인");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "테스트 API Key 로그인",
|
||||||
|
description = "API Key로 인증하고 JSON으로 토큰을 반환합니다. (OpenAPI/외부 서비스용)"
|
||||||
|
)
|
||||||
|
@PostMapping("/testApiKeyLogin")
|
||||||
|
public ResponseEntity<ApiResponse<TestLoginDto>> testUserApkKeyLogin(@Valid @RequestBody TestApikeyLoginForm testApiKeyLoginForm) {
|
||||||
|
TestLoginDto testLoginDto = testAuthService.selectTestUserApiKeyLogin(testApiKeyLoginForm.testLoginDto());
|
||||||
|
|
||||||
|
if (testLoginDto.getResultCode() == 2003) {
|
||||||
|
return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2003);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2001, "엑세스 토큰");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.alist.api.modules.auth.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class TestLoginDto {
|
||||||
|
@Schema(description = "아이디")
|
||||||
|
private String id;
|
||||||
|
@Schema(description = "엑세스 토큰")
|
||||||
|
private String accessToken;
|
||||||
|
@Schema(description = "리플레시 토큰")
|
||||||
|
private String refreshToken;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private String password;
|
||||||
|
@JsonIgnore
|
||||||
|
private String userApiKey;
|
||||||
|
@JsonIgnore
|
||||||
|
private int userTokenIdx;
|
||||||
|
@JsonIgnore
|
||||||
|
private Instant expiresAt;
|
||||||
|
@JsonIgnore
|
||||||
|
private int userIdx;
|
||||||
|
@JsonIgnore
|
||||||
|
private int resultCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.alist.api.modules.auth.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class TokenDto {
|
||||||
|
@Schema(description = "아이디")
|
||||||
|
private String id;
|
||||||
|
@Schema(description = "엑세스 토큰")
|
||||||
|
private String accessToken;
|
||||||
|
@Schema(description = "리플레시 토큰")
|
||||||
|
private String refreshToken;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private int resultCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.alist.api.modules.auth.form;
|
||||||
|
|
||||||
|
import com.alist.api.modules.auth.dto.TestLoginDto;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class TestApikeyLoginForm {
|
||||||
|
@Schema(
|
||||||
|
description = "api key",
|
||||||
|
example = "aaaa111dsddd"
|
||||||
|
)
|
||||||
|
@NotBlank(message = "apiKey를 입력해주세요.")
|
||||||
|
private String userApiKey;
|
||||||
|
|
||||||
|
public TestLoginDto testLoginDto() {
|
||||||
|
TestLoginDto testLoginDto = new TestLoginDto();
|
||||||
|
testLoginDto.setUserApiKey(this.userApiKey.trim());
|
||||||
|
|
||||||
|
return testLoginDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.alist.api.modules.auth.form;
|
||||||
|
|
||||||
|
import com.alist.api.modules.auth.dto.TestLoginDto;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class TestLoginForm {
|
||||||
|
@Schema(
|
||||||
|
description = "사용자 아이디 (공백 불가)",
|
||||||
|
example = "test"
|
||||||
|
)
|
||||||
|
@NotBlank(message = "아이디를 입력해주세요.")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "비밀번호 (8~64자, 영문 + 숫자 조합, 공백 불가)",
|
||||||
|
example = "pass1234"
|
||||||
|
)
|
||||||
|
@NotBlank(message = "비밀번호를 입력해주세요.")
|
||||||
|
@Size(min = 8, max = 64, message = "비밀번호는 8~64자여야 합니다.")
|
||||||
|
@Pattern(
|
||||||
|
regexp = "^(?=.*[A-Za-z])(?=.*\\d)\\S+$",
|
||||||
|
message = "비밀번호는 영문과 숫자를 포함하고 공백이 없어야 합니다."
|
||||||
|
)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public TestLoginDto testLoginDto() {
|
||||||
|
TestLoginDto testLoginDto = new TestLoginDto();
|
||||||
|
testLoginDto.setId(this.id.trim());
|
||||||
|
testLoginDto.setPassword(this.password);
|
||||||
|
|
||||||
|
return testLoginDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.alist.api.modules.auth.form;
|
package com.alist.api.modules.auth.form;
|
||||||
|
|
||||||
|
import com.alist.api.modules.auth.dto.TokenDto;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -11,6 +12,13 @@ import lombok.Setter;
|
|||||||
public class TokenForm {
|
public class TokenForm {
|
||||||
|
|
||||||
@NotBlank(message = "사용자명은 필수입니다.")
|
@NotBlank(message = "사용자명은 필수입니다.")
|
||||||
@Schema(description = "사용자명", example = "user123")
|
@Schema(description = "사용자명", example = "test")
|
||||||
private String userId;
|
private String id;
|
||||||
|
|
||||||
|
public TokenDto tokenDto() {
|
||||||
|
TokenDto tokenDto = new TokenDto();
|
||||||
|
tokenDto.setId(this.id.trim());
|
||||||
|
|
||||||
|
return tokenDto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.alist.api.modules.auth.mapper;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface TestAuthMapper {
|
||||||
|
TestLoginVo selectTestLogin(TestLoginDto testLoginDto);
|
||||||
|
|
||||||
|
TestLoginTokenVo selectTestUserTokenByUserId(TestLoginDto testLoginDto);
|
||||||
|
|
||||||
|
TestLoginTokenVo selectTestUserTokenByUserApiKey(TestLoginDto testLoginDto);
|
||||||
|
|
||||||
|
void updateRefreshToken(TestLoginDto testLoginDto);
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.alist.api.modules.auth.service;
|
||||||
|
|
||||||
|
import com.alist.api.config.jwt.JwtTokenProvider;
|
||||||
|
import com.alist.api.config.properties.JwtProperties;
|
||||||
|
import com.alist.api.modules.auth.dto.TestLoginDto;
|
||||||
|
import com.alist.api.modules.auth.mapper.TestAuthMapper;
|
||||||
|
import com.alist.api.modules.auth.vo.TestLoginTokenVo;
|
||||||
|
import com.alist.api.modules.auth.vo.TestLoginVo;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class TestAuthService {
|
||||||
|
private final TestAuthMapper testAuthMapper;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
private final JwtProperties jwtProperties;
|
||||||
|
|
||||||
|
public TestAuthService(TestAuthMapper testAuthMapper, PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider, JwtProperties jwtProperties) {
|
||||||
|
this.testAuthMapper = testAuthMapper;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
|
this.jwtProperties = jwtProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
// id login
|
||||||
|
public TestLoginDto selectTestLogin(TestLoginDto loginDto) {
|
||||||
|
TestLoginVo testLoginVo = testAuthMapper.selectTestLogin(loginDto);
|
||||||
|
|
||||||
|
if (testLoginVo != null) {
|
||||||
|
if (passwordEncoder.matches(loginDto.getPassword(), testLoginVo.getPassword())) {
|
||||||
|
|
||||||
|
loginDto.setUserIdx(testLoginVo.getUserIdx());
|
||||||
|
|
||||||
|
TestLoginTokenVo testLoginTokenVo = testAuthMapper.selectTestUserTokenByUserId(loginDto);
|
||||||
|
|
||||||
|
if (testLoginTokenVo != null) {
|
||||||
|
loginDto.setAccessToken(jwtTokenProvider.createAccessToken(testLoginTokenVo.getUserTokenIdx(), testLoginTokenVo.getUserRole()));
|
||||||
|
loginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(testLoginTokenVo.getUserTokenIdx()));
|
||||||
|
|
||||||
|
loginDto.setUserTokenIdx(testLoginTokenVo.getUserTokenIdx());
|
||||||
|
|
||||||
|
Instant now = Instant.now();
|
||||||
|
Instant expiresAt = now.plusSeconds(jwtProperties.getRefreshTokenValiditySeconds());
|
||||||
|
|
||||||
|
loginDto.setExpiresAt(expiresAt);
|
||||||
|
|
||||||
|
testAuthMapper.updateRefreshToken(loginDto);
|
||||||
|
|
||||||
|
loginDto.setResultCode(2001);
|
||||||
|
} else {
|
||||||
|
loginDto.setResultCode(2003);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loginDto.setResultCode(2003);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loginDto.setResultCode(2003);
|
||||||
|
}
|
||||||
|
|
||||||
|
return loginDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// api key login
|
||||||
|
public TestLoginDto selectTestUserApiKeyLogin(TestLoginDto loginDto) {
|
||||||
|
if (loginDto.getUserApiKey() != null) {
|
||||||
|
TestLoginTokenVo testLoginTokenVo = testAuthMapper.selectTestUserTokenByUserApiKey(loginDto);
|
||||||
|
|
||||||
|
if (testLoginTokenVo != null) {
|
||||||
|
loginDto.setAccessToken(jwtTokenProvider.createAccessToken(testLoginTokenVo.getUserTokenIdx(), testLoginTokenVo.getUserRole()));
|
||||||
|
loginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(testLoginTokenVo.getUserTokenIdx()));
|
||||||
|
|
||||||
|
loginDto.setResultCode(2001);
|
||||||
|
} else {
|
||||||
|
loginDto.setResultCode(2003);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loginDto.setResultCode(2003);
|
||||||
|
}
|
||||||
|
|
||||||
|
return loginDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.alist.api.modules.auth.vo;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class TestLoginTokenVo {
|
||||||
|
private int userTokenIdx;
|
||||||
|
private String userApiKey;
|
||||||
|
private String userRole;
|
||||||
|
private String refreshToken;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.alist.api.modules.auth.vo;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class TestLoginVo {
|
||||||
|
private int userIdx;
|
||||||
|
private String id;
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import com.alist.api.common.response.ApiResponseCode;
|
|||||||
import com.alist.api.modules.testUser.dto.TestUserDto;
|
import com.alist.api.modules.testUser.dto.TestUserDto;
|
||||||
import com.alist.api.modules.testUser.form.TestUserSignupForm;
|
import com.alist.api.modules.testUser.form.TestUserSignupForm;
|
||||||
import com.alist.api.modules.testUser.service.TestUserService;
|
import com.alist.api.modules.testUser.service.TestUserService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -13,9 +15,10 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "99. 테스트 사용자 관리", description = "테스트 사용자 회원가입 및 관리 API")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/testUser")
|
@RequestMapping("/test")
|
||||||
public class TestUserController {
|
public class TestUserController {
|
||||||
|
|
||||||
public final TestUserService testUserService;
|
public final TestUserService testUserService;
|
||||||
@@ -24,16 +27,20 @@ public class TestUserController {
|
|||||||
this.testUserService = testUserService;
|
this.testUserService = testUserService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/signup")
|
@Operation(
|
||||||
public ResponseEntity<ApiResponse<TestUserDto>> signup(@Valid @RequestBody TestUserSignupForm testUserSignupForm) {
|
summary = "테스트 사용자 회원가입",
|
||||||
|
description = "새로운 테스트 사용자를 등록합니다. 아이디 중복 시 오류를 반환합니다."
|
||||||
|
)
|
||||||
|
@PostMapping("/testSignup")
|
||||||
|
public ResponseEntity<ApiResponse<TestUserDto>> testSignup(@Valid @RequestBody TestUserSignupForm testUserSignupForm) {
|
||||||
|
|
||||||
TestUserDto userDto = testUserService.insertTestUserProc(testUserSignupForm.testUserDto());
|
TestUserDto testUserDto = testUserService.insertTestUserProc(testUserSignupForm.testUserDto());
|
||||||
|
|
||||||
if (userDto.getResultCode() == 2004) {
|
if (testUserDto.getResultCode() == 2004) {
|
||||||
return ApiResponse.entity(userDto, ApiResponseCode.CODE_2004, "아이디");
|
return ApiResponse.entity(testUserDto, ApiResponseCode.CODE_2004, "아이디");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiResponse.entity(userDto, ApiResponseCode.CODE_2002, "아이디");
|
return ApiResponse.entity(testUserDto, ApiResponseCode.CODE_2002, "아이디");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,9 @@ jwt:
|
|||||||
access-token-validity-seconds: 3600
|
access-token-validity-seconds: 3600
|
||||||
refresh-token-validity-seconds: 2592000
|
refresh-token-validity-seconds: 2592000
|
||||||
|
|
||||||
|
cookie:
|
||||||
|
secure: false # 로컬 개발 환경 (HTTP)
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
login:
|
login:
|
||||||
id: alist
|
id: alist
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ jwt:
|
|||||||
access-token-validity-seconds: 3600
|
access-token-validity-seconds: 3600
|
||||||
refresh-token-validity-seconds: 2592000
|
refresh-token-validity-seconds: 2592000
|
||||||
|
|
||||||
|
cookie:
|
||||||
|
secure: true # 프로젝트 환경 (HTTPS)
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
login:
|
login:
|
||||||
id: ${SWAGGER_ID}
|
id: ${SWAGGER_ID}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ mybatis:
|
|||||||
springdoc:
|
springdoc:
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
path: /swagger-ui.html
|
path: /swagger-ui.html
|
||||||
|
tags-sorter: alpha # 태그 알파벳 순 정렬
|
||||||
|
operations-sorter: alpha # API 알파벳 순 정렬
|
||||||
api-docs:
|
api-docs:
|
||||||
path: /v3/api-docs
|
path: /v3/api-docs
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<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()
|
||||||
|
where user_token_idx = #{userTokenIdx}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="selectTestLogin" resultType="com.alist.api.modules.auth.vo.TestLoginVo">
|
||||||
|
/*TestLoginMapper.selectTestLogin*/
|
||||||
|
select user_idx
|
||||||
|
, id
|
||||||
|
, password
|
||||||
|
from test_user
|
||||||
|
where id = #{id}
|
||||||
|
and del_yn = 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectTestUserTokenByUserId" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
|
||||||
|
/*TestLoginMapper.selectUserTokenByUserId*/
|
||||||
|
select user_token_idx
|
||||||
|
, user_role
|
||||||
|
, refresh_token
|
||||||
|
from test_user_token
|
||||||
|
where user_idx = #{userIdx}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectTestUserTokenByUserApiKey" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
|
||||||
|
/*TestLoginMapper.selectTestUserTokenByUserApiKey*/
|
||||||
|
select user_token_idx
|
||||||
|
, user_role
|
||||||
|
, refresh_token
|
||||||
|
from test_user_token
|
||||||
|
where user_api_key = #{userApiKey}
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user