我正在尝试将OWIN应用到我的应用程序中,但我讨厌登录控制器,所以我决定尝试将登录信息移到我的提供程序中。
我的控制器方法如下所示:
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("externallogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLoginAsync(string provider, string error = null) => await _userProvider.GetExternalLoginAsync(this, User.Identity, provider, error);
在我的 UserProvider 中,我已将代码更改为:
public async Task<IHttpActionResult> GetExternalLoginAsync(ApiController controller, IIdentity identity, string provider, string error = null)
{
// If we have an error, return it
if (error != null) throw new Exception(Uri.EscapeDataString(error));
// If the current user is not authenticated
if (!identity.IsAuthenticated) return new ChallengeResult(provider, controller);
// Validate the client and the redirct url
var redirectUri = await ValidateClientAndRedirectUriAsync(controller.Request);
// If we have no validated
if (!string.IsNullOrWhiteSpace(redirectUri)) throw new Exception(redirectUri);
// Get the current users external login
var externalLogin = GetExternalLoginDataFromIdentity(identity as ClaimsIdentity);
// If the current user has no external logins, throw an error
if (externalLogin == null) throw new ObjectNotFoundException();
// if the login provider doesn't match the one specificed
if (externalLogin.LoginProvider != provider)
{
// Sign the user out
AuthenticationManager(controller.Request).SignOut(DefaultAuthenticationTypes.ExternalCookie);
// Challenge the result
return new ChallengeResult(provider, controller);
}
// Get the user
var user = await FindAsync(new UserLoginInfo(externalLogin.LoginProvider, externalLogin.ProviderKey));
// Have they registered
bool hasRegistered = user != null;
// Update our url
redirectUri += $"{redirectUri}#external_access_token={externalLogin.ExternalAccessToken}&provider={externalLogin.LoginProvider}&haslocalaccount={hasRegistered.ToString()}&external_user_name={externalLogin.UserName}";
// Return our url
return new RedirectResult(redirectUri);
}
对于这个特定的方法,这不是一个太大的问题,但我希望能够使用控制器的 Ok(),BadRequest()和InternalServerError()方法。另外,我想使用 ModelState.IsValid 。后者可以通过将控制器传递给方法来使用,我可以简单地执行:
controller.ModelState.IsValid
但其他方法是内部方法,所以我必须在我的提供者中继承 ApiController ,我无法做到或找到另一种方法。
我发现this on stackoverflow让我觉得我可以做类似的事情,因为我已经将控制器传递给方法了,所以我创建了一个像这样的静态Extension类:
public static class ResponseExtensions
{
public static HttpResponseMessage Ok(T content) => new HttpResponseMessage(HttpStatusCode.Accepted, content);
}
在我的方法中,我希望做到这样的事情:
return controller.ResponseMessage(ResponseExtensions.Ok());
但出现同样的问题是 ResponseMessage 也是受保护的内部方法。
以前有没有人遇到这个问题,能否就如何解决这个问题给出一些指示?
答案 0 :(得分:2)
首先,您的控制器中没有“登录”。实际的身份验证和授权已经足够抽象。您在控制器中拥有的所有内容都是调用,这对控制器来说非常有效。进一步抽象的唯一原因是,如果你需要做一些事情,比如在多个不同的控制器中登录。否则,你只是在浪费时间。
其次,您不需要控制器实例来返回各种结果类型。像Controller
,Ok
等BadRequest
上的方法只是方便的方法。它们各自返回ActionResult
个子类,即OkResult
,BadRequestResult
等。如果您的“提供者”需要返回操作结果,只需返回这些类的新实例。
答案 1 :(得分:0)
我认为这样做的方法是使用(argh)方法隐藏。 我创建了一个 BaseApiController :
public class BaseApiController : ApiController
{
public IHttpActionResult Ok<T>(T content) => Ok<T>(content);
public IHttpActionResult Ok() => Ok();
public IHttpActionResult InternalServerError() => InternalServerError();
public IHttpActionResult InternalServerError(Exception exception) => InternalServerError(exception);
public IHttpActionResult BadRequest() => BadRequest();
public IHttpActionResult BadRequest(ModelStateDictionary modelState) => BadRequest(modelState);
public IHttpActionResult BadRequest(string message) => BadRequest(message);
public IHttpActionResult Redirect(Uri location) => Redirect(location);
public IHttpActionResult Redirect(string location) => Redirect(location);
}
然后我的 UserController 只是继承了这个基本控制器:
public class UsersController : BaseApiController
然后我可以将提供商方法更改为:
public async Task<IHttpActionResult> GetExternalLoginAsync(BaseApiController controller, IIdentity identity, string provider, string error = null)
{
// If we have an error, return it
if (error != null) return controller.BadRequest(Uri.EscapeDataString(error));
// If the current user is not authenticated
if (!identity.IsAuthenticated) return new ChallengeResult(provider, controller);
// Validate the client and the redirct url
var redirectUri = await ValidateClientAndRedirectUriAsync(controller.Request);
// If we have no validated
if (!string.IsNullOrWhiteSpace(redirectUri)) return controller.BadRequest(redirectUri);
// Get the current users external login
var externalLogin = GetExternalLoginDataFromIdentity(identity as ClaimsIdentity);
// If the current user has no external logins, throw an error
if (externalLogin == null) return controller.InternalServerError();
// if the login provider doesn't match the one specificed
if (externalLogin.LoginProvider != provider)
{
// Sign the user out
AuthenticationManager(controller.Request).SignOut(DefaultAuthenticationTypes.ExternalCookie);
// Challenge the result
return new ChallengeResult(provider, controller);
}
// Get the user
var user = await FindAsync(new UserLoginInfo(externalLogin.LoginProvider, externalLogin.ProviderKey));
// Have they registered
bool hasRegistered = user != null;
// Update our url
redirectUri += $"{redirectUri}#external_access_token={externalLogin.ExternalAccessToken}&provider={externalLogin.LoginProvider}&haslocalaccount={hasRegistered.ToString()}&external_user_name={externalLogin.UserName}";
// Return our url
return controller.Redirect(redirectUri);
}
这似乎奏效了。如果有任何更好的解决方案我会感兴趣,因为我并不想将整个控制器发送到我的方法。
答案 2 :(得分:0)
或者,如果您知道需要使用Redirect
方法,则可以将重定向lambda传递给您的方法。您还可以传递您想要使用的控制器中的所有数据。或者创建一个将包含这些lambda作为属性的对象:
class Context
{
Func<string, IHttpActionResult> Redirect { get; set; }
...
}
行动方法:
var context = new Context {Redirect = this.Redirect};
return UserProvider.GetExternalLoginAsync(..., context)
在提供者中:
return context.Redirect(redirectUri);
这可能是一种解决方法,但我宁愿重新考虑您的服务及其职责。