도메인은 같으나 path에 따라 개별 세션 쿠키 생성 방식(multi-tenant scheme)에 대해 테스트를 해 봤는데요.
결론부터 말씀드리면, 되는데 보강이 필요합니다.
우선 코드부터 공유 드릴게요.
테스트 코드라서 보안에 취약한 부분이 있습니다.
운영에 그대로 적용하시면 안되세요!
Cookie Scheme 등록
각 path별 Cookie 인증 Scheme을 등록합니다.
builder.Services.AddAuthentication(o =>
{
o.RequireAuthenticatedSignIn = false;
})
.AddCookie("ACookie", options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
options.Cookie.Name = "ACookie";
options.Cookie.Path = "/A";
options.CookieManager = new CookieManager();
})
.AddCookie("BCookie", options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
options.Cookie.Name = "BCookie";
options.Cookie.Path = "/B";
options.CookieManager = new CookieManager();
})
.AddCookie("CCookie", options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
options.Cookie.Name = "CCookie";
options.Cookie.Path = "/C";
options.CookieManager = new CookieManager();
});
Path별 쿠키를 처리할 CookieManager
path 별로 쿠키를 처리할 CookieManager를 별도 구현합니다.
public class CookieManager : ICookieManager
{
private readonly ICookieManager ConcreteManager;
private string RemoveSubdomain(string host)
{
var splitHostname = host.Split('.');
//if not localhost
if (splitHostname.Length > 1)
{
return string.Join(".", splitHostname.Skip(1));
}
else
{
return host;
}
}
public CookieManager()
{
ConcreteManager = new ChunkingCookieManager();
}
public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options)
{
options.Domain = RemoveSubdomain(context.Request.Host.Host); //Set the Cookie Domain using the request from host
ConcreteManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
{
ConcreteManager.DeleteCookie(context, key, options);
}
public string GetRequestCookie(HttpContext context, string key)
{
var result = ConcreteManager.GetRequestCookie(context, key);
return result;
}
}
웹 요청시 Cookie 처리
Scheme별 쿠키 인증을 처리하여 Identity Context를 저장합니다.
app.Use(async (context, next) =>
{
var principal = new ClaimsPrincipal();
var result1 = await context.AuthenticateAsync("ACookie");
if (result1?.Principal != null)
{
principal.AddIdentities(result1.Principal.Identities);
}
var result2 = await context.AuthenticateAsync("BCookie");
if (result2?.Principal != null)
{
principal.AddIdentities(result2.Principal.Identities);
}
var result3 = await context.AuthenticateAsync("CCookie");
if (result3?.Principal != null)
{
principal.AddIdentities(result3.Principal.Identities);
}
context.User = principal;
await next();
});
쿠키 생성
SignInAsync시 tenant에 맞는 쿠키가 생성 되도록 지정합니다.
[HttpPost("session")]
public async Task Session()
{
var userPrincipal = GetPrincipal("Roh", "a@a.com", "AUser");
var options = new AuthenticationProperties
{
IsPersistent = true
};
await HttpContext.SignInAsync("ACookie", userPrincipal, options).ConfigureAwait(false);
}
테스트
Path별 API 리스트
API 테스트
B의 session 쿠키를 위해 POST API를 호출 합니다.
B 세션 쿠키가 생성 되었습니다.
현재 버전
기대결과에 맞는 모습을 보여줍니다.
- 쿠키가 path 별로 생성 됩니다.
- 같은 Path API 호출시 쿠키가 전달됩니다.
- 다른 Path API 호출시 쿠키가 전달되지 않습니다.
현재 버전 문제점
[Authorize(AuthenticationSchemes = "CCookie")]
[HttpGet("user")]
public UserResult GetCUser()
{
return GetUserResult();
}
CCookie를 먼저 생성하고, 위와 같이 특정 API에 인증 제약이 적용되지 않아 API 호출에 실패하는 현상을 보였습니다.
아마도, multi-tenant에 걸맞는 Cookie scheme 세부 처리 옵션이나 AuthorizationHandler를 재작성해서 적용해야 될 것 같은데 너무 깊게 들어가는 것 같아서 현재 수준으로 공유 드립니다.
참조하셔서 원하시는 결과 얻으시길 바래요.
참조