[api] swagger 셋팅중

- 로그인 추가
This commit is contained in:
2026-02-25 17:11:17 +09:00
parent b1d4b6a60e
commit 41b7eba619
20 changed files with 547 additions and 72 deletions
@@ -60,7 +60,7 @@ public class SecurityConfig {
.accessDeniedHandler(new JwtAccessDeniedHandler())
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/actuator/health", "/auth/**", "/api/testUser/signup").permitAll()
.requestMatchers("/", "/actuator/health", "/auth/**", "/test/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
@@ -2,6 +2,7 @@ package com.alist.api.config.jwt;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
@@ -1,5 +1,8 @@
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.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -13,6 +16,7 @@ import com.alist.api.modules.auth.form.TokenForm;
import jakarta.validation.Valid;
@Tag(name = "00. 엑세스 토큰 발급", description = "강제 엑세스 토큰 발급 (JSON 전달, 테스트용)")
@RestController
@RequestMapping("/auth")
public class AuthController {
@@ -20,12 +24,17 @@ public class AuthController {
public AuthController(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
}
@Operation(
summary = "엑세스 토큰 발급",
description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)"
)
@PostMapping("/token")
public ResponseEntity<ApiResponse<String>> token(@Valid @RequestBody TokenForm tokenForm) {
String accessToken = jwtTokenProvider.createToken(tokenForm.getUserId());
public ResponseEntity<ApiResponse<TokenDto>> token(@Valid @RequestBody TokenForm tokenForm) {
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;
import com.alist.api.modules.auth.dto.TokenDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
@@ -11,6 +12,13 @@ import lombok.Setter;
public class TokenForm {
@NotBlank(message = "사용자명은 필수입니다.")
@Schema(description = "사용자명", example = "user123")
private String userId;
@Schema(description = "사용자명", example = "test")
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.form.TestUserSignupForm;
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 lombok.extern.slf4j.Slf4j;
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.RestController;
@Tag(name = "99. 테스트 사용자 관리", description = "테스트 사용자 회원가입 및 관리 API")
@Slf4j
@RestController
@RequestMapping("/api/testUser")
@RequestMapping("/test")
public class TestUserController {
public final TestUserService testUserService;
@@ -24,16 +27,20 @@ public class TestUserController {
this.testUserService = testUserService;
}
@PostMapping("/signup")
public ResponseEntity<ApiResponse<TestUserDto>> signup(@Valid @RequestBody TestUserSignupForm testUserSignupForm) {
@Operation(
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) {
return ApiResponse.entity(userDto, ApiResponseCode.CODE_2004, "아이디");
if (testUserDto.getResultCode() == 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
refresh-token-validity-seconds: 2592000
cookie:
secure: false # 로컬 개발 환경 (HTTP)
swagger:
login:
id: alist
+3
View File
@@ -17,6 +17,9 @@ jwt:
access-token-validity-seconds: 3600
refresh-token-validity-seconds: 2592000
cookie:
secure: true # 프로젝트 환경 (HTTPS)
swagger:
login:
id: ${SWAGGER_ID}
+2
View File
@@ -16,6 +16,8 @@ mybatis:
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha # 태그 알파벳 순 정렬
operations-sorter: alpha # API 알파벳 순 정렬
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>