ServiceStack API和ASP MVC身份验证有两种方式

时间:2013-08-21 15:04:59

标签: c# .net authentication forms-authentication servicestack

我在解决通过ServiceStack服务html页面和Web服务的ASP MVC应用程序的体系结构时遇到了麻烦。

该应用程序位于基本网址中,例如“http://myapplication.com”,而SS位于“http://myapplication.com/api”中,因为这是配置两者的最简单方法。

总的来说一切正常,但当我到达授权和身份验证的一部分时,就是我被困住的地方。

首先,我需要应用程序处理cookie,因为ASP通常会执行FormsAuthentication,并且当使用属性“Authorize”时,用户将通过登录屏幕并使用操作和控制器。这是典型的ASP,所以我没有问题,例如“http://myapplication.com/PurchaseOrders”。

另一方面,我的应用程序的客户端将从javascript使用我的Web服务api。在某些情况下,还会使用ServiceStack的“Authenticate”属性标记这些Web服务。例如,“http://myapplication.com/api/purchaseorders/25”必须验证用户是否可以查看该特定采购订单,否则发送401 Unauthorized,以便javascript可以处理这些情况并显示错误消息。

最后但并非最不重要的是,另一组用户将使用任何外部应用程序(可能是Java或.NET)通过令牌使用我的API。因此,我需要解决两种类型的身份验证,一种使用用户名和密码,另一种使用令牌并使其持久化,因此一旦第一次进行身份验证,下一次调用就可以更快地从API中解决。

这是我到目前为止的代码,我只是简单地说明了这个例子。

    [HttpPost]
    public ActionResult Logon(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            JsonServiceClient client = new JsonServiceClient("http://myapplication.com/api/");
            var authRequest = new Auth { provider = CredentialsAuthProvider.Name, UserName = model.UserName, Password = model.Password, RememberMe = model.RememberMe };
            try
            {

                var loginResponse = client.Send(authRequest);

                FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(loginResponse.UserName, false, 60);
                var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
                Response.Cookies.Add(cookie);

                if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
                {
                    return Redirect(returnUrl);
                }
                else
                {
                    return RedirectToAction("Index", "Test");
                }

            }
            catch (Exception)
            {
                ModelState.AddModelError("", "Invalid username or password");
            }
        }

        return View();
    }

至于身份验证提供程序我正在使用此类

    public class MyCredentialsAuthProvider : CredentialsAuthProvider
{
    public MyCredentialsAuthProvider(AppSettings appSettings)
        : base(appSettings)
    {

    }

    public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
    {
        //Add here your custom auth logic (database calls etc)
        //Return true if credentials are valid, otherwise false
        if (userName == "testuser" && password == "nevermind")
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
    {
        //Fill the IAuthSession with data which you want to retrieve in the app eg:
        session.FirstName = "some_firstname_from_db";
        //...

        session.CreatedAt = DateTime.Now;
        session.DisplayName = "Mauricio Leyzaola";
        session.Email = "mauricio.leyzaola@gmail.com";
        session.FirstName = "Mauricio";
        session.IsAuthenticated = true;
        session.LastName = "Leyzaola";
        session.UserName = "mauricio.leyzaola";
        session.UserAuthName = session.UserName;
        var roles = new List<string>();
        roles.AddRange(new[] { "admin", "reader" });
        session.Roles = roles;

        session.UserAuthId = "uniqueid-from-database";

        //base.OnAuthenticated(authService, session, tokens, authInfo);

        authService.SaveSession(session, SessionExpiry);
    }
}

在AppHost的配置功能上我正在设置我的自定义身份验证类以将其用作默认值。我想我应该创建另一个类并在此处添加它来处理令牌场景。

            Plugins.Add(new AuthFeature(() => new CustomUserSession(),
            new IAuthProvider[] {
                new MyCredentialsAuthProvider(appSettings)
            }, htmlRedirect: "~/Account/Logon"));

到目前为止,ServiceStack正如预期的那样工作。我可以通过用户名和密码向/ auth /凭证提交一个帖子,它会存储这些信息,所以下次调用服务请求已经被授权了,到目前为止很棒!

我需要知道的问题是如何调用(并且可能在SS中的某个位置)从我的帐户控制器登录的用户。如果您看到我尝试调用Web服务的第一个代码块(看起来我做错了)并且它可以工作,但是对任何Web服务的下一次调用看起来都是未经身份验证的。

请不要指向ServiceStack教程,过去两天我一直在那里,仍然无法理解。

提前多多感谢。

1 个答案:

答案 0 :(得分:3)

以下是我通常使用的内容:

您可以使用以下代码替换“登录”操作方法:

    public ActionResult Login(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            try
            {
                var authService = AppHostBase.Resolve<AuthService>();
                authService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
                var response = authService.Authenticate(new Auth
                {
                    UserName = model.UserName,
                    Password = model.Password,
                    RememberMe = model.RememberMe
                });

                // add ASP.NET auth cookie
                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);

                return RedirectToLocal(returnUrl);
            }
            catch (HttpError)
            {
            }
        }

        // If we got this far, something failed, redisplay form
        ModelState.AddModelError("", "The user name or password provided is incorrect.");
        return View(model);
    }

...和插件:

           //Default route: /auth/{provider}
            Plugins.Add(new AuthFeature(() => new CustomUserSession(),
            new IAuthProvider[] {
                new CustomCredentialsAuthProvider(),
                new CustomBasicAuthProvider()
            }));

.... Auth提供者类:

public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{  
    public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
    {
        return UserLogUtil.LogUser(authService, userName, password);
    }
}

public class CustomBasicAuthProvider : BasicAuthProvider
{
    public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
    {
        return UserLogUtil.LogUser(authService, userName, password);
    }
}

...最后,记录实用程序类

internal static class UserLogUtil
{
    public static bool LogUser(IServiceBase authService, string userName, string password)
    {
        var userService = new UserService(); //This can be a webservice; or, you can just call your repository from here
        var loggingResponse = (UserLogResponse)userService.Post(new LoggingUser { UserName = userName, Password = password });

        if (loggingResponse.User != null && loggingResponse.ResponseStatus == null)
        {
            var session = (CustomUserSession)authService.GetSession(false);
            session.DisplayName = loggingResponse.User.FName.ValOrEmpty() + " " + loggingResponse.User.LName.ValOrEmpty();
            session.UserAuthId = userName;
            session.IsAuthenticated = true;
            session.Id = loggingResponse.User.UserID.ToString();

            // add roles and permissions
            //session.Roles = new List<string>();
            //session.Permissions = new List<string>();
            //session.Roles.Add("Admin);
            //session.Permissions.Add("Admin");

            return true;
        }
        else
            return false;
    }
}