Как создать таймер сеанса входа для отдельных пользователей

Я работал над этим настраиваемым таймером , который я создал для сеанса входа каждого пользователя . До сих пор я не мог создавать индивидуальные таймеры для каждого сеанса входа.

Scenario:

Когда User1 входит в систему, таймер начнет отсчет . Когда User2 регистрируется, таймер Use1 сбрасывает значение, такое же, как таймер для User2. Кажется, что у них есть один таймер (не индивидуальный).

Вот что я хочу.

  • Когда пользователь1 войдет в систему, его таймер начнет подсчет.
  • Если таймер достигнет 900 секунд (15 минут), появится всплывающее окно с модальным полем, в котором говорится, что у его сеанса есть время.
  • модальный покажет обратный отсчет не менее 30 секунд.
  • после обратного отсчета пользователь автоматически выйдет из системы.
  • Каждый пользователь должен иметь свои собственные таймеры

Я сделал все, кроме последнего элемента Каждый пользователь должен иметь свои собственные таймеры

Вот мой код при создании таймера:

public class SessionTimer
{
    private static Timer timer;

    public static void StartTimer()
    {
        timer = new Timer();
        timer.Interval = (double)Utility.ActivityTimerInterval();
        timer.Elapsed += (s, e) => MonitorElapsedTime();

        timer.Start();
    }

    public static void ResetTimer()
    {
        TimeCount = 0;
        timer.Stop();
        timer.Start();
    }

    public static int TimeCount { get; set; }

    public static string ConnectionID { get; set; }

    private static void MonitorElapsedTime()
    {
        if (TimeCount >= Utility.TimerValue())
        {
            timer.Stop();
            Hubs.Notifier.SessionTimeOut(TimeCount);
        }
        else
        {
            Hubs.Notifier.SendElapsedTime(TimeCount);
        }

        TimeCount++;
    }
}  

После успешного входа в систему я собираюсь вызвать таймер, чтобы начать

[HttpPost]
public ActionResult SignIn(LoginCredentials info)
{
   //Success full login

    SessionTimer.StartTimer();

}  

Вот код signalr на сервере:

public class SessionTimerHub : Hub
{
    public void SendTimeOutNotice(int time)
    {
        Clients.Client(Context.ConnectionId).alertClient(time);
    }

    public void CheckElapsedTime(int time)
    {
        Clients.Client(Context.ConnectionId).sendElapsedTime(time);
    }

    public void UpdateConnectionID(string id)
    {
        SessionTimer.ConnectionID = id;
    }
}

public class Notifier
{
    public static void SessionTimeOut(int time)
    {
        var context = GlobalHost.ConnectionManager.GetHubContext();
        context.Clients.Client(SessionTimer.ConnectionID).alertClient(time);
    }

    public static void SendElapsedTime(int time)
    {
        var context = GlobalHost.ConnectionManager.GetHubContext();
        context.Clients.Client(SessionTimer.ConnectionID).sendElapsedTime(time);
    }
}  

И код jquery:

$(function() {

        /////////////////////////////////////////////////// SESSION TIMER
        var timer = $.connection.sessionTimerHub, $modaltimer = $('#session_timer_elapsed'), tt = null;

        timer.client.alertClient = function (time) {
            var $count = $modaltimer.find('.timer'), wait = 180;

            $count.text(wait);
            $modaltimer.modal('show');

            tt = setInterval(function() {
                $count.text(wait--);

                if (wait < 0) {
                    $.post('@Url.Action("Logout", "Auth")', function() { window.location.reload(); });
                    window.clearInterval(tt);
                }
            }, 1000);
        };

        timer.client.sendElapsedTime = function (time) {
            console.log(time);
        };

        $.connection.hub.start().done(function() {
            timer.server.updateConnectionID($.connection.hub.id);
        });

        $modaltimer.on('click', '.still_here', function() {
            $.post('@Url.Action("ResetTimer", "Auth")');
            $modaltimer.modal('hide');
            window.clearInterval(tt);
        }).on('click', '.log_out', function() {
            $.post('@Url.Action("Logout", "Auth")', function() { window.location.reload(); });
            $modaltimer.modal('hide');
        });

});  

Как вы видите, я делаю это:

timer.server.updateConnectionID($.connection.hub.id);  

передать идентификатор соединения, потому что я не могу получить идентификатор внутри public class Notifier

My failed solutions

Я попытался помещать SessionTimer в session с помощью dynamic и ExpandoObject
например:

public static dynamic Data
{
    get
    {
        #region FAILSAFE
        if (HttpContext.Current.Session[datakey] == null)
        {
            HttpContext.Current.Session[datakey] = new ExpandoObject();
        }
        #endregion

        return (ExpandoObject)HttpContext.Current.Session[datakey];
    }
}  

И он успешно отделил таймеры. Но при передаче идентификатора соединения в моей переменной expandoobject
например:

public void UpdateConnectionID(string id)
{
    MyExpandoObject.MySessionTimer.ConnectionID = id;
} 

он выбрасывает исключение для NULL. Кажется, что мой expandoObject получает значение null при передаче данных из SignalR (только мое мышление), но я не уверен в этом.

Пожалуйста, помогите мне с этими отдельными таймерами и отправьте сообщение конкретным пользователям, когда их таймеры истекли.

Please note
I want to create the timer on Server Side.

Resetting the timer from server

Таймер должен иметь возможность сброса на сервере. В этом случае я помещаю пользовательские атрибуты на каждый AcrionReseult

например.:

[HttpPost]
[BasecampAuthorize]
public ActionResult LoadEmailType()
{
    return Json(Enum.GetNames(typeof(EmailType)).ToList());
}  

Когда пользователь передает [BasecampAuthorize] , это означает, что он сделал операцию.
Внутри [BasecampAuthorize]

public class BasecampAuthorizeAttribute : AuthorizeAttribute
{
    string url { get; set; }

    public BasecampAuthorizeAttribute()
    {
        if (string.IsNullOrEmpty(url))
        {
            url = "~/SomeUrl";
        }
    }

    public BasecampAuthorizeAttribute(string URL)
    {
        url = URL;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.HttpContext.Response.Redirect(url);
        }
        else
        {
           //MUST RESET SESSION TIMER HERE
        }

        base.OnAuthorization(filterContext);
    }
}  

@ halter73 - Как я могу вызвать таймер сброса здесь?

1
пожалуйста, дайте повод для голосования. Я задаю вопрос, который мне трудно ответить.
добавлено автор fiberOptics, источник
хм ... может быть, ты ненавидишь мой вопрос. ладно, спасибо в любом случае.
добавлено автор fiberOptics, источник
@Henrik Спасибо за ваш комментарий, это мой первый раз, чтобы разобраться с этой функцией. Если вы знаете некоторые ссылки, учебники, которые описывают и обсуждают то, что вы сказали, пожалуйста, поделитесь со мной. Честно говоря, я не знаю, как создать программное обеспечение для удовлетворения этих требований. Я хочу узнать, пожалуйста, скажите мне, что делать. Благодаря!
добавлено автор fiberOptics, источник
Я думаю, что причина, по которой люди голосуют в этом вопросе, состоит в том, что вы не создаете программное обеспечение таким образом. Почему каждому клиенту нужен собственный таймер на сервере? Почему они не могут получить подписанный токен с сервера вместо этого, который действителен только в течение определенного времени, а затем JavaScript на стороне клиента реализует таймер?
добавлено автор Henrik, источник
@fiberOptics, я согласен с вами. Мне не нравится анонимный снайпер. Я не вижу ничего плохого в твоем вопросе.
добавлено автор Dave Alperovich, источник

3 ответы

Ваша проблема в том, что у вас есть только одна статическая переменная таймера, которая является экземпляром signle, совместно используемой во всем AppDomain. В ASP.NET AppDomain для каждого веб-приложения не для пользователя . Вы можете использовать статическую переменную, но эта переменная должна представлять собой коллекцию с уникальным таймером для каждого connectionId. Это сломается, если вы окажетесь ниже балансировки нагрузки, или IIS перезапустит приложение, которое, очевидно, создаст новый AppDomain.

public class SessionTimer : IDisposable
{
    public static readonly ConcurrentDictionary Timers;

    private readonly Timer timer;

    static SessionTimer()
    {
        Timers = new ConcurrentDictionary();
    }

    private SessionTimer(string connectionID)
    {
        ConnectionID = connectionID;
        timer = new Timer();
        timer.Interval = (double)Utility.ActivityTimerInterval();
        timer.Elapsed += (s, e) => MonitorElapsedTime();

        timer.Start();
    }

    private int TimeCount { get; set; }

    private string ConnectionID { get; set; }

    public static void StartTimer(string connectionID)
    {
        var newTimer = new SessionTimer(connectionID);
        if (!Timers.TryAdd(connectionID, newTimer))
        {
            newTimer.Dispose();
        }
    }

    public static void StopTimer(string connectionID)
    {
        SessionTimer oldTimer;
        if (Timers.TryRemove(connectionID, out oldTimer))
        {
            oldTimer.Dispose();
        }
    }

    public void ResetTimer()
    {
        TimeCount = 0;
        timer.Stop();
        timer.Start();
    }

    public override Dispose()
    {
       //Stop might not be necessary since we call Dispose
        timer.Stop();
        timer.Dispose();
    }

    private void MonitorElapsedTime()
    {
        if (TimeCount >= Utility.TimerValue())
        {
            StopTimer(ConnectionID);
            Hubs.Notifier.SessionTimeOut(ConnectionID, TimeCount);
        }
        else
        {
            Hubs.Notifier.SendElapsedTime(ConnectionID, TimeCount);
        }

        TimeCount++;
    }
}

Поскольку вы храните идентификатор соединения внутри класса SessionTimer , вы можете просто передать его в качестве параметра при вызове методов в классе Notifier .

public static class Notifier
{
    private static context = GlobalHost.ConnectionManager.GetHubContext(); 

    public static void SessionTimeOut(string connectionID, int time)
    {
        context.Clients.Client(connectionID).alertClient(time);
    }

    public static void SendElapsedTime(string connectionID, int time)
    {
        context.Clients.Client(connectionID).sendElapsedTime(time);
    }
}

Вам не нужен SendTimeOutNotice или CheckElapsedTime , поскольку вы вызываете методы клиента в классе Notifier . UpdateConnectionID можно заменить на OnConnected .

public class SessionTimerHub : Hub
{
    public override Task OnConnected()
    {
        SessionTimer.StartTimer(Context.ConnectionId);
        return base.OnConnected();
    }

    public override Task OnDisconnected()
    {
        SessionTimer.StopTimer(Context.ConnectionId);
        return base.OnDisconnected();
    }

    public void ResetTimer()
    {
        SessionTimer.Timers[Context.ConnectionId].ResetTimer();
    }
}

Вы должны создать свой SessionTimers внутри своего концентратора, чтобы вы могли связать его с вашим идентификатором соединения. Вы можете сделать это в OnConnected , как указано выше, но это означает, что вы должны только запустить ваше SignalR-соединение после входа в систему, и вы действительно хотите запустить свой SessionTimer для этого подключение. Это также помогает иметь ResetTimer на концентраторе, чтобы у вас был идентификатор соединения клиента. Кроме того, вы можете получить идентификатор соединения на клиенте из $. Connection.hub.id и опубликовать его.

$.connection.hub.start().done(function() {
    $modaltimer.on('click', '.still_here', function() {
        timer.server.resetTimer();
        $.post('@Url.Action("ResetTimer", "Auth")');
        $modaltimer.modal('hide');
        window.clearInterval(tt);
    });
});

РЕДАКТИРОВАТЬ:

Если по какой-либо причине SessionTimerHub.ResetTimer выбрасывает KeyNotFoundException (это не должно происходить, если вы вызываете timer.server.resetTimer после <кода > $. connection.hub.start (). done ), вы можете сделать следующее:

public void ResetTimer()
{
    SessionTimer timer;
    if (SessionTimer.Timers.TryGetValue(Context.ConnectionId, out timer))
    {
        timer.ResetTimer();
    }
    else
    {   
        SessionTimer.StartTimer(Context.ConnectionId);
    }
}

Если по какой-то причине IIS перезапустит ваше приложение, вы можете добавить это в SessionTimerHub.OnReconnected , поскольку клиенты будут повторно подключаться, но ваш статический SessionTimer.Timers будет сброшен, и все ваши SessionTimers исчезнут.

public override Task OnReconnected()
{
    if (!SessionTimer.Timers.ContainsKey(Context.ConnectionId))
    {
        SessionTimer.StartTimer(Context.ConnectionId);
    }
    return base.OnReconnected();
}

Вы никогда не вызываете SessionTimerHub.ResetTimer() в C# правильно?

4
добавлено
Благословение с небес! Спасибо за ваш ответ, я попробовал, и он работает !!!! Но я забыл включить, что сброс таймера имеет путь, 1) От щелчка ** Да, я здесь ** от клиента, и 2) С сервера. См. Мое редактирование. Спасибо!
добавлено автор fiberOptics, источник
возникла проблема при вызове SessionTimer.Timers [Context.ConnectionId] .ResetTimer (); он выдает сообщение об ошибке KeyNotFoundException . И когда я проверяю, что SessionTimer.Timers count равно 0. Почему это происходит?
добавлено автор fiberOptics, источник
Как я могу получить доступ к Context.ConnectionId , если мой код для сброса таймера находится внутри public override void OnAuthorization (AuthorizationContext filterContext) ?
добавлено автор fiberOptics, источник
Я так благодарен за ваши усилия, чтобы помочь мне с моей проблемой. Я просто хочу что-то прояснить, я хотел бы использовать BasecampAuthorizeAttribute.OnAuthorization , чтобы сбросить таймер, потому что это единственный способ (просто мое мышление). Я знаю, чтобы поймать весь запрос, отправленный в ActionResult через ajax $. post . И с этим могу сказать, что пользователь делает активность.
добавлено автор fiberOptics, источник
Я также думаю, что если бы я смог поймать все события $. Post на странице, мне просто нужно вызвать timer.server.resetTimer , чтобы сбросить таймер , И мне больше не нужно использовать BasecampAuthorizeAttribute.OnAuthorization . Но пока у меня нет большой идеи для этого.
добавлено автор fiberOptics, источник
Получение SessionTimer.Timers [Context.ConnectionId] должно всегда работать, если оно вызывается вызовом timer.server.resetTimer в $. Connection.hub.start (). Done . Вы можете сделать Timers.TryGetValue msdn.microsoft.com /en-us/library/dd267270.aspx и запустите новый SessionTimer, если он не существует.
добавлено автор halter73, источник
Если вам нужно сбросить таймер в OnAuthorization , я просто использовал бы что-то вроде IIdentitity.Name в качестве ключа для SessionTimer.Timers , поскольку это доступно в вашем концентраторе и в OnAuthorization . Внутри SessionTimerHub к этому можно получить доступ как Context.User.Identity.Name . Внутри OnAuthorization это filterContext.HttpContext.User.Identity.Name . Я не хочу снова редактировать свой ответ. Вы можете просто заменить идентификатор соединения с именем идентификации почти везде.
добавлено автор halter73, источник
BTW, BasecampAuthorizeAttribute.OnAuthorization , скорее всего, вызывается до SessionTimerHub.OnConnected , поэтому вы, вероятно, захотите запустить таймер в первом методе, если таймер еще не существует в SessionTimer.Timers . Мой измененный ResetTimer после редактирования делает это. Очевидно, вам придется изменить его, чтобы использовать User.Identity.Name .
добавлено автор halter73, источник
Наконец, я должен отметить, что каждый раз, когда вы вызываете $. Connection.hub.start() , пользователь получает новый идентификатор соединения. Таким образом, у пользователя будет новый идентификатор соединения в любое время, когда страница обновится. Если вы создаете одностраничное приложение, это, вероятно, не проблема, но User.Identity.Name , безусловно, более устойчив. Вам, скорее всего, понадобится другой словарь, который отображает имена пользователей в идентификатор соединения, чтобы вы могли фактически отправлять сообщения клиенту, но я оставлю это как упражнение для читателя. :)
добавлено автор halter73, источник

На главной странице:

public partial class MasterPage : System.Web.UI.MasterPage
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Session["LoginID"] != null)
            Label1.Text="Welcome :: " + Session["LoginID"].ToString();
        else
            Response.Redirect("Login.aspx");

    }
    protected void lnkLogout_Click(object sender, EventArgs e)
    {
        Session["LoginID"] = null;
        Response.Redirect("Login.aspx");

    }
}

на странице cs:

protected void Login_Click(object sender, EventArgs e)
    {
        string conString = "Provider=Microsoft.JET.OLEDB.4.0; data source=" + Server.MapPath (string.Empty)  + @"\Database\Northwind.mdb";
        string sqlString = "SELECT * FROM CUSTOMERS where CustomerID='" + TextBox1.Text + "' and City='" + TextBox2.Text + "'";
        OleDbConnection conn = new OleDbConnection(conString);
        DataSet ds = new DataSet();
        OleDbDataAdapter adapter = new OleDbDataAdapter(sqlString, conn);
        adapter.Fill(ds);
        if (ds != null)
        {
            if (ds.Tables[0].Rows.Count > 0)
            {
                Session["LoginID"] = ds.Tables[0].Rows[0]["CustomerID"].ToString();
                Response.Redirect("Welcome.aspx");

            }
            else
                Label1.Text = "Enter correct id/city";
        }


    }
}
4
добавлено
извините, но я думаю, что это не то, что я ищу.
добавлено автор fiberOptics, источник
Исправьте свой код. На данный момент он не читается.
добавлено автор Impulss, источник

Там уже есть таймер мира: часы пользователя. Позвольте собственному времени пользователя отсчитываться, когда он вошел в систему: сохраните свой личный журнал во времени в файле cookie javascript.

Предположим, мы аутентифицируем пользователя с ключом RSA ( openssl genrsa 2048 ).

Теперь идет переписка:

> GET /login

< 200 OK
challenge: mie4NaNgfcqwkhYtNiy4oF

<form action='/login'>
  <input type='text' name='user-public-key' />
  <input type='text' name='rsa-signed-challenge' />
</form>

> POST /login
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA24PnkIZYg/k6lLbNZl5X
IxNl8KEsJTUX7h9B2P+o5LjB6e1OozVwsPFwXjCJP3WN8KwBQhvpxH4ZCK9H0LyT
+NXBPCsydBgY2VfqKK3wQONRKUaEQkSlV/gA0ciWa/jXD9FDbc3onrlqNTmtjGiM
OkXzSJDPz67tEDx+sZQ/+zcEO9tVPSioq6tZwMHx3EfBratA9W148OZRLOS1AFmc
RSSJzsgj/MmOTxrGjfNE2dKMvul4usjWf8Of+GEpEqW6+SrKZ3ivhP/jx+q1v8Bp
pbhDYag6I+YTwEu4feXJEgM4z83e05DJvh3hUDgzv7KDMqCp/h3m8f3TolcgvNox
fQIDAQAB
-----END PUBLIC KEY-----

rsa-signed-challenge:
IyGoX2aqQOH/tBRxxxGPIWGmbo2r6KfEmcDUdDvt7nNaN0GwqXm6MZaJ6eQvjkCR
AVnVRpJNGQDWCaLqzyBLVrysTlnC0dHkJD/vJg3jv74UqZGOqeKSW06HIhzz79UY
RghkdjadDpA8Jzs8NSYBzFopExfzSz+K1sOOwVXWa9nywhGqEj7XXoJO1I0j+o63
Wt94xEa30gmW1oVWIvjWLnBewH4H9ZzXv8PeGTdLdp2v9c9a3nsd7PsYi2yHul+S
CfAlFo/hITfEqucUX5zgyJyU0+SAVRod+vRlSaimMW2CQq8K8kQSbADaQpET4pa1
9eVnG99rz2UHw9b6UG0nBQ==

< 301 Redirect /dashboard
< Set-Cookie: Token=
iPtyfAxAcenFog1kn/h5VrdkBj0wxjeZBnB4JfXLcOWlh+G6/GdndgT7VTg3rSyq
uNFfbgnKNQtiGBjJ45cUHDai44ILK0g6DXPcicusEhs30xJhh8CT4P2FfK/juTtz
d0nj9ypncFOsUPAzJdirgdxO9oMquw0DW2b/iG1O/Dn2fU1/lDrmFpKKIMnPZ10g
cD6gY7Yhqs+2OcyfuiuIBYf2tAq8LYtKSm69j0AlgEiFNNmPl12Wr1R7YhxJZ1hi
smDjNTom5tDfxi3LDfwFtsHKn61OCbfuI3PhlPPqYTUd6omL1Efbr29l4jj+9cgF
0NnaX1L6LUUd6RaVmCw30w==


> GET /dashboard
> Cookie: Token=
iPtyfAxAcenFog1kn/h5VrdkBj0wxjeZBnB4JfXLcOWlh+G6/GdndgT7VTg3rSyq
uNFfbgnKNQtiGBjJ45cUHDai44ILK0g6DXPcicusEhs30xJhh8CT4P2FfK/juTtz
d0nj9ypncFOsUPAzJdirgdxO9oMquw0DW2b/iG1O/Dn2fU1/lDrmFpKKIMnPZ10g
cD6gY7Yhqs+2OcyfuiuIBYf2tAq8LYtKSm69j0AlgEiFNNmPl12Wr1R7YhxJZ1hi
smDjNTom5tDfxi3LDfwFtsHKn61OCbfuI3PhlPPqYTUd6omL1Efbr29l4jj+9cgF
0NnaX1L6LUUd6RaVmCw30w==

< 200 OK
<html>
<head>
  <script>
    jQuery.fn.startCountDown = function(el, opts) {
      opts = $.extend({ minutes : 20 }, opts);
      var start = new Time();
      var updater = function() { el.text("Seconds left: " + (opts.minutes * 60 - ((new Time()) - start))); }
      setTimeout(1000, updater);
    }
    $(function() {
      $("#timer").startCountDown({ minutes: 30 });
    })
  </script>
</head>
<body>

</body> </html>

The crux here is that you can't trust the client to provide a sane value of the count-down, so the above solution encrypts the time of login and uses that as the token.

This is from the client's perspective.

$ openssl genrsa 2048 >client.key
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA24PnkIZYg/k6lLbNZl5XIxNl8KEsJTUX7h9B2P+o5LjB6e1O
ozVwsPFwXjCJP3WN8KwBQhvpxH4ZCK9H0LyT+NXBPCsydBgY2VfqKK3wQONRKUaE
QkSlV/gA0ciWa/jXD9FDbc3onrlqNTmtjGiMOkXzSJDPz67tEDx+sZQ/+zcEO9tV
PSioq6tZwMHx3EfBratA9W148OZRLOS1AFmcRSSJzsgj/MmOTxrGjfNE2dKMvul4
usjWf8Of+GEpEqW6+SrKZ3ivhP/jx+q1v8BppbhDYag6I+YTwEu4feXJEgM4z83e
05DJvh3hUDgzv7KDMqCp/h3m8f3TolcgvNoxfQIDAQABAoIBAQCg8VrsObPYPvjW
ZBjAf1a/3s8U1/Z36S98ZOpwYTHBUDzMeDL5sorHEJ3kUQ2vu06wMExT3gdNC27r
USgEQN70yDP/G2TIfYpqf+ysmqrVyFSPQKZjt9TKZIilRr4St8VmUXVwolF1Xlgi
YgF+OoDlkLfIcnQKvyQMjW4OYLVwRw+bCKAq/T45kki+X41VK4Ubsnjddy+yT++3
GKMPqezVmZHGuhyVtR+dB9vQB3zWZocQRrqDDoQviDB7+scQD2XeWz53SUScBdwm
TzW5YaYclbttVWib0okCSxnhF3yah8cqvQHgqalrACnhaQx9oLcqyrg6KLCI/Lnn
yOZLvKZ5AoGBAP46Y8waGAYgPtgI/7Rni4NslnANGh6psWKaELftPLThxiuCoHad
rMpSojqP8FbKgqILDUOwczPIy/+jWK6J8EJPv4za2dlOcGnnfzD3Ko6LN2Jq3reC
N0Ywi3esTaesHp/SDOB39xW3XAAkdsejffy+LHsnrpVKYKlVm43Jhy6nAoGBAN0L
k+0m9RgwOxWiiAJK3XSBgdHK0e9BnVMKFVPiS2Q1wrnOUDb1CansAAyYSVZYsJZD
eh5VocYPGPTydl/muywlPceGgW2O1CO/LZPj1cKQANHx0+6F9itmk8leaTR4rTuf
2G67sdvlYCyKPoIvVg3WJQnj3dsqo4Ldt/5KkSc7AoGBAMDbIqHOmbLr+0B/cxs0
AY3tbiIKjmn8aOhX357nhUnijCatrXTOICpLjW3Hi5cLgRXUNHfI/1ulU7vV+oxN
b8meHb2IuAI1kumEB+TpW4tO6PDsCZBEZBIG+YYLW816sLCk88fEudfrhQtGniTM
TeLRkYTLkZEHH1TV8G8bFkW5AoGAXf9XZ2jCnwebiIa2KatmYu3To8AI6CJR4YcP
LL21a6bE6LiIOeaXtm+KUdDMlvBeH3gQTSgDBDNVXIxitENs4sfvbpKPJWSwZ4cb
vaEMPJF6F80rX2oOFcSoIeCJAmwy1oERy3z7lFQFQsuC619vy7B9zafdpx6Jq9PX
M0bIVRMCgYEAy9LvPbyXMWYmOy9svYRy4iAL9sCRNmzZgvC5B5b3lN55EWoE8ipw
g4qNBv3NaCJ/lTFezRQZVRfFfQOpNGDvJBLmTEaSR3OuV8hRw7zttC3/MazDqwTy
qZeg526uklXN7IvkFfiHlYZeed1u4wc7SXSi76RIE0w5lDBIb+CPPM4=
-----END RSA PRIVATE KEY-----

$ openssl rsa -in client.key -pubout >client.key.pub
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA24PnkIZYg/k6lLbNZl5X
IxNl8KEsJTUX7h9B2P+o5LjB6e1OozVwsPFwXjCJP3WN8KwBQhvpxH4ZCK9H0LyT
+NXBPCsydBgY2VfqKK3wQONRKUaEQkSlV/gA0ciWa/jXD9FDbc3onrlqNTmtjGiM
OkXzSJDPz67tEDx+sZQ/+zcEO9tVPSioq6tZwMHx3EfBratA9W148OZRLOS1AFmc
RSSJzsgj/MmOTxrGjfNE2dKMvul4usjWf8Of+GEpEqW6+SrKZ3ivhP/jx+q1v8Bp
pbhDYag6I+YTwEu4feXJEgM4z83e05DJvh3hUDgzv7KDMqCp/h3m8f3TolcgvNox
fQIDAQAB
-----END PUBLIC KEY-----

This is the challenge we get from the server (random garbage)

$ echo "mie4NaNgfcqwkhYtNiy4oF" >to-sign
$ openssl dgst -sha1 -sign client.key -out to-sign.sha1 to-sign

(Optional: make sure the client's signature works)

$ openssl dgst -sha1 -verify client.key.pub -signature to-sign.sha1 to-sign

Now you can encode the client signature in a copy-able way:

$ cat to-sign.sha1 | openssl base64 -e
g8nbi3mrcZ8afEpf4iRG6TQFDJtxw48AJ0QTKOdTKh2khxbQdPQIxAFWU++xsJRd
m0wnKRc1SpEYxhLeMKYjyhMTDce/KUBM/5i1Hoh9RPi9+G/cxNoXJiVKaxIF+rDf
NYk7mQD9ofdYgBCAbu2hi6jR5t2BY6emd7z/F53E8edVFtzqlHYjVLHNNYiNlXqD
WHl9OLV2b+yHY/mAkgUBYoVjyBvFnHFcRxTrZMC9A8K3jfU/bEOLgxEmq3UgmvWC
2M7fwpOBAUN7q9yoIR/kOGNPqePghhHTuyeFtbC33PmD8qgbgVUQby8jpVQ7T+5Q
kjEP2sbnETHlkjS6TXSKHg==

Now let's sign an authentication token, so we're moving on to the server...

$ openssl genrsa 2048 >server.key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAoD80ZhPHm1x62wAESbEdHmzCdYCwC3VwkOwDMr4fD9Qqmw6D
Zu2oMSAJ8Kw1Q4oRJd3sZkHu7ZnCwHTe3P83uBKrCfDDFiu/aB1mJQJDAZzPwh5r
W8/2FAHmq/iw/f3U3IOaPuE1w7eoEaqFuNnmfb0EEBhsi7zhbjKpKowUXObPz1W5
mIJvpWqlyygHG6+JJVMs1jTdwzzQzcyHZDgEu/fODHgW5ErBQS7fKOOvZZIxEuLg
i7x5xB/mMYpWdIO0BI/xcu3SdmmNE/Ix8MoAZXXTK9u8Uu+2jnO69pFWtBGPIa2l
AGKNQ1jlUgvECglo356OOpKx9rb9Fd9WY4SIxwIDAQABAoIBABIiFNPYOSYjeON/
RPzxxdHDjN2vCjzBtVMw4cvEJ8+quoeBRO1Ix1eHwJgzZHOYFAis7CtGGrtYQul0
UCPB3ZQ+yIv/apP/r1EgwoY9k0eDbx8QQiXJipcJAAlFwwF6z7OEUNf8tBDJn4Mg
QLGCNsrTsLoBiYbmgLvvj6T45PT+G3ztaETv7iiiUIMX5R/7thK/+odiUeqyIbHH
m0673m0nYdZvGe7ujZarbh1h2x4Srs+OiMaPXH1ehw/nTvYAoEgGvz8eGVFCUrfa
87rizYMLYk8zs/TAz4CDtAk9PtLVMS6NZdKCYe4zvLBdjGjwx7gQJFpjXJCRQwA2
JGE8U8ECgYEAzn4I5yHc+ApzJDJyRmeR70MMWk0KpzIi+8PhwKUNpdP3DyKGOTAs
Z3g3su1/C5tcq2GIK/hprvDxdtg7wu7RSZdhz1W3Ee584JTqzqAG464bmCqd1aPi
sp7DVOeSrb5EZDtrZ9UpW79R2skRhU3qLAay0bl/cvKauBukCr0gUUsCgYEAxqq+
slkXI1tvCzaoxAWNyf0hooGKjEeAsCyrFYJwVk9cveJ0wMZUC7pLPH9UVEIjZzzo
Fm+xr5kVbXhLBn9cCXjP/OgBtuM1KqA/mcsmysVn2okuU8BIy43RYXgq1+eUI92/
MDFKtuFzMikSqI5gY3sIB0GRfL19FAvdy4iKtPUCgYEApUiJA8k9QGXM6Epg4i4A
yA1ZE+bbAh3FltSiHTuAgx35genWmmwO/vthSh2ENdwz/xJglyGOJnPCM6i9nTjf
2RINPpKTqQzGdFV+5cl9+jzg5ZonIFzAFs2x+IIsDFpiEADn5gLfygqIEKIlHhjR
uk/aTrk2ZOIAKiIl2lqsRaUCgYA3K8O5k7QxRXsZChzkEwbFSV7F2mO3gUPjqQP5
/TdlQLToprL1th4xA5NRQasRmyxpxyhM0sftk/23YOi07TmKB9r6yRNwzrg9FjOT
ai9jsF6e+em7qHKO1NuIze5X9x/UtggaQhYVo5ZyH6Xm2WM7PTeFjFfy5EyP/Juj
ok+i4QKBgQCf6mndYOkBCmzyMv2gASr8nj7Fh0oTxfaFRrs9k/DnUTOSUgrHrmO4
eCAk/D5FdPjcmF1np8wasVt38sw5nxUmbYonoV/2H+xmvKrRqtuflGRQx98P/+Qd
6vIF/n3NM66oZG9zgeYdEzxAbLXptCO60arJ4Ekrod/J+EpGSQb+bg==

Here's what the server has run when set up:

$ openssl rsa -in server.key -pubout >server.key.pub
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoD80ZhPHm1x62wAESbEd
HmzCdYCwC3VwkOwDMr4fD9Qqmw6DZu2oMSAJ8Kw1Q4oRJd3sZkHu7ZnCwHTe3P83
uBKrCfDDFiu/aB1mJQJDAZzPwh5rW8/2FAHmq/iw/f3U3IOaPuE1w7eoEaqFuNnm
fb0EEBhsi7zhbjKpKowUXObPz1W5mIJvpWqlyygHG6+JJVMs1jTdwzzQzcyHZDgE
u/fODHgW5ErBQS7fKOOvZZIxEuLgi7x5xB/mMYpWdIO0BI/xcu3SdmmNE/Ix8MoA
ZXXTK9u8Uu+2jnO69pFWtBGPIa2lAGKNQ1jlUgvECglo356OOpKx9rb9Fd9WY4SI
xwIDAQAB
-----END PUBLIC KEY-----

Now that the server has a private and public key, let's create the token to be signed and used for authentication.

$ echo "2013-02-09T16:33:44+0000" >auth.token.plain

Because the text to encrypt is so tiny, we don't experience problems with key length on the server, and so we can use the key as-is rather than doing symmetric encryption.

$ openssl rsautl -encrypt -inkey server.key.pub -pubin -in auth.token.plain -out auth.token.cipher

This we give to the client. Every time we receive this token, we can validate that we signed it. If you have a server farm, make them sign with the same keys.

$ cat auth.token.cipher | openssl base64 -e
iPtyfAxAcenFog1kn/h5VrdkBj0wxjeZBnB4JfXLcOWlh+G6/GdndgT7VTg3rSyq
uNFfbgnKNQtiGBjJ45cUHDai44ILK0g6DXPcicusEhs30xJhh8CT4P2FfK/juTtz
d0nj9ypncFOsUPAzJdirgdxO9oMquw0DW2b/iG1O/Dn2fU1/lDrmFpKKIMnPZ10g
cD6gY7Yhqs+2OcyfuiuIBYf2tAq8LYtKSm69j0AlgEiFNNmPl12Wr1R7YhxJZ1hi
smDjNTom5tDfxi3LDfwFtsHKn61OCbfuI3PhlPPqYTUd6omL1Efbr29l4jj+9cgF
0NnaX1L6LUUd6RaVmCw30w==

The server can validate the token again. Failure to decrypt the cipher means it has been modified and we fail the token. A production system should also verify a signature (in same envelope) to avoid corrupted data decrypting successfully.

$ openssl rsautl -decrypt -inkey server.key -in auth.token.cipher
2013-02-09T16:33:44+0000
0
добавлено
DotNetRuChat
DotNetRuChat
2 992 участник(ов)

Чат русскоязычного .NET сообщества http://dotnet.ru/ Вам могут быть интересны: @dotnetchat, @cilchat, @fsharp_chat, @pro_net, @xamarin_russia, @microsoftstackjobs, @uwp_ru Флуд в @dotnettalks

Microsoft Stack Jobs
Microsoft Stack Jobs
1 788 участник(ов)

Work & freelance only Microsoft Stack. Feed https://t.me/Microsoftstackjobsfeed Чат про F#: @Fsharp_chat Чат про C#: @CSharpChat Чат про Xamarin: @xamarin_russia Чат общения:@dotnettalks

pro.net
pro.net
710 участник(ов)

Обсуждение .NET Framework и всего, что с ним связано. Правила: не флудить не по теме, уважать ваших коллег и никакой рекламы (объявления о вакансиях можно согласовать с @AlexFails). Флудилка: @dotnettalks Участник @proDOT

Microsoft Developer Community Chat
Microsoft Developer Community Chat
584 участник(ов)

Чат для разработчиков и системных администраторов Microsoft Developer Community. __________ Новостной канал: @msdevru __________ Баним за: оскорбления, мат, рекламу, флуд, флейм, спам, NSFW контент, а также большое количество оффтоп тем. @banofbot

.NET Talks: Force Push Masters
.NET Talks: Force Push Masters
490 участник(ов)

Свободный чат .NET разработчиков. Правила: t.me/dotnettalks/56823 Вам могут быть интересны: @dotnetruchat, @dotnetchat, @cilchat, @fsharp_chat, @pro_net, @dotnetgroup, @xamarin_russia, @microsoftstackjobs, @uwp_ru http://combot.org/chat/-1001128250813

.NET Chat Убежище
.NET Chat Убежище
246 участник(ов)

Чат .NET разработчиков под эгидой MSK/SPB .NET Community Group Вам могут быть интересны: @fsharp_chat, @dotnetruchat, @cilchat, @xamarin_russia, @microsoftstackjobs, @dotnetgroup Флуд в @dotnettalks

.NET CIL Chat
.NET CIL Chat
54 участник(ов)

.NET CIL (aka IL aka MSIL)