精华内容
下载资源
问答
  • 2021-03-25 20:21:23

    最近在折腾一些控制相关的软件设计,想起来状态机这个东西,对解决一些控制系统状态切换还是挺有用的。

    状态机(有限状态自动机)网上有很多介绍。简单理解就是定义一系列状态,通过一系列的事件,可以使得状态可以相互之间切换。

    如果不使用状态机的思想来编程,那么针对过程的编程方法会使得程序拓展性变差,并且不容易调试。而状态机只需要定义好了各种状态和状态切换之间的事件,你只管触发事件,剩下的事情它自己就自动完成了(毕竟名称叫做有限状态自动机),这对于很多需要定义各种控制阶段的系统简直是完美适配。了解到.NET也有很多库可以实现这些功能,本文主要介绍一下Stateless的应用。

    Stateless介绍

    Stateless可以创建极简的状态机与对应的工作流。很多项目(包括VisualStudio Extension、AIlab)都有使用到它。

    它支持以下特性:

    • 支持各种类型作为状态和触发事件
    • 支持状态继承
    • 支持状态进入/离开事件
    • 支持条件状态转移
    • 支持状态/转移查询

    也有几点需要注意的:

    • 它支持异步语法,但是它是单线程的,不是线程安全的。
    • 可以导出DOT graph

    安装起来很简单,直接在nuget中安装即可:

    Install-Package Stateless
    

    Stateless使用

    用起来也挺简单的,以打电话这个事情为例,针对打电话的种种动作和状态做成一个状态机。
    需要先定义一些状态和事件/触发器,电话有拨号、接通、留言等事件,有响铃、挂起、挂断等事件:

    //代码来自官方示例,可以在官方github库上找到,略有修改以完整展示功能。
    enum Trigger
    {
        CallDialed,
        CallConnected,
        LeftMessage,
        PlacedOnHold,
        TakenOffHold,
        PhoneHurledAgainstWall,
        MuteMicrophone,
        UnmuteMicrophone,
        SetVolume
    }
    
    enum State
    {
        OffHook,
        Ringing,
        Connected,
        OnHold,
        PhoneDestroyed
    }
    

    然后就是创建一个状态机了:

    _machine = new StateMachine<State, Trigger>(() => _state, s => _state = s);
    

    最后也是最需要详细解释的,就是配置状态机的行为了:

    /*
    为了解释尽可能多的功能,以下程序修改了官方的代码,可以在官方找可以直接执行的代码。
    */
    
    //使用Permit指示发生某个事件后,从一个状态变换到另外一个状态。
    _machine.Configure(State.OffHook)
        .Permit(Trigger.CallDialed, State.Ringing);
    
    //设置一个带参数的事件,这个事件是CallDialed的类型
    var _setCalleeTrigger = _machine.SetTriggerParameters<string>(Trigger.CallDialed);
    _machine.Configure(State.Ringing)
        //允许重新进入当前的状态,这个过程会触发进入和退出动作
        .PermitReentry(Trigger.Ringing)
        //使用OnEntryFrom指示在触发这个状态的时候,运行某个动作,这里指定的是一个带参数的事件
        .OnEntryFrom(_setCalleeTrigger, callee => OnDialed(callee), "Caller number to call")
        .Permit(Trigger.CallConnected, State.Connected);
    
    _machine.Configure(State.OnHold)
        //定义子状态
        .SubstateOf(State.Connected)
        .Permit(Trigger.TakenOffHold, State.Connected)
        .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
    
    _machine.Configure(State.Connected)
        //进入状态的时候执行动作
        .OnEntry(t => StartCallTimer())
        //离开状态执行动作
        .OnExit(t => StopCallTimer())
        //状态不变化,但是响应某种事件,和PermitReentry不同,它不会触发进入和退出的动作
        .InternalTransition(Trigger.MuteMicrophone, t => OnMute())
        .InternalTransition(Trigger.UnmuteMicrophone, t => OnUnmute())
        .InternalTransition<int>(_setVolumeTrigger, (volume, t) => OnSetVolume(volume))
        .Permit(Trigger.LeftMessage, State.OffHook)
        .Permit(Trigger.PlacedOnHold, State.OnHold)
        //指定在发生同一种事件的时候,根据事件的参数不同而决定进入不同的状态。
        .PermitIf(_setCalleeTrigger, State.Connected, callee => string.IsNullOrWhiteSpace(callee))
        .PermitIf(_setCalleeTrigger, State.Connected, callee => !string.IsNullOrWhiteSpace(callee))
        //如果没有定义这个事件而发生了这个事件,会弹出异常。通过指定忽略某一类事件,可以避免这个情况。
        .Ignore(Trigger.CallDialled);
    
    //当然也可以使用这个来避免弹出上面说的异常
    _machine.OnUnhandledTrigger((state, trigger) => { });
    
    //可以使用异步调用,但是必须要在触发事件的时候,使用FireAsync
    _machine.Configure(State.PhoneDestroyed)
        .OnEntryAsync(async () => await SendEmailToAssignee());
    
    

    配置好了各状态之间的转换,下面就是触发事件了。

    public void Dialed(string callee)
    {
        //有参数的触发
        _machine.Fire(_setCalleeTrigger, callee);
    }
    
    public void Connected()
    {
        //无参数的触发
        _machine.Fire(Trigger.CallConnected);
    }
    
    public async Task PhoneDestroy()
    {
        //异步触发
        await _machine.FireAsync(Trigger.PhoneDestroyed);
    }
    
    public string ToDotGraph()
    {
        //导出DOT GRAPH
        return UmlDotGraph.Format(_machine.GetInfo());
    }
    

    外部调用很简洁:

    phoneCall.Dialed("Prameela");
    phoneCall.Connected();
    phoneCall.SetVolume(2);
    phoneCall.Hold();
    

    只需要调用事件即可,别的都会按照我们设置好的动作来进行了,非常自动化。

    总结

    Stateless可以很好地实现状态机,有点事件驱动的编程的感觉,但本质上不同,Stateless核心是各个状态的迁移。

    虽然Stateless很小巧方便,但是还有有很多地方不尽如人意(官方说这就是他们自己的设计目标,维持极简):

    • 没有启动和停止的说法,一般在构造函数里面创建就一直有效。
    • 不是线程安全的
    • 拓展性有限

    还有一个Appccelerate.StateMachine地址),这个支持四种不同的状态机实现:

    • Passive State Machine: 同步单线程处理状态转换
    • Active State Machine: 同步多线程处理状态转换
    • Async Passive State Machine: 异步单线程处理状态转换
    • Async Active State Machine: 异步多线程处理状态转换

    其中active的是线程安安全的。另外,它还支持状态、事件的持久化,拓展性强。用法差不多,只是配置的关键字上面有一点区别,大家可以自行翻文档。

    更多相关内容
  • 浅析C# 状态机Stateless

    2020-12-17 05:40:15
    最近在折腾一些控制相关的软件设计,想起来状态机这个东西,对解决一些控制系统状态切换还是挺有用的。...了解到.NET也有很多库可以实现这些功能,本文主要介绍一下Stateless的应用。 Stateless介绍 St
  • TRex无状态GUI TREX无国籍GUI应用程序提供了一种图形用户界面 。 描述和主要特点: TRex无状态GUI应用程序是基于JavaFX的应用程序。 该应用程序的主要功能可以分为树状部分(TRex管理,流量配置文件管理和数据包...
  • 主要介绍了React 无状态组件(Stateless Component) 与高阶组件,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 无状态的 一个用DELPHI代码创建状态机的简单库 这是的delphi克隆
  • phoneCall := stateless . NewStateMachine ( stateOffHook ) phoneCall . Configure ( stateOffHook ). Permit ( triggerCallDialed , stateRinging ) phoneCall . Configure ( stateRinging ). OnEntryFrom ( ...
  • ninja_id_project 一个新的 Flutter 项目。 入门 该项目是 Flutter 应用程序的起点。 如果这是您的第一个 Flutter 项目,请提供一些帮助您入门的资源: 如需有关 Flutter 入门的帮助,请查看我们的,其中提供了...
  • stateless-future, 在完全特色的Scala 语法中,异步编程 无状态未来 无状态未来是一组特定于异步编程的领域特定语言,在纯功能。无状态期货为 scala.concurrent.Future 和 scala.async 提供类似的API,除了无状态...
  • 导出DOT图 Stateless还提供了一个在运行时生成DOT图代码的功能,使用生成的DOT图代码,我们可以生成可视化的状态机图。 这里我们可以使用UmlDotGraph.Format()方法来生成DOT图代码。 string graph = UmlDotGraph....

    一、什么是状态机和状态模式

    状态机是一种用来进行对象建模的工具,它是一个有向图像,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每一个事件都在属于“当前”节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少由一个必须的终态。当到达终态,状态机停止。

    状态模式主要用来解决对象状态转换比较复杂的情况。它把状态的逻辑判断转移到不同的类中,可以把复杂的逻辑简单化。

    二、状态机的要素

    状态机有四个要素,即现态、条件、动作、次态。其中,现态和条件是“因”,动作和次态是“果”。

    • 现态-是值当前对象的状态
    • 条件-当一个条件满足时,当前对象会触发一个动作
    • 动作-条件满足之后,执行的动作
    • 次态-条件满足后,当前对象的新状态。次态是相对现态而言的,次态一旦触发,就变成了现态

    三、Stateless

    dotnet-state-machine/stateless at master (github.com)

    现在我们来写一个简单的例子:

    首先我们定义两个枚举类型用来描述状态和触发

        public enum PhoneState
        {
            OffHook,
            Ringing,
            Connected,
            OnHold,
        }
    
        public enum PhoneTrigger
        {
            CallDialled,
            CallConnected,
            LeftMessage,
            PlacedOnHold,
            TakenOffHold,
        } 
    

    然后再定义一个类:

        public class IPhone
        {
            //初始化了一个状态机来描述点电话的状态,这里电话的初始状态为挂机状态(OffHook)
            StateMachine<PhoneState, PhoneTrigger> state = new StateMachine<PhoneState, PhoneTrigger>(PhoneState.OffHook);
    
            public StateMachine<PhoneState, PhoneTrigger> State
            {
                get { return state; }
            }
            public IPhone()
            {
                //当电话处于挂机状态时,如果触发被呼叫事件,电话的状态会变为响铃状态(Ringing)
                state.Configure(PhoneState.OffHook)
                    .Permit(PhoneTrigger.CallDialled, PhoneState.Ringing);
    
                //当电话处于响铃状态时,如果触发通过连接事件,电话的状态会变为已连接状态(Connected)
                state.Configure(PhoneState.Ringing)
                    .Permit(PhoneTrigger.CallConnected, PhoneState.Connected);
    
                state.Configure(PhoneState.Connected)
                    .OnEntry(() => StartCallTimer())//当电话处于已连接状态时,系统会开始计时,
                    .OnExit(() => StopCallTimer()) //已连接状态变为其他状态时,系统会结束计时
                    .Permit(PhoneTrigger.LeftMessage, PhoneState.OffHook)//当电话处于已连接状态时,如果触发留言事件,电话的状态会变为挂机状态(OffHook)
                    .Permit(PhoneTrigger.PlacedOnHold, PhoneState.OnHold);//当电话处于已连接状态时,如果触发挂起事件,电话的状态会变为挂起状态(OnHold)
            }
    
            private void StopCallTimer()
            {
                Console.WriteLine("结束计时");
            }
    
            private void StartCallTimer()
            {
                Console.WriteLine("开始计时");
            }
        }
    

    再Main函数中使用这个类:

    IPhone phone = new IPhone();
                Console.WriteLine($"CurrentState:{phone.State.State}");
    
                phone.State.Fire(PhoneTrigger.CallDialled);
                Console.WriteLine($"CurrentState:{phone.State.State}");
    
                phone.State.Fire(PhoneTrigger.CallConnected);
                Console.WriteLine($"CurrentState:{phone.State.State}");
    
                phone.State.Fire(PhoneTrigger.LeftMessage);
                Console.WriteLine($"CurrentState:{phone.State.State}");
    
    output:
    
    CurrentState:OffHook
    CurrentState:Ringing
    开始计时
    CurrentState:Connected
    结束计时
    CurrentState:OffHook
    

    分层状态

    给状态机对象添加了一组配置。

                //OnHold状态是Connected状态的子状态。这意味着电话挂起的时候,还是连接状态的。
                state.Configure(PhoneState.OnHold)
                    .SubstateOf(PhoneState.Connected)
                    .Permit(PhoneTrigger.TakenOffHold, PhoneState.Connected);
    

    当电话的状态从已连接(Connected)变为挂起(OnHold)时, 不会触发StartCallTimer()方法和StopCallTimer()方法, 这是因为OnHoldConnected的子状态。

                IPhone phone = new IPhone();
                Console.WriteLine($"CurrentState:{phone.State.State}");
    
                phone.State.Fire(PhoneTrigger.CallDialled);
                Console.WriteLine($"CurrentState:{phone.State.State}");
    
                phone.State.Fire(PhoneTrigger.CallConnected);
                Console.WriteLine($"CurrentState:{phone.State.State}");
    
                phone.State.Fire(PhoneTrigger.PlacedOnHold);
                Console.WriteLine($"CurrentState:{phone.State.State}");
    
                phone.State.Fire(PhoneTrigger.TakenOffHold);
                Console.WriteLine($"CurrentState:{phone.State.State}");
    

    状态的进入和退出事件

    state.Configure(PhoneState.Connected)
                    .OnEntry(() => StartCallTimer())//当电话处于已连接状态时,系统会开始计时,
                    .OnExit(() => StopCallTimer()) //已连接状态变为其他状态时,系统会结束计时
                    .Permit(PhoneTrigger.LeftMessage, PhoneState.OffHook)//当电话处于已连接状态时,如果触发留言事件,电话的状态会变为挂机状态(OffHook)
                    .Permit(PhoneTrigger.PlacedOnHold, PhoneState.OnHold);//当电话处于已连接状态时,如果触发挂起事件,电话的状态会变为挂起状态(OnHold)
    

    也可以改为异步方法:

    state.Configure(PhoneState.Connected)
                    .OnEntryAsync(StartCallTimer)//当电话处于已连接状态时,系统会开始计时,
    
            private async Task StartCallTimer()
            {
                await Task.Run(() => Console.WriteLine("开始计时"));
            }
    

    但是改为异步配置后,Fire方法也需要使用异步方法。

                phone.State.FireAsync(PhoneTrigger.CallConnected);
                Console.WriteLine($"CurrentState:{phone.State.State}");
    

    还可以指定Trigger:

                state.Configure(PhoneState.Connected)
                    .OnEntryFrom(PhoneTrigger.CallConnected, StartCallTimer)//当电话处于已连接状态时,系统会开始计时,
    

    外部状态存储

    有时候,当前对象的状态需要来自于一个ORM对象,或者需要将当前对象的状态保存到一个ORM对象中。为了支持这种外部状态存储,StateMachine类的构造函数支持了读写状态值。

    var stateMachine = new StateMachine<State, Trigger>(
        () => myState.Value,
        s => myState.Value = s);
    

    内省

    状态机可以通过StateMachine.PermittedTriggers属性,提供一个当前对象状态下,可以触发的触发器列表。并提供了一个方法StateMachine.GetInfo()来获取有关状态的配置信息。

    保护子句

    状态机将根据保护子句在多个转换之间进行选择,配置中的保护子句必须是互斥的,子状态可以通过重新指定来覆盖状态转换,但是子状态不能覆盖父状态允许的状态转换。

    PermitIf
    

    参数化触发器

    var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign);
    
    stateMachine.Configure(State.Assigned)
        .OnEntryFrom(assignTrigger, email => OnAssigned(email));
    
    stateMachine.Fire(assignTrigger, "joe@example.com");
    

    状态改变通知

    state.OnTransitioned(trans => Console.WriteLine($"{trans.Trigger}:{trans.Source}=>{trans.Destination}"));
    

    导出DOT图

    Stateless还提供了一个在运行时生成DOT图代码的功能,使用生成的DOT图代码,我们可以生成可视化的状态机图。

    这里我们可以使用UmlDotGraph.Format()方法来生成DOT图代码。

    string graph = UmlDotGraph.Format(state.GetInfo());
    

    然后将字符串在Webgraphviz在线转换成图表。

    展开全文
  • 状态机 stateless State Machines and business processes that describe a series of states seem like they'll be easy to code but you'll eventually regret trying to do it yourself. Sure, you'll start with...
    状态机 stateless

    状态机 stateless

    State Machines and business processes that describe a series of states seem like they'll be easy to code but you'll eventually regret trying to do it yourself. Sure, you'll start with a boolean, then two, then you'll need to manage three states and there will be an invalid state to avoid then you'll just consider quitting all together. ;)

    描述一系列状态的状态机和业务流程似乎很容易编写代码,但是您最终会后悔自己尝试这样做。 当然,您将以一个布尔值开始,然后是两个,然后您将需要管理三个状态,并且将存在一个无效状态,以避免出现这种情况,然后您将考虑同时退出所有状态。 ;)

    "Stateless" is a simple library for creating state machines in C# code. It's recently been updated to support .NET Core 1.0. They achieved this not by targeting .NET Core but by writing to the .NET Standard. Just like API levels in Android abstract away the many underlying versions of Android, .NET Standard is a set of APIs that all .NET platforms have to implement. Even better, the folks who wrote Stateless 3.0 targeted .NET Standard 1.0, which is the broadest and most compatible standard - it basically works everywhere and is portable across the .NET Framework on Windows, .NET Core on Windows, Mac, and LInux, as well as Windows Store apps and all phones.

    Stateless ”是一个简单的库,用于使用C#代码创建状态机。 它最近已更新为支持.NET Core 1.0。 他们不是通过针对.NET Core而是通过编写.NET Standard来实现这一目标的。 就像Android中的API级别抽象了许多底层的Android版本一样,.NET Standard是所有.NET平台都必须实现的一组API。 更好的是,编写Stateless 3.0的人们针对的是.NET Standard 1.0,这是最广泛和最兼容的标准-它基本上可以在任何地方使用,并且可以跨Windows的.NET Framework,Windows,Mac的LME.NET和LInux进行移植,以及Windows Store应用和所有手机。

    Sure, there's Windows Workflow, but it may be overkill for some projects. In Nicholas Blumhardt's words:

    当然,这里有Windows Workflow,但对于某些项目来说可能有些过头了。 用尼古拉斯·布鲁姆哈特(Nicholas Blumhardt)的话说:

    ...over time, the logic that decided which actions were allowed in each state, and what the state resulting from an action should be, grew into a tangle of if and switch. Inspired by Simple State Machine, I eventually refactored this out into a little state machine class that was configured declaratively: in this state, allow this trigger, transition to this other state, and so-on.

    ……随着时间的流逝,决定每个状态中允许哪些动作以及该动作应导致的状态是什么的逻辑,变成了ifswitch的纠结。 受简单状态机的启发,我最终将其重构为声明式配置的一个小的状态机类:在此状态下,允许此触发器过渡到该另一状态,如此等等。

    You can use state machines for anything. You can certainly describe high-level business state machines, but you can also easily model IoT device state, user interfaces, and more.

    您可以将状态机用于任何事物。 您当然可以描述高级业务状态机,但是也可以轻松地为IoT设备状态,用户界面等建模。

    Even better, Stateless also serialize your state machine to a standard text-based "DOT Graph" format that can then be generated into an SVG or PNG like this with http://www.webgraphviz.com. It's super nice to be able to visualize state machines at runtime.

    更好的是,Stateless还可以将状态机序列化为基于文本的标准“ DOT Graph”格式,然后可以使用http://www.webgraphviz.com将其生成为SVG或PNG。 能够在运行时可视化状态机非常好。

    使用无状态建模简单状态机 (Modeling a Simple State Machine with Stateless)

    Let's look at a few code examples. You start by describing some finite states as an enum, and some finite "triggers" that cause a state to change. Like a switch could have On and Off as states and Toggle as a trigger.

    让我们看一些代码示例。 您首先将一些有限状态描述为一个枚举,然后将某些引起状态改变的有限“触发器”描述为枚举。 就像开关可以将“打开”和“关闭”作为状态,而将“ Toggle”作为触发器。

    A more useful example is the Bug Tracker included in the Stateless source on GitHub. To start with here are the states of a Bug and the Triggers that cause state to change:

    一个更有用的示例是GitHub上Stateless源中包含的Bug Tracker。 从这里开始是Bug的状态和导致状态更改的触发器:

    enum State { Open, Assigned, Deferred, Resolved, Closed }
    enum Trigger { Assign, Defer, Resolve, Close }

    You then have your initial state, define your StateMachine, and if you like, you can pass Parameters when a state is trigger. For example, if a Bug is triggered with Assign you can pass in "Scott" so the bug goes into the Assigned state - assigned to Scott.

    然后,您将拥有初始状态,定义StateMachine,并且如果愿意,可以在触发状态时传递参数。 例如,如果通过分配触发了错误,则可以传递“ Scott”,以便使错误进入“已分配”状态-分配给Scott。

    State _state = State.Open;
    StateMachine<State, Trigger> _machine;
    StateMachine<State, Trigger>.TriggerWithParameters<string> _assignTrigger;

    string _title;
    string _assignee;

    Then, in this example, the Bug constructor describes the state machine using a fluent interface that reads rather nicely.

    然后,在此示例中,Bug构造函数使用流畅的接口描述了状态机,该接口读起来非常好。

    public Bug(string title)
    {
    _title = title;

    _machine = new StateMachine<State, Trigger>(() => _state, s => _state = s);

    _assignTrigger = _machine.SetTriggerParameters<string>(Trigger.Assign);

    _machine.Configure(State.Open)
    .Permit(Trigger.Assign, State.Assigned);

    _machine.Configure(State.Assigned)
    .SubstateOf(State.Open)
    .OnEntryFrom(_assignTrigger, assignee => OnAssigned(assignee))
    .PermitReentry(Trigger.Assign)
    .Permit(Trigger.Close, State.Closed)
    .Permit(Trigger.Defer, State.Deferred)
    .OnExit(() => OnDeassigned());

    _machine.Configure(State.Deferred)
    .OnEntry(() => _assignee = null)
    .Permit(Trigger.Assign, State.Assigned);
    }

    For example, when the State is Open, it can be Assigned. But as this is written (you can change it) you can't close a Bug that is Open but not Assigned. Make sense?

    例如,当状态为“打开”时,可以对其进行分配。 但是随着本文的撰写(您可以更改),您无法关闭已打开但未分配的错误。 有道理?

    When the Bug is Assigned, you can Close it, Defer it, or Assign it again. That's PermitReentry(). Also, notice that Assigned is a Substate of Open.

    分配错误后,您可以关闭,推迟或再次分配它。 那是PermitReentry()。 另外,请注意,Assigned是Open的子状态。

    You can have events that are fired as states change. Those events can take actions as you like.

    您可以拥有随着状态变化而触发的事件。 这些事件可以采取您喜欢的动作。

    void OnAssigned(string assignee)
    {
    if (_assignee != null && assignee != _assignee)
    SendEmailToAssignee("Don't forget to help the new employee.");

    _assignee = assignee;
    SendEmailToAssignee("You own it.");
    }

    void OnDeassigned()
    {
    SendEmailToAssignee("You're off the hook.");
    }

    void SendEmailToAssignee(string message)
    {
    Console.WriteLine("{0}, RE {1}: {2}", _assignee, _title, message);
    }

    With a nice State Machine library like Stateless you can quickly model states that you'd ordinarily do with a "big ol' switch statement."

    使用像Stateless这样的不错的State Machine库,您可以快速建模通常用“ big ol'switch语句”执行的状态。

    What have you used for state machines like this in your projects?

    您在项目中将什么用于这样的状态机?

    翻译自: https://www.hanselman.com/blog/stateless-30-a-state-machine-library-for-net-core

    状态机 stateless

    展开全文
  • 它在/target目录中生成stateless-rest-1.0.0-SNAPSHOT-runner.jar文件。 请注意,它不是über-jar,因为依赖项已复制到target/lib目录中。 现在可以使用java -jar target/stateless-rest-1.0.0-SNAPSHOT-runner....
  • npm install stateless-csrf 这个怎么运作 此CSRF保护可将用户的唯一cookie散列到服务器端机密中。 当请求进入时,服务器使用服务器端秘密对cookie进行哈希处理,然后将其与CSRF令牌进行比较。 如果匹配,则验证...
  • 为了解决这个问题, stateless-dtm将这种全局突变隐藏在纯功能接口的后面。 跟踪事件时,必须完整提供“数据层”对象,以确保所有事件都是完全原子的,并且不依赖于先前的突变。 用法 import track from 'stateless...
  • 我的Stateless Spring Security系列文章的第三部分也是最后一部分是关于将基于JWT令牌的身份验证与spring-social-security混合在一起的。 这篇文章直接建立在此基础上,并且主要集中在已更改的部分上。 想法是使用...

    我的Stateless Spring Security系列文章的第三部分也是最后一部分是关于将基于JWT令牌的身份验证与spring-social-security混合在一起的。 这篇文章直接建立在此基础上,并且主要集中在已更改的部分上。 想法是使用基于OAuth 2的“使用Facebook登录”功能来替换基于用户名/密码的登录,但是此后仍使用相同的基于令牌的身份验证。

    登录流程

    客户端

    用户单击“使用Facebook登录”按钮,该按钮是指向“ / auth / facebook”的简单链接,SocialAuthenticationFilter注意到缺少其他查询参数,并触发了将您的网站用户重定向到Facebook的重定向。 他们使用用户名/密码登录,然后重定向回“ / auth / facebook”,但这一次指定了“?code =…&state =…”参数。 (如果用户以前登录过facebook并设置了cookie,那么facebook甚至会立即重定向回该用户,并且根本不会向用户显示任何facebook屏幕。)有趣的是,您可以按照浏览器网络日志中的说明进行操作。所有操作均使用纯HTTP 302重定向完成。 (HTTP响应中的“ Location”标头用于告诉浏览器下一步要去哪里)

    服务器端

    从facebook重定向到“ / auth / facebook?code =…&state =…”之后,SocialAuthenticationFilter现在可以看到适当的参数,并将触发两个服务器调用Facebook。 第一个是获取已登录用户的访问令牌,第二个是通过使用访问令牌获取用户详细信息来测试整个过程是否成功。 完成所有这些操作后,就认为用户已登录,并且可以使用另一个302重定向(到“ /”)将其重定向回到应用程序的根目录。

    关于Spring社交的一些话

    Spring Social是用于处理社交网络的完整框架,其范围远远超出了单纯的登录方案。 除了不同的社交网络适配器之外,还有一个名为Spring Social Security的小型集成库,该库以与Spring Security更好地集成的方式实现了社交身份验证用例。 它带有一个映射到“ / auth”的SocialAuthenticationFilter,这就是我们将要使用的。

    因此,设置社交身份验证需要使用简洁的Spring Social Security库配置Spring Social本身以及Spring Security

    Spring社交

    配置它基本上涉及扩展SocialConfigurerAdapter。 首先,您告诉它要支持哪些社交网络:

    将facebook添加为提供者

    @Override
    public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
    	cfConfig.addConnectionFactory(new FacebookConnectionFactory(
    			env.getProperty("facebook.appKey"),
    			env.getProperty("facebook.appSecret")));
    }

    它还需要知道如何获取当前用户的用户ID:

    检索UserId

    @Override
    public UserIdSource getUserIdSource() {
    	//retrieve the UserId from the UserAuthentication in security context
    	return new UserAuthenticationUserIdSource();
    }

    最后,它需要一个UsersConnectionRepository。 基本上负责用户及其与社交网络的连接之间的关系。 Spring Social带有自己的两个实现(jdbc或内存中)。 我选择自己动手,因为我想重用基于Spring Data JPA的UserDetailsS​​ervice。

    自定义UsersConnectionRepository

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
    	SimpleUsersConnectionRepository usersConnectionRepository =
    			new SimpleUsersConnectionRepository(userService, connectionFactoryLocator);
    	
    	// if no local user record exists yet for a facebook's user id
    	// automatically create a User and add it to the database
    	usersConnectionRepository.setConnectionSignUp(autoSignUpHandler);
    	
    	return usersConnectionRepository;
    }

    Spring安全

    如上一篇博客文章所述,配置它基本上涉及扩展WebSecurityConfigurerAdapter。 除了配置和公开AuthenticationManager和UserDetailsS​​ervice之类的常规内容外,它现在还需要配置和插入SocialAuthenticationFilter。 由于SpringSocialConfigurer完成了大部分工作,因此这基本上只涉及很少的代码。 它可能很简单:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	// apply the configuration from the socialConfigurer 
    	// (adds the SocialAuthenticationFilter)
    	http.apply(new SpringSocialConfigurer());
    }

    考虑到我想插入基于令牌的身份验证,我自己的succesHandler和userIdSource; 我必须进行一些配置更改:

    @Autowired private SocialAuthenticationSuccessHandler successHandler;
    @Autowired private StatelessAuthenticationFilter jwtFilter;
    @Autowired private UserIdSource userIdSource;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    // Set a custom successHandler on the SocialAuthenticationFilter (saf)
    final SpringSocialConfigurer sc = new SpringSocialConfigurer();
    sc.addObjectPostProcessor(new ObjectPostProcessor<...>() {
    	@Override
    	public <...> O postProcess(O saf) {
    		saf.setAuthenticationSuccessHandler(successHandler);
    		return saf;
    	}
    });
    
    http.
    
    ...
    
    // add custom authentication filter for stateless JWT based authentication
    .addFilterBefore(jwtFilter, AbstractPreAuthenticatedProcessingFilter.class)
    
    // apply the configuration from the SocialConfigurer
    .apply(sc.userIdSource(userIdSource));
    }

    如果您愿意,还可以继承SpringSocialConfigurer的子类,并为自定义的successHandler提供更优雅的设置器…

    过去的样板(在这里赞誉您)

    现在是时候关注一些更有趣的地方了。

    在建立与Facebook的初始成功连接后,立即触发自定义ConnectionSignUp:

    @Override
    @Transactional
    public String execute(final Connection<?> connection) {
        //add new users to the db with its default roles
        final User user = new User();
        final String firstName = connection.fetchUserProfile().getFirstName();
        user.setUsername(generateUniqueUserName(firstName));
        user.setProviderId(connection.getKey().getProviderId());
        user.setProviderUserId(connection.getKey().getProviderUserId());
        user.setAccessToken(connection.createData().getAccessToken());
        grantRoles(user);
        userRepository.save(user);
        return user.getUserId();
    }

    如您所见,我的版本只是将用户的连接数据保留为单个JPA对象。 故意仅支持用户与Facebook上的身份之间的一对一关系。

    请注意,我最终从用户生成的实际令牌中排除了连接属性。 就像我之前排除了密码字段(该字段不再是User对象的一部分)一样:

    @JsonIgnore
    private String accessToken;

    走这条路线确实意味着对facebook API的任何调用都需要数据库查询其他连接字段。 稍后将对此进行更多介绍。

    在用户通过身份验证之后,立即触发自定义AuthenticationSuccessHandler:

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) {
    
    	// Lookup the complete User object from the database
    	final User user = userService.loadUserByUsername(auth.getName());
    
    	// Add UserAuthentication to the response
    	final UserAuthentication ua = new UserAuthentication(user);
    	tokenAuthenticationService.addAuthentication(response, ua);
    	super.onAuthenticationSuccess(request, response, auth);
    }

    这看起来很像以前的博客文章中的代码,但是我必须在TokenAuthenticationService中进行一些更改。 由于客户端是在重定向之后加载的,因此要在此之前在客户端保留令牌,必须将其作为cookie发送给客户端:

    public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) {
      final User user = authentication.getDetails();
      user.setExpires(System.currentTimeMillis() + TEN_DAYS);
      final String token = tokenHandler.createTokenForUser(user);
    
      // Put the token into a cookie because the client can't capture response
      // headers of redirects / full page reloads. 
      // (this response triggers a redirect back to "/")
      response.addCookie(createCookieForToken(token));
    }

    最终成为最终重定向响应的一部分,如下所示:

    成功登录后,最终重定向回客户端

    成功登录后,最终重定向回客户端

    成功登录后,最终重定向回客户端

    最后也是最好的部分是所有代码结合在一起形成一个非常漂亮的API。 由于Spring Social已经负责创建用户特定的请求范围的ConnectionRepository,因此可以通过将以下bean代码添加到SocialConfigurerAdapter来创建其特定于连接的API:

    @Bean
    @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
    public Facebook facebook(ConnectionRepository repo) {
    Connection<Facebook> connection = repo.findPrimaryConnection(Facebook.class);
    	return connection != null ? connection.getApi() : null;
    }

    此用户特定的facebook bean可以在控制器中使用,如下所示:

    @Autowired
    Facebook facebook;
    
    @RequestMapping(value = "/api/facebook/details", method = RequestMethod.GET)
    public FacebookProfile getSocialDetails() {
    	return facebook.userOperations().getUserProfile();
    }

    客户端实施

    如前所述,令牌现在作为Cookie传递给客户端。 但是,就像以前一样,服务器端仍然只接受发送到特殊HTTP标头中的令牌。 承认这是相当随意的,您可以让它简单地接受cookie。 我不希望这样做,因为它可以防止CSRF攻击。 (因为无法指示浏览器将正确的身份验证令牌自动添加到请求中。)

    因此,在检索当前用户详细信息之前,前端的init方法现在首先尝试将cookie移至本地存储:

    $scope.init = function () {
    	var authCookie = $cookies['AUTH-TOKEN'];
    	if (authCookie) {
    		TokenStorage.store(authCookie);
    		delete $cookies['AUTH-TOKEN'];
    	}
    	$http.get('/api/user/current').success(function (user) {
    		if (user.username) {
    			$rootScope.authenticated = true;
    			$scope.username = user.username;
    			
    			// For display purposes only
    			$scope.token = JSON.parse(atob(
    			    TokenStorage.retrieve().split('.')[0]));
    		}
    	});
    };

    自定义HTTP标头的放置在与上次相同的http拦截器中进行处理。

    实际的“使用Facebook登录”按钮只是触发整个重定向狂潮的链接:

    <a href="/auth/facebook"><button>Login with Facebook</button></a>

    为了检查实际的Facebook API是否有效,我添加了另一个按钮,用于在登录后显示来自facebook的用户详细信息。

    最后的话(建议)

    将我的自定义版本的JWT与社交身份验证集成在一起是一个很大的旅程。 有些部分不那么琐碎。 就像在将数据库调用卸载到JWT令牌之间找到一个很好的平衡。 最终,我选择不与客户端共享Facebook的访问令牌,因为只有在使用Facebook的API时才需要它。 这意味着对Facebook的任何查询都需要数据库调用来获取令牌。 实际上,这意味着对具有@Autowired Facebook服务的任何控制器的任何REST API调用都会导致获取请求令牌的过程非常热烈,这是请求范围的Bean创建的一部分。 但是,通过使用专用控制器进行Facebook调用可以轻松缓解这种情况,但这绝对是需要注意的。

    如果您打算实际使用此代码并进行Facebook API调用,请确保您的JWT令牌在facebook令牌之前过期(当前有效期为60天)。 最好在检测到故障时实施强制重新登录,因为任何重新登录都会自动将新获取的facebook令牌存储在数据库中。

    您可以在github上找到完整的工作示例。 也可以在此处找到有关如何运行它的详细信息。 我已经包含了Maven和Gradle构建文件。

    翻译自: https://www.javacodegeeks.com/2015/01/stateless-spring-security-part-3-jwt-social-authentication.html

    展开全文
  • Flutter_1_Stateless

    2021-03-26 12:08:03
    flutter_stateless 一个新的Flutter应用程序。 入门 该项目是Flutter应用程序的起点。 如果这是您的第一个Flutter项目,那么有一些资源可以帮助您入门: 要获得Flutter入门方面的帮助,请查看我们的,其中提供了...
  • 发包工具 TRex stateless 使用笔记

    千次阅读 2020-04-21 19:11:59
    发包工具 TRex stateless 使用笔记 https://github.com/cisco-system-traffic-generator/trex-core Linux 运行优化 for file in `find /sys/devices/system/cpu/ -name scaling_governor`; do echo performance >...
  • 回顾:在上一篇文章中,我们介绍了stateless4j statemachine的使用入门,这一篇我们介绍 Exit && Entry Action;废话不多说,show you the code:import com.github.oxo42.stateless4j.StateMachineConfig;/*** ...
  • 1.有状态(Stateful):有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,...无状态对象(Stateless Bean),就是没有实例变量的对象.不能保存数据,类里面没有成员变量,或者有成员变量但是不可变的...
  • 标题:.NET中的状态机库Stateless作者:Lamond Lu地址:https://www.cnblogs.com/lwqlun/p/10674018.html...
  • Flutter 中 stateless 和 stateful widget 的区别 介绍 要在 Flutter 中构建任何应用程序,我们必须创建一个小部件类,它是 Flutter 应用程序的构建块。Flutter 使用小部件来创建现代移动应用程序。 Flutter 中的 ...
  • static-stateless-2-way-NAT-on-Linux-with-iptables perform a static stateless 2-way NAT with iptables 如果你在寻找Linux上配置诸如Cisco设备上的static双向NAT的方法,这个或许就是你想要的; what?你觉得它...
  • trex_stateless.pdf

    2021-11-01 14:52:21
    trex stateless手册说明文档
  • 无状态服务(stateless service)

    千次阅读 2020-12-17 16:17:19
    简介: 一、定义 无状态服务(stateless service)对单次请求的处理,不依赖其他请求,也就是说,处理一次请求所需的全部信息,要么都包含在这个请求里,要么可以从外部获取到(比如说数据库),服务器本身不存储...
  • stateless4j

    2020-03-25 18:32:45
    stateless4j 为状态机,可以根据不同触发事件决定下一个状态,适用于当状态字段有许多枚举值,避免状态回退,可以用状态机设置状态的流转,统一管理状态机制。 二 搭建过程 只需要引入依赖即可,如下: <...
  • 可以导出DOT graph 安装起来很简单,直接在nuget中安装即可: Install-Package Stateless Stateless使用 用起来也挺简单的,以打电话这个事情为例,针对打电话的种种动作和状态做成一个状态机。 需要先定义一些状态...
  • 有状态和无状态(@stateless/@stateful)

    千次阅读 2018-01-25 10:29:15
    基本概念:  有状态就是有数据存储功能。...无状态对象(Stateless Bean),就是没有实例变量的对象.不能保存数据,是不变类,是线程安全的。  代码更好理解: /** * 有状态bean,有state,us
  • cd stateless-workstation-config ./deploy.sh 在Windows版Ubuntu上的用法 sudo apt install git git clone https://github.com/cristiklein/stateless-workstation-config.git cd stateless-workstation-config ./...
  • 无状态未来的实用程序 安装 如果您使用 ,请将这些行放在您的build.sbt : libraryDependencies += ...stateless-future-util应该适用于 Scala 2.10.3、2.10.4 或 2.11.x。 链接

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,765
精华内容 12,706
关键字:

stateless

友情链接: eeg kalman.rar