[api] SsoController.java, AuthController.java 컨벤션 맞춰 변경

This commit is contained in:
2026-05-22 11:55:48 +09:00
parent 3a4f4679c0
commit 7b10ef0db8
23 changed files with 508 additions and 293 deletions
@@ -4,14 +4,12 @@ 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.config.jwt.JwtTokenProvider;
import com.alist.api.modules.auth.dto.SsoExchangeDto;
import com.alist.api.modules.auth.dto.LoginDto;
import com.alist.api.modules.auth.dto.TokenDto;
import com.alist.api.modules.auth.form.ApikeyLoginForm;
import com.alist.api.modules.auth.form.TokenForm;
import com.alist.api.modules.auth.service.SsoService;
import com.alist.api.modules.auth.form.AuthApiKeyLoginForm;
import com.alist.api.modules.auth.form.AuthTokenForm;
import com.alist.api.modules.auth.service.AuthService;
import com.alist.api.modules.auth.vo.TokenVo;
import com.alist.api.modules.auth.service.SsoService;
import com.alist.api.modules.auth.vo.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
@@ -21,9 +19,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@Tag(name = "102. 엑세스 토큰 발급", description = "엑세스 토큰 발급")
@RestController
@RequestMapping("/auth")
@@ -59,89 +54,92 @@ public class AuthController {
}
@Operation(
summary = "엑세스 토큰 발급 (테스트용 실사용X)",
description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)"
summary = "엑세스 토큰 발급 (테스트용 실사용X)"
, description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)"
)
@PostMapping("/token")
public ResponseEntity<ApiResponse<TokenVo>> token(@Valid @RequestBody TokenForm tokenForm) {
TokenVo tokenVo = new TokenVo();
tokenVo.setAccessToken(jwtTokenProvider.createToken(tokenForm.toTokenDto().getId()));
public ResponseEntity<ApiResponse<AuthTokenVo>> authToken(@Valid @RequestBody AuthTokenForm authTokenForm) {
AuthTokenVo authTokenVo = new AuthTokenVo();
authTokenVo.setAccessToken(jwtTokenProvider.createToken(authTokenForm.toDto().getId()));
return ApiResponse.entity(tokenVo, ApiResponseCode.CODE_2001, "엑세스 토큰");
return ApiResponse.entity(authTokenVo, ApiResponseCode.CODE_2001, "엑세스 토큰");
}
@Operation(
summary = "SSO 기반 토큰 발급",
description = "ALIST_SSO 쿠키 기준으로 SSO 로그인 상태를 확인한 뒤 accessToken, refreshToken 쿠키를 발급합니다."
summary = "SSO 기반 토큰 발급"
, description = "ALIST_SSO 쿠키 기준으로 SSO 로그인 상태를 확인한 뒤 accessToken, refreshToken 쿠키를 발급합니다."
)
@PostMapping("/access")
public ResponseEntity<ApiResponse<Map<String, Object>>> access(
public ResponseEntity<ApiResponse<AuthAccessVo>> authAccess(
HttpServletRequest request,
HttpServletResponse response
) {
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
SsoExchangeDto ssoSession = ssoService.loginChecked(ssoSessionId);
SsoExchangeVo ssoExchangeVo = ssoService.selectSsoLoginChecked(ssoSessionId);
if (ssoSession == null) {
return ApiResponse.entity(Map.of("isAccessToken", false), ApiResponseCode.CODE_401);
AuthAccessVo authAccessVo = new AuthAccessVo();
if (ssoExchangeVo == null) {
authAccessVo.setAccessToken(false);
return ApiResponse.entity(authAccessVo, ApiResponseCode.CODE_401);
}
String accessToken = jwtTokenProvider.createAccessToken(ssoSession.getUserTokenIdx(), ssoSession.getUserRole());
String refreshToken = jwtTokenProvider.createRefreshToken(ssoSession.getUserTokenIdx());
String accessToken = jwtTokenProvider.createAccessToken(ssoExchangeVo.getUserTokenIdx(), ssoExchangeVo.getUserRole());
String refreshToken = jwtTokenProvider.createRefreshToken(ssoExchangeVo.getUserTokenIdx());
LoginDto loginDto = new LoginDto();
loginDto.setUserTokenIdx(ssoSession.getUserTokenIdx());
loginDto.setUserTokenIdx(ssoExchangeVo.getUserTokenIdx());
loginDto.setRefreshToken(refreshToken);
authService.updateRefreshToken(loginDto);
SessionUtil.addTokenCookie(response, "accessToken", accessToken, cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds);
SessionUtil.addTokenCookie(response,"refreshToken", refreshToken, cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
SessionUtil.addTokenCookie(response, "refreshToken", refreshToken, cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
Map<String, Object> result = new HashMap<>();
result.put("isAccessToken", true);
result.put("userIdx", ssoSession.getUserIdx());
result.put("userId", ssoSession.getUserId());
result.put("userRole", ssoSession.getUserRole());
result.put("userType", ssoSession.getUserType());
authAccessVo.setAccessToken(true);
authAccessVo.setUserIdx(ssoExchangeVo.getUserIdx());
authAccessVo.setUserId(ssoExchangeVo.getUserId());
authAccessVo.setUserRole(ssoExchangeVo.getUserRole());
authAccessVo.setUserType(ssoExchangeVo.getUserType());
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "토큰");
return ApiResponse.entity(authAccessVo, ApiResponseCode.CODE_2001, "토큰");
}
@Operation(
summary = "리프레시 토큰으로 엑세스 토큰 재발급",
description = "리프레시 토큰으로 엑세스 토큰 재발급"
summary = "리프레시 토큰으로 엑세스 토큰 재발급"
, description = "리프레시 토큰으로 엑세스 토큰 재발급"
)
@PostMapping("/refresh")
public ResponseEntity<ApiResponse<Map<String, Object>>> refresh(
public ResponseEntity<ApiResponse<AuthRefreshVo>> authRefresh(
@CookieValue(name = "refreshToken", required = false) String refreshToken
, HttpServletResponse response
) {
AuthRefreshVo authRefreshVo = authService.selectAuthRefresh(refreshToken);
LoginDto result = authService.refreshByToken(refreshToken);
if (result == null) {
return ApiResponse.entity(Map.of("refreshed", false), ApiResponseCode.CODE_401);
if (authRefreshVo == null) {
AuthRefreshVo authRefreshVo1 = new AuthRefreshVo();
authRefreshVo1.setRefreshed(false);
return ApiResponse.entity(authRefreshVo1, ApiResponseCode.CODE_401);
}
SessionUtil.addTokenCookie(response, "accessToken", result.getAccessToken(), cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds);
SessionUtil.addTokenCookie(response, "refreshToken", result.getRefreshToken(), cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
SessionUtil.addTokenCookie(response, "accessToken", authRefreshVo.getAccessToken(), cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds);
SessionUtil.addTokenCookie(response, "refreshToken", authRefreshVo.getRefreshToken(), cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
return ApiResponse.entity(Map.of("refreshed", true), ApiResponseCode.CODE_200);
return ApiResponse.entity(authRefreshVo, ApiResponseCode.CODE_200);
}
@Operation(
summary = "API Key 로그인",
description = "API Key로 인증하고 JSON으로 토큰을 반환합니다. (OpenAPI/외부 서비스용)"
summary = "API Key 로그인"
, description = "API Key로 인증하고 JSON으로 토큰을 반환합니다. (OpenAPI/외부 서비스용)"
)
@PostMapping("/apiKeyLogin")
public ResponseEntity<ApiResponse<LoginDto>> apiKeyLogin(@Valid @RequestBody ApikeyLoginForm apiKeyLoginForm) {
LoginDto result = authService.selectUserApiKeyLogin(apiKeyLoginForm.toLoginDto());
public ResponseEntity<ApiResponse<AuthApiKeyLoginVo>> authApiKeyLogin(@Valid @RequestBody AuthApiKeyLoginForm authApiKeyLoginForm) {
AuthApiKeyLoginVo authApiKeyLoginVo = authService.selectAuthApiKeyLogin(authApiKeyLoginForm.toDto());
if (result.getResultCode() == 2003) {
return ApiResponse.entity(result, ApiResponseCode.CODE_2003);
if (authApiKeyLoginVo.getResultCode() == 2003) {
return ApiResponse.entity(authApiKeyLoginVo, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "엑세스 토큰");
return ApiResponse.entity(authApiKeyLoginVo, ApiResponseCode.CODE_2001, "엑세스 토큰");
}
}
@@ -3,13 +3,14 @@ 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.SsoExchangeDto;
import com.alist.api.modules.auth.dto.SsoLoginCheckDto;
import com.alist.api.modules.auth.dto.SsoLoginDto;
import com.alist.api.modules.auth.form.SsoExchangeForm;
import com.alist.api.modules.auth.form.SsoLoginForm;
import com.alist.api.modules.auth.service.SsoService;
import com.alist.api.modules.auth.service.AuthService;
import com.alist.api.modules.auth.service.SsoService;
import com.alist.api.modules.auth.vo.SsoExchangeVo;
import com.alist.api.modules.auth.vo.SsoLoginCheckedVo;
import com.alist.api.modules.auth.vo.SsoLoginVo;
import com.alist.api.modules.auth.vo.SsoLogoutVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
@@ -20,8 +21,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
@Tag(name = "101. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API")
@RestController
@@ -50,126 +49,124 @@ public class SsoController {
}
@Operation(
summary = "SSO 로그인",
description = "아이디/비밀번호로 공통 SSO 로그인 상태를 생성하고 HttpOnly 쿠키를 발급합니다."
summary = "SSO 로그인"
, description = "아이디/비밀번호로 공통 SSO 로그인 상태를 생성하고 HttpOnly 쿠키를 발급합니다."
)
@PostMapping("/login")
public ResponseEntity<ApiResponse<SsoLoginCheckDto>> login(
public ResponseEntity<ApiResponse<SsoLoginVo>> ssoLogin(
@Valid @RequestBody SsoLoginForm ssoLoginForm
, HttpServletRequest request
, HttpServletResponse response
) {
SsoLoginDto ssoLoginDto = ssoService.login(ssoLoginForm.toSsoLoginDto(), request);
SsoLoginVo ssoLoginVo = ssoService.selectSsoLogin(ssoLoginForm.toDto());
SsoLoginCheckDto result = new SsoLoginCheckDto();
if (ssoLoginDto.getResultCode() == 2003) {
result.setLoginIn(false);
return ApiResponse.entity(result, ApiResponseCode.CODE_2003);
if (ssoLoginVo.getResultCode() == 2003) {
SsoLoginVo ssoLoginVo1 = new SsoLoginVo();
ssoLoginVo1.setLoginIn(false);
return ApiResponse.entity(ssoLoginVo1, ApiResponseCode.CODE_2003);
}
SessionUtil.addTokenCookie(response, ssoCookieName, ssoLoginDto.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
SessionUtil.addTokenCookie(response, ssoCookieName, ssoLoginVo.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
result.setLoginIn(true);
result.setId(ssoLoginDto.getUserId());
result.setUserIdx(ssoLoginDto.getUserIdx());
result.setUserRole(ssoLoginDto.getUserRole());
result.setUserType(ssoLoginDto.getUserType());
ssoLoginVo.setLoginIn(true);
ssoLoginVo.setId(ssoLoginVo.getUserId());
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "SSO 로그인");
return ApiResponse.entity(ssoLoginVo, ApiResponseCode.CODE_2001, "SSO 로그인");
}
@Operation(
summary = "SSO 로그인 상태 확인",
description = "SSO 쿠키 기준으로 현재 공통 로그인 상태가 유효한지 확인하고, access/refresh 토큰 존재 여부도 함께 반환합니다."
summary = "SSO 로그인 상태 확인"
, description = "SSO 쿠키 기준으로 현재 공통 로그인 상태가 유효한지 확인하고, access/refresh 토큰 존재 여부도 함께 반환합니다."
)
@GetMapping("/loginChecked")
public ResponseEntity<ApiResponse<Map<String, Object>>> loginChecked(
public ResponseEntity<ApiResponse<SsoLoginCheckedVo>> ssoLoginChecked(
HttpServletRequest request
, HttpServletResponse response
) {
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
Map<String, Object> result = new HashMap<>();
SsoLoginCheckedVo ssoLoginCheckedVo = new SsoLoginCheckedVo();
SsoExchangeDto ssoSession = ssoService.loginChecked(ssoSessionId);
SsoExchangeVo ssoExchangeVo = ssoService.selectSsoLoginChecked(ssoSessionId);
String accessToken = SessionUtil.resolveSsoCookieValue(request, "accessToken");
String refreshToken = SessionUtil.resolveSsoCookieValue(request, "refreshToken");
result.put("isAccessToken", accessToken != null && !accessToken.trim().isEmpty());
result.put("isRefreshToken", refreshToken != null && !refreshToken.trim().isEmpty());
ssoLoginCheckedVo.setAccessToken(accessToken != null && !accessToken.trim().isEmpty());
ssoLoginCheckedVo.setRefreshToken(refreshToken != null && !refreshToken.trim().isEmpty());
if (ssoSession == null) {
result.put("loggedIn", false);
return ApiResponse.entity(result, ApiResponseCode.CODE_200);
if (ssoExchangeVo == null) {
ssoLoginCheckedVo.setLoggedIn(false);
return ApiResponse.entity(ssoLoginCheckedVo, ApiResponseCode.CODE_200);
}
SessionUtil.addTokenCookie(response, ssoCookieName, ssoSession.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite,ssoSessionTtlSeconds);
SessionUtil.addTokenCookie(response, ssoCookieName, ssoExchangeVo.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
result.put("loggedIn", true);
result.put("ssoSessionId", ssoSession.getSsoSessionId());
result.put("userId", ssoSession.getUserId());
result.put("userIdx", ssoSession.getUserIdx());
result.put("userTokenIdx", ssoSession.getUserTokenIdx());
result.put("userRole", ssoSession.getUserRole());
result.put("userType", ssoSession.getUserType());
ssoLoginCheckedVo.setLoggedIn(true);
ssoLoginCheckedVo.setSsoSessionId(ssoExchangeVo.getSsoSessionId());
ssoLoginCheckedVo.setUserId(ssoExchangeVo.getUserId());
ssoLoginCheckedVo.setUserIdx(ssoExchangeVo.getUserIdx());
ssoLoginCheckedVo.setUserTokenIdx(ssoExchangeVo.getUserTokenIdx());
ssoLoginCheckedVo.setUserRole(ssoExchangeVo.getUserRole());
ssoLoginCheckedVo.setUserType(ssoExchangeVo.getUserType());
return ApiResponse.entity(result, ApiResponseCode.CODE_200);
return ApiResponse.entity(ssoLoginCheckedVo, ApiResponseCode.CODE_200);
}
@Operation(
summary = "SSO 인가 코드 발급",
description = "공통 로그인 상태를 확인한 뒤 대상 서비스로 이동할 1회용 인가 코드를 발급하고 redirectUri 로 리다이렉트합니다."
summary = "SSO 인가 코드 발급"
, description = "공통 로그인 상태를 확인한 뒤 대상 서비스로 이동할 1회용 인가 코드를 발급하고 redirectUri 로 리다이렉트합니다."
)
@GetMapping("/authorize")
public ResponseEntity<Void> authorize(
public ResponseEntity<Void> ssoAuthorize(
@RequestParam("clientId") String clientId
, @RequestParam("redirectUri") String redirectUri
, @RequestParam(value = "state", required = false) String state
, HttpServletRequest request
) {
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
String redirectUrl = ssoService.authorize(clientId, redirectUri, state, ssoSessionId);
String redirectUrl = ssoService.selectSsoAuthorize(clientId, redirectUri, state, ssoSessionId);
return ResponseEntity.status(302).location(URI.create(redirectUrl)).build();
}
@Operation(
summary = "SSO 인가 코드 교환",
description = "서비스가 전달받은 1회용 인가 코드를 검증하고, 자체 세션 생성에 필요한 사용자 정보를 반환합니다."
summary = "SSO 인가 코드 교환"
, description = "서비스가 전달받은 1회용 인가 코드를 검증하고, 자체 세션 생성에 필요한 사용자 정보를 반환합니다."
)
@PostMapping("/exchange")
public ResponseEntity<ApiResponse<SsoExchangeDto>> exchange(
public ResponseEntity<ApiResponse<SsoExchangeVo>> ssoExchange(
@Valid @RequestBody SsoExchangeForm ssoExchangeForm
, HttpServletResponse response
) {
SsoExchangeDto result = ssoService.exchange(ssoExchangeForm.toSsoExchangeDto());
SsoExchangeVo ssoExchangeVo = ssoService.selectSsoExchange(ssoExchangeForm.toDto());
SessionUtil.addTokenCookie(response, ssoCookieName, result.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
SessionUtil.addTokenCookie(response, ssoCookieName, ssoExchangeVo.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "SSO code 교환");
return ApiResponse.entity(ssoExchangeVo, ApiResponseCode.CODE_2001, "SSO code 교환");
}
@Operation(
summary = "SSO 로그아웃",
description = "공통 로그인 세션을 만료시켜 이후 authorize 및 서비스 접근 시 재로그인이 필요하도록 처리합니다."
summary = "SSO 로그아웃"
, description = "공통 로그인 세션을 만료시켜 이후 authorize 및 서비스 접근 시 재로그인이 필요하도록 처리합니다."
)
@PostMapping("/logout")
public ResponseEntity<ApiResponse<Map<String, Object>>> logout(
public ResponseEntity<ApiResponse<SsoLogoutVo>> ssoLogout(
HttpServletRequest request
, HttpServletResponse response
, @CookieValue(name = "refreshToken", required = false) String refreshToken
) {
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
ssoService.logout(ssoSessionId);
ssoService.updateSsoLogout(ssoSessionId);
authService.clearRefreshToken(refreshToken);
SessionUtil.expireCookie(response, ssoCookieName, cookieDomain, cookieSecure, cookieSameSite);
SessionUtil.expireCookie(response, "accessToken", cookieDomain, cookieSecure, cookieSameSite);
SessionUtil.expireCookie(response, "refreshToken", cookieDomain, cookieSecure, cookieSameSite);
return ApiResponse.entity(Map.of("logout", true), ApiResponseCode.CODE_200);
SsoLogoutVo ssoLogoutVo = new SsoLogoutVo();
ssoLogoutVo.setLogout(true);
return ApiResponse.entity(ssoLogoutVo, ApiResponseCode.CODE_200);
}
}
@@ -0,0 +1,24 @@
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;
@Schema(description = "테스트용 토큰 발급 요청 DTO")
@Getter
@Setter
public class AuthTokenDto {
@Schema(description = "아이디")
private String id;
@Schema(description = "엑세스 토큰")
private String accessToken;
@Schema(description = "리플레시 토큰")
private String refreshToken;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -17,7 +17,7 @@ public class SsoLoginDto {
private String userRole;
private String userType;
public LoginDto toLoginDto() {
public LoginDto toDto() {
LoginDto loginDto = new LoginDto();
loginDto.setId(this.id);
loginDto.setPassword(this.password);
@@ -9,18 +9,18 @@ import lombok.Setter;
@Schema(description = "API Key 로그인 요청 폼")
@Getter
@Setter
public class ApikeyLoginForm {
public class AuthApiKeyLoginForm {
@Schema(
description = "사용자 API Key",
example = "YjeWJr9-N-XaJ7ix2M2spnfjOyG9nTmRlY9t6sKw4_M",
requiredMode = Schema.RequiredMode.REQUIRED
description = "사용자 API Key"
, example = "YjeWJr9-N-XaJ7ix2M2spnfjOyG9nTmRlY9t6sKw4_M"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "apiKey를 입력해주세요.")
private String userApiKey;
public LoginDto toLoginDto() {
public LoginDto toDto() {
LoginDto loginDto = new LoginDto();
loginDto.setUserApiKey(this.userApiKey.trim());
loginDto.setUserApiKey(userApiKey == null ? null : userApiKey.trim());
return loginDto;
}
@@ -0,0 +1,24 @@
package com.alist.api.modules.auth.form;
import com.alist.api.modules.auth.dto.AuthTokenDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Schema(description = "토큰 요청 폼")
public class AuthTokenForm {
@NotBlank(message = "사용자명은 필수입니다.")
@Schema(description = "사용자명", example = "test")
private String id;
public AuthTokenDto toDto() {
AuthTokenDto authTokenDto = new AuthTokenDto();
authTokenDto.setId(this.id.trim());
return authTokenDto;
}
}
@@ -11,22 +11,22 @@ import lombok.Setter;
@Setter
public class SsoExchangeForm {
@Schema(
description = "SSO 인가 코드",
example = "authorization-code",
requiredMode = Schema.RequiredMode.REQUIRED
description = "SSO 인가 코드"
, example = "authorization-code"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "인가 코드는 필수입니다.")
private String code;
@Schema(
description = "SSO 클라이언트 ID",
example = "alist-main",
requiredMode = Schema.RequiredMode.REQUIRED
description = "SSO 클라이언트 ID"
, example = "alist-main"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "클라이언트 ID는 필수입니다.")
private String clientId;
public SsoExchangeDto toSsoExchangeDto() {
public SsoExchangeDto toDto() {
SsoExchangeDto ssoExchangeDto = new SsoExchangeDto();
ssoExchangeDto.setCode(code == null ? null : code.trim());
ssoExchangeDto.setClientId(clientId == null ? null : clientId.trim());
@@ -11,22 +11,22 @@ import lombok.Setter;
@Setter
public class SsoLoginForm {
@Schema(
description = "회원 아이디",
example = "test",
requiredMode = Schema.RequiredMode.REQUIRED
description = "회원 아이디"
, example = "test"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "아이디는 필수입니다.")
private String id;
@Schema(
description = "비밀번호",
example = "password1234",
requiredMode = Schema.RequiredMode.REQUIRED
description = "비밀번호"
, example = "password1234"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "비밀번호는 필수입니다.")
private String password;
public SsoLoginDto toSsoLoginDto() {
public SsoLoginDto toDto() {
SsoLoginDto ssoLoginDto = new SsoLoginDto();
ssoLoginDto.setId(id == null ? null : id.trim());
ssoLoginDto.setPassword(password == null ? null : password.trim());
@@ -1,24 +0,0 @@
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;
import lombok.Setter;
@Getter
@Setter
@Schema(description = "토큰 요청 폼")
public class TokenForm {
@NotBlank(message = "사용자명은 필수입니다.")
@Schema(description = "사용자명", example = "test")
private String id;
public TokenDto toTokenDto() {
TokenDto tokenDto = new TokenDto();
tokenDto.setId(this.id.trim());
return tokenDto;
}
}
@@ -4,7 +4,6 @@ import com.alist.api.modules.auth.dto.LoginDto;
import com.alist.api.modules.auth.vo.LoginTokenVo;
import com.alist.api.modules.auth.vo.LoginVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface AuthMapper {
@@ -4,6 +4,8 @@ import com.alist.api.config.jwt.JwtTokenProvider;
import com.alist.api.config.properties.JwtProperties;
import com.alist.api.modules.auth.dto.LoginDto;
import com.alist.api.modules.auth.mapper.AuthMapper;
import com.alist.api.modules.auth.vo.AuthApiKeyLoginVo;
import com.alist.api.modules.auth.vo.AuthRefreshVo;
import com.alist.api.modules.auth.vo.LoginTokenVo;
import com.alist.api.modules.auth.vo.LoginVo;
import lombok.extern.slf4j.Slf4j;
@@ -28,70 +30,88 @@ public class AuthService {
this.jwtProperties = jwtProperties;
}
// id login
public LoginDto selectLogin(LoginDto loginDto) {
@Transactional
public LoginVo selectLogin(LoginDto loginDto) {
LoginVo loginVo = authMapper.selectLogin(loginDto);
if (loginVo != null) {
if (passwordEncoder.matches(loginDto.getPassword(), loginVo.getPassword())) {
if (loginVo == null) {
LoginVo loginVo1 = new LoginVo();
loginVo1.setResultCode(2003);
return loginVo1;
}
if (!passwordEncoder.matches(loginDto.getPassword(), loginVo.getPassword())) {
LoginVo loginVo1 = new LoginVo();
loginVo1.setResultCode(2003);
return loginVo1;
}
loginDto.setUserIdx(loginVo.getUserIdx());
LoginTokenVo loginTokenVo = authMapper.selectUserTokenByUserId(loginDto);
if (loginTokenVo != null) {
loginDto.setAccessToken(jwtTokenProvider.createAccessToken(loginTokenVo.getUserTokenIdx(), loginTokenVo.getUserRole()));
loginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(loginTokenVo.getUserTokenIdx()));
if (loginTokenVo == null) {
LoginVo loginVo1 = new LoginVo();
loginVo1.setResultCode(2003);
return loginVo1;
}
String accessToken = jwtTokenProvider.createAccessToken(loginTokenVo.getUserTokenIdx(), loginTokenVo.getUserRole());
String refreshToken = jwtTokenProvider.createRefreshToken(loginTokenVo.getUserTokenIdx());
Instant expiresAt = Instant.now().plusSeconds(jwtProperties.getRefreshTokenValiditySeconds());
loginVo.setAccessToken(accessToken);
loginVo.setRefreshToken(refreshToken);
loginVo.setUserTokenIdx(loginTokenVo.getUserTokenIdx());
loginVo.setUserRole(loginTokenVo.getUserRole());
loginVo.setUserType(loginTokenVo.getUserType());
loginVo.setExpiresAt(expiresAt);
loginVo.setResultCode(2001);
loginDto.setUserTokenIdx(loginVo.getUserTokenIdx());
loginDto.setRefreshToken(loginVo.getRefreshToken());
loginDto.setExpiresAt(loginVo.getExpiresAt());
authMapper.updateRefreshToken(loginDto);
authMapper.updateLoginByLastAt(loginDto);
return loginVo;
}
@Transactional
public AuthApiKeyLoginVo selectAuthApiKeyLogin(LoginDto loginDto) {
AuthApiKeyLoginVo authApiKeyLoginVo = new AuthApiKeyLoginVo();
if (loginDto.getUserApiKey() == null) {
authApiKeyLoginVo.setResultCode(2003);
return authApiKeyLoginVo;
}
LoginTokenVo loginTokenVo = authMapper.selectUserTokenByUserApiKey(loginDto);
if (loginTokenVo == null) {
authApiKeyLoginVo.setResultCode(2003);
return authApiKeyLoginVo;
}
String accessToken = jwtTokenProvider.createAccessToken(loginTokenVo.getUserTokenIdx(), loginTokenVo.getUserRole());
String refreshToken = jwtTokenProvider.createRefreshToken(loginTokenVo.getUserTokenIdx());
loginDto.setUserTokenIdx(loginTokenVo.getUserTokenIdx());
loginDto.setUserRole(loginTokenVo.getUserRole());
loginDto.setUserType(loginTokenVo.getUserType());
Instant now = Instant.now();
Instant expiresAt = now.plusSeconds(jwtProperties.getRefreshTokenValiditySeconds());
loginDto.setExpiresAt(expiresAt);
loginDto.setRefreshToken(refreshToken);
loginDto.setExpiresAt(Instant.now().plusSeconds(jwtProperties.getRefreshTokenValiditySeconds()));
authMapper.updateRefreshToken(loginDto);
// 마지막 로그인 시간 추가
authMapper.updateLoginByLastAt(loginDto);
authApiKeyLoginVo.setAccessToken(accessToken);
authApiKeyLoginVo.setRefreshToken(refreshToken);
authApiKeyLoginVo.setResultCode(2001);
loginDto.setResultCode(2001);
} else {
loginDto.setResultCode(2003);
}
} else {
loginDto.setResultCode(2003);
}
} else {
loginDto.setResultCode(2003);
return authApiKeyLoginVo;
}
return loginDto;
}
// api key login
public LoginDto selectUserApiKeyLogin(LoginDto loginDto) {
if (loginDto.getUserApiKey() != null) {
LoginTokenVo loginTokenVo = authMapper.selectUserTokenByUserApiKey(loginDto);
if (loginTokenVo != null) {
loginDto.setAccessToken(jwtTokenProvider.createAccessToken(loginTokenVo.getUserTokenIdx(), loginTokenVo.getUserRole()));
loginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(loginTokenVo.getUserTokenIdx()));
loginDto.setResultCode(2001);
} else {
loginDto.setResultCode(2003);
}
} else {
loginDto.setResultCode(2003);
}
return loginDto;
}
public LoginDto refreshByToken(String refreshToken) {
@Transactional
public AuthRefreshVo selectAuthRefresh(String refreshToken) {
if (refreshToken == null || !jwtTokenProvider.validateToken(refreshToken)) {
return null;
}
@@ -112,14 +132,22 @@ public class AuthService {
return null;
}
LoginDto result = new LoginDto();
result.setUserTokenIdx(userTokenIdx);
result.setAccessToken(jwtTokenProvider.createAccessToken(tokenVo.getUserTokenIdx(), tokenVo.getUserRole()));
result.setRefreshToken(jwtTokenProvider.createRefreshToken(tokenVo.getUserTokenIdx()));
result.setExpiresAt(Instant.now().plusSeconds(jwtProperties.getRefreshTokenValiditySeconds()));
String newAccessToken = jwtTokenProvider.createAccessToken(tokenVo.getUserTokenIdx(), tokenVo.getUserRole());
String newRefreshToken = jwtTokenProvider.createRefreshToken(tokenVo.getUserTokenIdx());
authMapper.updateRefreshToken(result);
return result;
LoginDto loginDto = new LoginDto();
loginDto.setUserTokenIdx(userTokenIdx);
loginDto.setRefreshToken(newRefreshToken);
loginDto.setExpiresAt(Instant.now().plusSeconds(jwtProperties.getRefreshTokenValiditySeconds()));
authMapper.updateRefreshToken(loginDto);
AuthRefreshVo authRefreshVo = new AuthRefreshVo();
authRefreshVo.setAccessToken(newAccessToken);
authRefreshVo.setRefreshToken(newRefreshToken);
authRefreshVo.setRefreshed(true);
return authRefreshVo;
}
public void clearRefreshToken(String refreshToken) {
@@ -2,10 +2,11 @@ package com.alist.api.modules.auth.service;
import com.alist.api.modules.auth.dto.SsoExchangeDto;
import com.alist.api.modules.auth.dto.SsoLoginDto;
import com.alist.api.modules.auth.dto.LoginDto;
import com.alist.api.modules.auth.mapper.SsoMapper;
import com.alist.api.modules.auth.vo.LoginVo;
import com.alist.api.modules.auth.vo.SsoClientVo;
import jakarta.servlet.http.HttpServletRequest;
import com.alist.api.modules.auth.vo.SsoExchangeVo;
import com.alist.api.modules.auth.vo.SsoLoginVo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@@ -32,18 +33,18 @@ public class SsoService {
this.ssoCodeTtl = Duration.ofSeconds(ssoCodeTtlSeconds);
}
public SsoLoginDto login(SsoLoginDto ssoLoginDto, HttpServletRequest request) {
LoginDto loginDto = authService.selectLogin(ssoLoginDto.toLoginDto());
public SsoLoginVo selectSsoLogin(SsoLoginDto ssoLoginDto) {
LoginVo loginVo = authService.selectLogin(ssoLoginDto.toDto());
SsoLoginDto result = new SsoLoginDto();
result.setResultCode(loginDto.getResultCode());
SsoLoginVo ssoLoginVo = new SsoLoginVo();
ssoLoginVo.setResultCode(loginVo.getResultCode());
if (loginDto.getResultCode() == 2003) {
return result;
if (loginVo.getResultCode() == 2003) {
return ssoLoginVo;
}
String ssoSessionId = "SSO_" + UUID.randomUUID();
String latestSessionKey = "alist:sso:userIdx:" + loginDto.getUserIdx();
String latestSessionKey = "alist:sso:userIdx:" + loginVo.getUserIdx();
String sessionKey = "alist:sso:session:" + ssoSessionId;
String oldSsoSessionId = redisTemplate.opsForValue().get(latestSessionKey);
@@ -51,26 +52,26 @@ public class SsoService {
redisTemplate.delete("alist:sso:session:" + oldSsoSessionId);
}
redisTemplate.opsForHash().put(sessionKey, "userIdx", String.valueOf(loginDto.getUserIdx()));
redisTemplate.opsForHash().put(sessionKey, "userTokenIdx", String.valueOf(loginDto.getUserTokenIdx()));
redisTemplate.opsForHash().put(sessionKey, "userId", loginDto.getId());
redisTemplate.opsForHash().put(sessionKey, "userRole", loginDto.getUserRole());
redisTemplate.opsForHash().put(sessionKey, "userType", loginDto.getUserType());
redisTemplate.opsForHash().put(sessionKey, "userIdx", String.valueOf(loginVo.getUserIdx()));
redisTemplate.opsForHash().put(sessionKey, "userTokenIdx", String.valueOf(loginVo.getUserTokenIdx()));
redisTemplate.opsForHash().put(sessionKey, "userId", loginVo.getId());
redisTemplate.opsForHash().put(sessionKey, "userRole", loginVo.getUserRole());
redisTemplate.opsForHash().put(sessionKey, "userType", loginVo.getUserType());
redisTemplate.expire(sessionKey, ssoSessionTtl);
redisTemplate.opsForValue().set(latestSessionKey, ssoSessionId, ssoSessionTtl);
result.setSsoSessionId(ssoSessionId);
result.setUserId(loginDto.getId());
result.setUserIdx(loginDto.getUserIdx());
result.setUserRole(loginDto.getUserRole());
result.setUserType(loginDto.getUserType());
result.setResultCode(200);
ssoLoginVo.setSsoSessionId(ssoSessionId);
ssoLoginVo.setUserId(loginVo.getId());
ssoLoginVo.setUserIdx(loginVo.getUserIdx());
ssoLoginVo.setUserRole(loginVo.getUserRole());
ssoLoginVo.setUserType(loginVo.getUserType());
ssoLoginVo.setResultCode(200);
return result;
return ssoLoginVo;
}
public SsoExchangeDto loginChecked(String ssoSessionId) {
public SsoExchangeVo selectSsoLoginChecked(String ssoSessionId) {
if (ssoSessionId == null || ssoSessionId.isBlank()) {
return null;
}
@@ -94,18 +95,18 @@ public class SsoService {
String userRole = (String) redisTemplate.opsForHash().get(sessionKey, "userRole");
String userType = (String) redisTemplate.opsForHash().get(sessionKey, "userType");
SsoExchangeDto result = new SsoExchangeDto();
result.setSsoSessionId(ssoSessionId);
result.setUserId(userId);
result.setUserIdx(userIdx == null ? null : Integer.parseInt(userIdx));
result.setUserTokenIdx(userTokenIdx == null ? null : Integer.parseInt(userTokenIdx));
result.setUserRole(userRole);
result.setUserType(userType);
SsoExchangeVo ssoExchangeVo = new SsoExchangeVo();
ssoExchangeVo.setSsoSessionId(ssoSessionId);
ssoExchangeVo.setUserId(userId);
ssoExchangeVo.setUserIdx(userIdx == null ? null : Integer.parseInt(userIdx));
ssoExchangeVo.setUserTokenIdx(userTokenIdx == null ? null : Integer.parseInt(userTokenIdx));
ssoExchangeVo.setUserRole(userRole);
ssoExchangeVo.setUserType(userType);
return result;
return ssoExchangeVo;
}
public String authorize(String clientId, String redirectUri, String state, String ssoSessionId) {
public String selectSsoAuthorize(String clientId, String redirectUri, String state, String ssoSessionId) {
if (ssoSessionId == null || ssoSessionId.isBlank()) {
return "/login";
}
@@ -158,7 +159,7 @@ public class SsoService {
return redirect.toString();
}
public SsoExchangeDto exchange(SsoExchangeDto ssoExchangeDto) {
public SsoExchangeVo selectSsoExchange(SsoExchangeDto ssoExchangeDto) {
String codeKey = "alist:sso:code:" + ssoExchangeDto.getCode();
String ssoSessionId = (String) redisTemplate.opsForHash().get(codeKey, "ssoSessionId");
String clientId = (String) redisTemplate.opsForHash().get(codeKey, "clientId");
@@ -182,17 +183,18 @@ public class SsoService {
refreshSsoSessionTtl(ssoSessionId, userIdx);
redisTemplate.delete(codeKey);
SsoExchangeDto result = new SsoExchangeDto();
result.setUserId(userId);
result.setUserIdx(Integer.parseInt(userIdx));
result.setUserTokenIdx(Integer.parseInt(userTokenIdx));
result.setUserRole(userRole);
result.setUserType(userType);
result.setSsoSessionId(ssoSessionId);
return result;
SsoExchangeVo ssoExchangeVo = new SsoExchangeVo();
ssoExchangeVo.setUserId(userId);
ssoExchangeVo.setUserIdx(Integer.parseInt(userIdx));
ssoExchangeVo.setUserTokenIdx(Integer.parseInt(userTokenIdx));
ssoExchangeVo.setUserRole(userRole);
ssoExchangeVo.setUserType(userType);
ssoExchangeVo.setSsoSessionId(ssoSessionId);
return ssoExchangeVo;
}
public void logout(String ssoSessionId) {
public void updateSsoLogout(String ssoSessionId) {
if (ssoSessionId == null || ssoSessionId.isBlank()) {
return;
}
@@ -0,0 +1,27 @@
package com.alist.api.modules.auth.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "SSO 기반 토큰 발급 응답")
@Getter
@Setter
public class AuthAccessVo {
@JsonProperty("isAccessToken")
@Schema(description = "Access Token 발급 여부", example = "true")
private boolean accessToken;
@Schema(description = "회원 PK", example = "1")
private Integer userIdx;
@Schema(description = "회원 아이디", example = "test")
private String userId;
@Schema(description = "회원 권한 코드", example = "CCD/SCM/I/R/E")
private String userRole;
@Schema(description = "회원 유형 코드. T: 교사, S: 학생", example = "S")
private String userType;
}
@@ -0,0 +1,24 @@
package com.alist.api.modules.auth.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "API Key 로그인 응답")
@Getter
@Setter
public class AuthApiKeyLoginVo {
@Schema(description = "아이디")
private String id;
@Schema(description = "엑세스 토큰")
private String accessToken;
@Schema(description = "리플레시 토큰")
private String refreshToken;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -0,0 +1,22 @@
package com.alist.api.modules.auth.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "Access Token 재발급 응답")
@Getter
@Setter
public class AuthRefreshVo {
@Schema(description = "토큰 재발급 여부", example = "true")
private boolean refreshed;
@JsonIgnore
@Schema(hidden = true)
private String accessToken;
@JsonIgnore
@Schema(hidden = true)
private String refreshToken;
}
@@ -1,20 +1,20 @@
package com.alist.api.modules.auth.dto;
package com.alist.api.modules.auth.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
@Schema(description = "테스트용 토큰 발급 응답")
@Getter
@Setter
public class TokenDto {
public class AuthTokenVo {
@Schema(description = "아이디")
private String id;
@Schema(description = "엑세스 토큰")
private String accessToken;
@Schema(description = "리플레시 토큰")
private String refreshToken;
@@ -1,10 +1,22 @@
package com.alist.api.modules.auth.vo;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
public class LoginVo {
private Integer userIdx;
private String id;
private String password;
private String accessToken;
private String refreshToken;
private Integer userTokenIdx;
private String userRole;
private String userType;
private Instant expiresAt;
private int resultCode;
}
@@ -0,0 +1,34 @@
package com.alist.api.modules.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "SSO 인가 코드 교환 응답")
@Getter
@Setter
public class SsoExchangeVo {
@Schema(description = "SSO 인가 코드", example = "authorization-code")
private String code;
@Schema(description = "SSO 클라이언트 ID", example = "alist-main")
private String clientId;
@Schema(description = "SSO 세션 ID")
private String ssoSessionId;
@Schema(description = "회원 PK", example = "1")
private Integer userIdx;
@Schema(description = "회원 토큰 PK", example = "1")
private Integer userTokenIdx;
@Schema(description = "회원 아이디", example = "test")
private String userId;
@Schema(description = "회원 권한 코드(CCD/SCM/I/R/E)", example = "SCM")
private String userRole;
@Schema(description = "회원 유형 코드. T: 교사, S: 학생", example = "T")
private String userType;
}
@@ -0,0 +1,40 @@
package com.alist.api.modules.auth.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "SSO 로그인 상태 확인 응답")
@Getter
@Setter
public class SsoLoginCheckedVo {
@JsonProperty("isAccessToken")
@Schema(description = "Access Token 쿠키 존재 여부", example = "true")
private boolean accessToken;
@JsonProperty("isRefreshToken")
@Schema(description = "Refresh Token 쿠키 존재 여부", example = "true")
private boolean refreshToken;
@Schema(description = "로그인 상태 여부", example = "true")
private boolean loggedIn;
@Schema(description = "SSO 세션 ID")
private String ssoSessionId;
@Schema(description = "회원 아이디", example = "test")
private String userId;
@Schema(description = "회원 PK", example = "1")
private Integer userIdx;
@Schema(description = "회원 토큰 PK", example = "1")
private Integer userTokenIdx;
@Schema(description = "회원 권한 코드", example = "CCD/SCM/I/R/E")
private String userRole;
@Schema(description = "회원 유형 코드. T: 교사, S: 학생", example = "S")
private String userType;
}
@@ -1,5 +1,6 @@
package com.alist.api.modules.auth.dto;
package com.alist.api.modules.auth.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -7,7 +8,7 @@ import lombok.Setter;
@Schema(description = "SSO 로그인 응답")
@Getter
@Setter
public class SsoLoginCheckDto {
public class SsoLoginVo {
@Schema(description = "로그인 성공 여부", example = "true")
private boolean loginIn;
@@ -22,4 +23,20 @@ public class SsoLoginCheckDto {
@Schema(description = "회원 유형 코드. T: 교사, S: 학생", example = "S")
private String userType;
@JsonIgnore
@Schema(hidden = true)
private String ssoSessionId;
@JsonIgnore
@Schema(hidden = true)
private Integer userTokenIdx;
@JsonIgnore
@Schema(hidden = true)
private String userId;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -0,0 +1,13 @@
package com.alist.api.modules.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "SSO 로그아웃 응답")
@Getter
@Setter
public class SsoLogoutVo {
@Schema(description = "로그아웃 처리 여부", example = "true")
private boolean logout;
}
@@ -1,22 +0,0 @@
package com.alist.api.modules.auth.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "테스트용 토큰 발급 응답")
@Getter
@Setter
public class TokenVo {
@Schema(description = "아이디")
private String id;
@Schema(description = "엑세스 토큰")
private String accessToken;
@Schema(description = "리플레시 토큰")
private String refreshToken;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}