5

ServiceStack を介して HTML ページと Web サービスを提供する ASP MVC アプリケーションのアーキテクチャを解決するのに問題があります。

アプリケーションは " http://myapplication.com " などのベース URL に存在し、SS は " http://myapplication.com/api " に存在します。両方を構成する最も簡単な方法だからです。

一般的にはすべて正常に動作しますが、承認と認証の部分に到達すると、行き詰まります。

1 つには、ASP が通常 FormsAuthentication を介して Cookie を処理する必要があり、ユーザーはログイン画面を通過し、属性「Authorize」が使用されているときにアクションとコントローラーを使用できます。これは典型的なASPなので、「 http://myapplication.com/PurchaseOrders 」などで問題ありません。

一方、アプリケーションのクライアントは、javascript から Web サービス API を使用します。これらの Web サービスは、ServiceStack の属性「Authenticate」でタグ付けされる場合もあります。たとえば、「http://myapplication.com/api/purchaseorders/25」は、ユーザーがその特定の注文書を表示できるかどうかを検証する必要があります。それ以外の場合は、401 Unauthorized を送信して、JavaScript がそれらのケースを処理し、エラー メッセージを表示できるようにします。

最後になりましたが、別のユーザー グループが、任意の外部アプリケーション (おそらく Java または .NET) を使用して、トークンによって私の API を利用します。そのため、2 種類の認証を解決する必要があります。1 つはユーザー名とパスワードを使用し、もう 1 つはトークンを使用して永続化します。これにより、最初に認証されると、次の呼び出しは 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/credentials に投稿を送信すると、この情報が保存されるため、サービスへの次の呼び出しでは、リクエストは既に承認されています。

私が知る必要がある質問は、Account コントローラーからログインしているユーザーを呼び出す (そしておそらく SS のどこかに設定する) 方法です。コードの最初のブロックが表示された場合、Web サービスを呼び出そうとしていますが (間違っているように見えます)、動作しますが、Web サービスへの次の呼び出しは認証されていないように見えます。

私に ServiceStack のチュートリアルを教えないでください。私は過去 2 日間そこにいましたが、まだ理解できません。

よろしくお願いします。

4

1 に答える 1

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;
    }
}
于 2013-08-22T15:22:44.250 に答える