[] 카카오 로그인 구현

1. 인가코드 받기
2. 리다이렉트 -> 인가 코드를 엑세스 토큰으로
3. User.Me -> snsID 받아오기
4. DB 에서 snsID 조회 까지 완료
This commit is contained in:
김선규 2025-05-29 17:53:50 +09:00
parent bc4658afca
commit 65962c01c2
10 changed files with 138 additions and 78 deletions

View File

@ -20,6 +20,13 @@
<ItemGroup>
<Folder Include="Program\Controllers\V1\Interfaces\" />
<Folder Include="publish\debug\" />
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="wwwroot\css\app.css" />
<_ContentIncludedByDefault Remove="wwwroot\css\tailwind.css" />
</ItemGroup>

View File

@ -127,7 +127,7 @@ builder.Services.AddScoped<IRepositoryService, RepositoryService>();
builder.Services.AddScoped<IHeaderConfig, HeaderConfigRepository>();
builder.Services.AddScoped<IUserService, UserService>();
// builder.Services.AddScoped<IKakaoService, KakaoService>();
builder.Services.AddScoped<IKakaoService, KakaoService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IAppService, AppService>();

View File

@ -24,6 +24,13 @@ namespace Back.Program.Common.Auth
await _next(context);
return;
}
if (context.Request.Path.Value != null && context.Request.Path.Value.Contains("/out/"))
{
await _next(context);
return;
}
// 정적 파일 요청은 미들웨어 건너뜀
var path = context.Request.Path.Value;
@ -58,12 +65,11 @@ namespace Back.Program.Common.Auth
}
await _next(context);
}
{
await _next(context);
return;
}
await _next(context);
}
}
}

View File

@ -0,0 +1,64 @@
using System.Text.Json;
using Back.Program.Controllers.V1;
using Back.Program.Services.V1;
using Back.Program.Services.V1.Interfaces;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
namespace Back.Program.Controllers;
[ApiController]
[Route("/api/v1/out/user")]
[ApiExplorerSettings(GroupName = "외부 동작(사용자)")]
public class OutController: ControllerBase
{
private readonly ILogger<OutController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly IUserService _userService;
private readonly IKakaoService _kakaoService;
public OutController(ILogger<OutController> logger,
IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService)
{
_logger = logger;
_repositoryService = repositoryService;
_userService = userService;
_kakaoService = kakaoService;
}
[HttpGet("kakao/auth")]
[CustomOperation("카카오 로그인", "카카오 로그인 동작", "사용자")]
public async Task<IActionResult> KakaoLogin([FromQuery] string? scope)
{
var url = await _kakaoService.GetAuthorizationUrl(scope ?? "");
Console.WriteLine($"카카오 로그인 API: {url}");
return Ok(new { url });
}
[HttpGet("kakao/redirect")]
public async Task<IActionResult> RedirectFromKakao([FromQuery] string code)
{
var (success, response) = await _kakaoService.Redirect(code);
Console.WriteLine($"리다이렉트 : {response}");
if (success)
{
// HttpContext.Session.SetString("AccessToken", response);
var (idSuccess, idResponse) = await _kakaoService.UserMe(response);
if (idSuccess)
{
var json = JsonDocument.Parse(idResponse);
if (json.RootElement.TryGetProperty("id", out var idElement))
{
var snsId = idElement.ToString();
Console.WriteLine($"ID = {snsId}");
var loginResult = await _userService.Login("SNS Login", "ST01", snsId);
Console.WriteLine($"login = {loginResult.JsonToString()}");
// return Ok(new { id="cc" });
}
}
Console.WriteLine($"ID_res = {idResponse}");
}
return BadRequest();
}
}

View File

@ -5,6 +5,7 @@ using Back.Program.Common.Model;
using Back.Program.Models.Entities;
using Back.Program.Services.V1.Interfaces;
using Back.Program.Services.V1;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -21,7 +22,6 @@ namespace Back.Program.Controllers.V1
private readonly ILogger<UserController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly IUserService _userService;
private readonly IKakaoService _kakaoService;
public UserController(ILogger<UserController> logger,
IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService)
@ -29,7 +29,6 @@ namespace Back.Program.Controllers.V1
_logger = logger;
_repositoryService = repositoryService;
_userService = userService;
_kakaoService = kakaoService;
}
[HttpGet]
@ -106,15 +105,12 @@ namespace Back.Program.Controllers.V1
var result = await _userService.GetAcademy(summary, token);
return Ok(result);
}
[HttpGet("kakao/auth")]
[CustomOperation("카카오 로그인", "카카오 로그인 동작", "사용자")]
public async Task<IActionResult> KakaoLogin([FromQuery] string scope)
{
var authUrl = await _kakaoService.GetAuthorizationUrl(scope);
return Redirect(authUrl);
}
}
}
@ -122,7 +118,7 @@ namespace Back.Program.Controllers.V1
// 근데 회원 정보를 변경하는게 뭐뭐를 변경해야 하는지 아직 정해진게 없어서 이건 일단 보류
/*
[HttpGet("set")]
[CustomOperation("회원 정보 변경", "회원 정보 변경", "사자")]
[CustomOperation("회원 정보 변경", "회원 정보 변경", "사자")]
public async Task<IActionResult> SetUserData(string token, string refresh) //, [FromBody])
{
if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh))

View File

@ -9,4 +9,5 @@ public interface IKakaoService
Task<(bool Success, string Response)> Redirect(string code);
Task<(bool Success, string Response)> Logout(string accessToken);
Task<(bool Success, string Response)> Unlink(string accessToken);
Task<(bool Success, string Response)> UserMe(string accessToken);
}

View File

@ -53,6 +53,11 @@ public class KakaoService: IKakaoService
}
}
/// <summary>
/// 인가 받은 코드로 토큰 받기를 실행하고 이 토큰으로 사용자 정보 가져오기를 할 수 있다.
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public async Task<string> GetAccessToken(string code)
{
var content = new FormUrlEncodedContent(new[]
@ -81,6 +86,13 @@ public class KakaoService: IKakaoService
return JsonSerializer.Serialize(new { access_token = accessToken.GetString() });
}
/// <summary>
/// 인가코드를 받는다 이 인가코드를 사용해 토큰 받기를 요청 할 수 있다.
/// 이게 리다이렉트에 포함되어있음
/// </summary>
/// <param name="scope"></param>
/// <returns></returns>
public Task<string> GetAuthorizationUrl(string scope)
{
var authUrl = $"{KAKAO_AUTH_BASE_URL}/oauth/authorize?client_id={_clientId}&redirect_uri={_redirectUri}&response_type=code";
@ -132,4 +144,14 @@ public class KakaoService: IKakaoService
var response = await Call(HttpMethod.Post, $"{KAKAO_API_BASE_URL}/v1/user/unlink");
return (true, response);
}
public async Task<(bool Success, string Response)> UserMe(string accessToken)
{
if (string.IsNullOrEmpty(accessToken))
return (false, JsonSerializer.Serialize(new { error = "Not logged in" }));
SetHeaders(accessToken);
var response = await Call(HttpMethod.Get, $"{KAKAO_API_BASE_URL}/v2/user/me");
return (true, response);
}
}

View File

@ -1,5 +1,7 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.Json;
using Back.Program.Common.Auth;
using Back.Program.Common.Data;
using Back.Program.Common.Model;
@ -14,6 +16,7 @@ namespace Back.Program.Services.V1
Task<bool> SaveData<T>(T entity, Expression<Func<T, object>> key = null) where T : class;
Task<bool> DeleteData<T>(T entity, Expression<Func<T, object>> key = null) where T : class;
String ReadSummary(Type type, String name);
Task SendFrontData<T>(T data, string url);
}
public class RepositoryService: IRepositoryService
@ -28,61 +31,6 @@ namespace Back.Program.Services.V1
_logger = logger;
_jwtTokenService = jwtTokenService;
}
// public async Task<ValidateToken> ValidateToken(string token, string refresh)
// {
// var principalToken = await _jwtTokenService.ValidateToken(token);
// if (principalToken != null)
// {
// var uid = principalToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
// _logger.LogInformation($"토큰 변환 - {uid}");
// return new ValidateToken
// {
// token = token,
// refresh = refresh,
// uid = uid
// };
// }
// else
// {
// _logger.LogInformation("엑세스 토큰 만료");
// // var refreshToken = await _dbContext.RefreshTokens
// // .FirstOrDefaultAsync(t => t.refresh_token == refresh);
// // if (refreshToken == null)
// // throw new TokenException("입력 받은 토큰 자체의 문제");
// //
// // var uid = refreshToken.uid;
// //
// // if (refreshToken.revoke_Date < DateTime.Now)
// // throw new RefreshRevokeException("리프레시 토큰 해지");
// //
// // if (refreshToken.expire_date > DateTime.Now)
// // {
// // _logger.LogInformation($"인증 완료 리프레시 : {uid}");
// // var access = _jwtTokenService.GenerateJwtToken(uid);
// //
// // return new ValidateToken
// // {
// // token = access,
// // refresh = refreshToken.refresh_token,
// // uid = uid
// // };
// // }
// // else
// // {
// // refreshToken = _jwtTokenService.GenerateRefreshToken(uid);
// // _logger.LogInformation("리프레시 토큰 만료");
// // // await SaveData<RefreshToken, string>(refreshToken, rt => rt.uid);
// // return new ValidateToken
// // {
// // token = token,
// // refresh = refreshToken.refresh_token,
// // uid = uid
// // };
// // }
// }
// }
public async Task<bool> SaveData<T>(T entity, Expression<Func<T, object>> key = null) where T : class
{
@ -227,5 +175,14 @@ namespace Back.Program.Services.V1
var att = method.GetCustomAttribute<CustomOperationAttribute>() ?? throw new AcaException(ResposeCode.NetworkErr,"swagger summary Load ERROR: NULL");
return att.Summary;
}
public async Task SendFrontData<T>(T data, string url)
{
using var httpClient = new HttpClient();
var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
}
}
}

View File

@ -7,8 +7,15 @@
},
"PushFileSetting": {
"uri": "https://api.sandbox.push.apple.com/",
"p12Path": "/src/private/AM_Push_Sandbox.p12",
"p12PWPath": "/src/private/appleKeys.json",
"p12Path": "./private/AM_Push_Sandbox.p12",
"p12PWPath": "./private/appleKeys.json",
"apnsTopic": "me.myds.ipstein.acamate.AcaMate"
},
"AllowedHosts": "*",
"Kakao": {
"ClientId": "a9632e6c14d8706ef6c8fe2ef52b721d",
"ClientSecret": " this is rest api secret key ",
"RedirectUri": "http://0.0.0.0:5144/api/v1/out/user/kakao/redirect"
// "RedirectUri": "https://acamate.ipstein.com/kakao"
}
}

View File

@ -11,10 +11,10 @@
"P12PWPath": "/src/private/appleKeys.json",
"ApnsTopic": "me.myds.ipstein.acamate.AcaMate"
},
"AllowedHosts": "*"
// "Kakao": {
// "ClientId": "a9632e6c14d8706ef6c8fe2ef52b721d",
// "ClientSecret": " this is rest api secret key ",
// "RedirectUri": "https://acamate.ipstein.com/kakao"
// }
"AllowedHosts": "*",
"Kakao": {
"ClientId": "a9632e6c14d8706ef6c8fe2ef52b721d",
"ClientSecret": " this is rest api secret key ",
"RedirectUri": "https://acamate.ipstein.com/kakao"
}
}