wonbin

wonbin 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

wonbin 收藏了文章 · 1月10日

LNMP环境下php项目目录权限配置

说明

nginx仅能处理静态文件,当遇到php文件时须转发给php-fpm处理,因此项目中的php文件须同时具有给nginx和php-fpm执行的权限,必要时还要加上的权限。nginx和php-fpm的权限来源于它们的执行用户的权限,这里使这两者设置为同一执行用户来统一权限是比较方便的做法。
示范环境:

  • CentOS7.4
  • Nginx1.12.2
  • php7.2
  • 项目根目录是sorgo/
  • 项目管理员的用户名是uu,umask值是0002

操作

设置nginx和php-fpm为同一执行用户

新建执行用户www并自带用户组www,且不可登录.

useradd www -s /usr/sbin/nologin

将已有的项目管理人员账号(如:uu,当然你也可以再创建一个专用的)加入www

usermod -G www uu

修改nginx配置文件,变更执行用户为www,我的环境中该文件路径是:/etc/nginx/nginx.conf
修改nginx配置文件
修改php-fpm配置文件,变更执行用户为www,我的环境中该文件路径是:/etc/php-fpm.d/www.conf
修改php-fpm配置文件

修改完后重启nginx和php-fpm.

设置项目目录权限

下面以laravel项目目录为例,只给相关用户必要的权限,又不会像设置777那样权限过大而存在可能的安全问题。

chown -R uu:www sorgo/       #设置归属
chmod -Rf g+s sorgo/         #SGID,新生成文件或文件夹保持与父目录同一权限组
chmod -R 750 sorgo/          #设置具体权限
chmod -R 770 sorgo/storage/  #程序需要写入和创建新文件的加`写`权限

SGID属性对于像laravel框架这种有tinker让开发者在交互式命令行界面调试代码的特别有用。比如你使用tinker新建了一个日志文件,如果没有SGID那这个新日志文件的所有者和所有组会是uu:uu,这时www用户执行下的程序要写入东西到这个新日志的话就会因权限受阻而报错;而父目录有SGID属性的则会是uu:www,同时保证了两个相关的用户都能正常读写。

后言

曾经有过各种因权限问题而报错的,基本办法也都是设置777权限了事,直到后来一次在我一怒之下将整个项目文件都设了777权限但还报错,真见鬼了,简直要疯掉了。。。
于是才下决心正面解决这个问题,就在网络上找了各种解决方案,有靠谱的也有不靠谱的,以上方案是经我整理实践后确认可靠的方案,希望对你有用。
如果你还知道更好的方案,也请留言分享出来,谢谢~

查看原文

wonbin 收藏了文章 · 2019-10-12

解读 Laravel 中的扩展自动注册机制(Package Auto-discovery)

文章转发自专业的Laravel开发者社区,原始链接:https://learnku.com/laravel/t...

在进入探究 Laravel 包提供者与门面如何自动发现之前,让我们先粗浅剖析一下PHP 中包的概念。

一个包就是一个在多个项目内可复用的代码片段,例如 包 spatie/laravel-analytics 可以让你在laravel项目内,用一种简易方式从谷歌统计(Google Analytics)中取回数据,该包被托管 在 GitHub 上,由 Spatie进行维护,它们会持续发布,更新和修复该包 bug,如果你在项目当中使用该包,希望获取这些一旦发布的更新和修复,无须担心使用Composer 从 Github 上 拷贝一份新代码即可。

Composer 是一个 PHP 依赖管理工具。它允许你声明项目库依赖且管理(安装/更新)它们。 -- 详见官网 getcomposer.org

Laravel 自带 composer.json文件,文件内的 require 或 require-dev条目下,给出了你扩展应用功能需用到的包,执行 composer update:

{
    "require": {
        "spatie/laravel-analytics": "3.*",
    },
}

你也可以使用下面命令,达到同样的效果

composer require spatie/laravel-analytics

Composer所做的工作在于,拉取你所需版本包,下载到  vendor 目录,上述命令执行完毕, 包内所有类和文件被加载进项目,你就可以马上使用它们了,每次当你再次执行 composer update ,Composer 将会重新获取(译者注 通常从 composer 仓库拉取)更新该包,并且自动更新位于你项目  vendor 目录下的文件。

在 Laravel 项目中使用某些 Laravel包 需要以下额外几个步骤

  • 注册服务提供者
  • 注册别名/门面
  • 发布资源

如果你看过 Spatie包安装说明 你会发现,在继续下一步这前,项目配置必须注册服务提供者和一个门面,是一个很好的习惯,这个步骤由 Taylor Otwell定义,只是一个非必要条件, Dries Vints,且达到无论何时你决定引入一个新包或移除包,服务提供者和门面皆可被自动发现。

重温 Taylor 的新特性声明  在媒体上.

什么是服务提供者和门面?

服务提供者负责将事物绑定到 Laravel 的服务容器中,并通知 Laravel 在哪里加载包资源,例如视图,配置和本地化文件。-- laravel.com 文档

你可以在上面阅读有关服务提供者的更多信息 官方文档.

门面为应用程序服务容器中可用的类提供 "static" 接口 -- laravel.com 文档

你可以再上面阅读更多有关门面的信息 官方文档.

在查找和安装/更新不同的扩展包时,Composer 会触发你可以订阅的多个事件并运行你自己的一段代码甚至是命令行可执行文件,其中一个有趣的事件称为 post-autoload-dump。 在 composer 生成项目中自动加载的最终类列表之后直接触发,此时 Laravel 已经可以访问所有类,并且应用程序已准备好使用加载到其中的所有包类。

Laravel 在主 composer.json 文件中订阅此事件:

"scripts": {
    "post-autoload-dump": [
        "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
        "@php artisan package:discover"
    ]
}

首先它调用 postAutoloadDump() 静态方法,此方法会清理缓存的服务或之前发现的包,另一个是它运行 package:discover artisan 命令,这就是 Laravel 可以自动发现是秘密。

包自动发现

Illuminate\Foundation\Console\PackageDiscoverCommandIlluminate\Foundation\PackageManifest 类中调用 build() 方法,该类是Laravel 发现已安装包的地方。

 PackageManifest 在应用程序引导程序的早期注册到容器中,完全来自 Illuminate\Foundation\Application::registerBaseServiceProviders(),此方法在创建 Laravel 应用程序的新实例后直接运行。

在 build() 方法中,Laravel 查找vendor/composer/installed.json 文件,它由composer 生成并保存一个完整的映射,其中包含 composer 安装的所有扩展包的composer.json 文件内容, Laravel 映射该文件的内容并搜索包含 extra.laravel部分的包:

"extra": {
    "laravel": {
        "providers": [
            "Barryvdh\\Debugbar\\ServiceProvider"
        ],
        "aliases": {
            "Debugbar": "Barryvdh\\Debugbar\\Facade"
        }
    }
}

它首先收集该部分的内容,然后查看主 composer.json 文件下的 extra.laravel.dont-discover 的内容,看看你是否决定不自动发现某些包或所有包:

"extra": {
    "laravel": {
        "dont-discover": [
            "barryvdh/laravel-debugbar"
        ]
    }
}

你可以在数组中添加 * 以指示 laravel 完全停止自动注册。

现在 Laravel 收集了有关扩展包的信息

是的,一旦获得所需要的信息,它将在 bootstrap/cache/packages.php 中编写一个 PHP 文件:

<?php return array (
  'barryvdh/laravel-debugbar' =>
  array (
    'providers' =>
    array (
      0 => 'Barryvdh\\Debugbar\\ServiceProvider',
    ),
    'aliases' =>
    array (
      'Debugbar' => 'Barryvdh\\Debugbar\\Facade',
    ),
  ),
);

包注册

Laravel 有两个 bootstrappers,在 HTTP 或控制台内核启动时使用:

  • \Illuminate\Foundation\Bootstrap\RegisterFacades
  • \Illuminate\Foundation\Bootstrap\RegisterProviders

第一个使用 Illuminate\Foundation\AliasLoader 将所有门面加载到应用程序中,现在 Laravel 将查看 packages.php 生成的文件并提取包中希望 Laravel 自动注册的所有别名并注册这些别名。 它使用 PackageManifest::aliases() 方法来收集这些信息。

// 在 RegisterFacades::bootstrap()

AliasLoader::getInstance(array_merge(
    $app->make('config')->get('app.aliases', []),
    $app->make(PackageManifest::class)->aliases()
))->register();

如你所见,从 config/app.php 文件加载的别名与从 PackageManifest 类加载的别名合并。

类似地,Laravel 在启动时注册服务提供者,RegisterProviders bootstrapper 调用 Foundation\ApplicationregisterConfiguredProviders() 方法,并且 Laravel 在这会收集所有应该自动注册的包提供者并注册它们。

$providers = Collection::make($this->config['app.providers'])
                ->partition(function ($provider) {
                    return Str::startsWith($provider, 'Illuminate\\');
                });

$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

在这里,我们在 Illuminate 服务提供者和可能在你的配置文件中的任何其他服务提供者之间注入自动发现的服务提供者,这样我们确保你可以通过在配置文件中重新注册它们来覆盖扩展包服务提供者,并且 Illuminate 组件将会在尝试加载任何其他服务提供者之前加载。

查看原文

wonbin 收藏了文章 · 2019-10-12

你是如何处理 PHP 代码中的枚举类型 Enum 的?

文章转发自专业的Laravel开发者社区,原始链接:https://learnku.com/laravel/t...

本文旨在提供一些更好的理解什么是枚举,什么时候使用它们以及如何在php中使用它们.

我们在某些时候使用了常量来定义代码中的一些常数值.他们被用来避免魔法值.用一个象征性的名字代替一些魔法值,我们可以给它一些意义.然后我们在代码中引用这个符号名称.因为我们定义了一次并使用了很多次,所以搜索它并稍后重命名或更改一个值会更容易.

这就是为什么看到类似于下面的代码并不罕见.


<?php
class User {
    const GENDER_MALE = 0;
    const GENDER_FEMALE = 1;
    const STATUS_INACTIVE = 0;
    const STATUS_ACTIVE = 1;
}

以上常量表示了两组属性,GEDNER_* 和 STATUS_*。他们表示一组性别和一组用户状态。每一组都是一个枚举 。枚举是一组元素(也叫做成员)的集合,每一个枚举都定义了一种新类型。这个类型,和它的值一样,可以包含任意属于该枚举的元素。

在上面的例子中,枚举借助于常量,每一个常量的值都是一个成员。注意,这样做的话,我们只能在常量包含的类型中取值。因此,我们在写这些值的时候不会有类型提示,不知道详细的枚举类型。

来看一个简短的例子, 但我们假定例子中有更多的代码

<?php
interface UserFactory {
    public function create(
        string $email,
        int $gender,
        int $status
    ): User;
}
$factory->create(
    $email,
    User::STATUS_ACTIVE,
    User::GENDER_FEMALE
);

第一眼看上去代码很好,但是他只是碰巧正确运行了!因为两个不同的枚举成员实际上是同一个值,调用create方法成功,是因为这最后两个参数被互换了不影响结果。尽管我们检查方法接受的值是否有效,运行界面也不会警告我们,测试也会通过。有人能正确的发现这些bug,但是它也很可能被忽视掉。之后一些情况,比如合并冲突的时候,如果它的值改变了,它可能会引起系统异常。

如果使用标量类型,我们会受限于这种类型,无法辨别这两个值是是不是属于两个不同的枚举。

另一个问题是这个代码描述的的不是很好。想象一下 create 方法没有引用常量。$gender 被别人看作为一个枚举元素将是有多么困难?看这些元素在哪里被定义又有多么困难?我们之后将会阅读那些代码,因此我们应该尽可能是让代码易于阅读以及和通过。

我们可以做得更好吗? Sure! 这个方法就是是使用类实例作为枚举元素,类本身定义了一个新的类型。 直到PHP 7,我们可以安装 SPL类 PECL扩展并且使用SplEnum


<?php
class YesNo extends \SplEnum
{
    const __default =  self::YES;
    const NO = 0;
    const YES = 1;
}
$no = new YesNo(YesNo::NO);
var_dump($no == YesNo::NO); //true
var_dump(new YesNo(YesNo::NO) == YesNo::NO); //true

我们扩展 SplEnum 并且定义用于创建枚举元素的常量。枚举元素是我们手动构造的对象,在这种情况下是常量值本身。 我们可以将整型与对象进行比较,这可能很奇怪。 另外,正如文档所述,这是一个仿真的枚举。 PHP本身并不支持枚举类型,所以我们在这里探讨的所有内容都是仿真的。

我们用这种方法得到了什么? 我们可以输入提示我们的参数,并让PHP引擎在发生错误时提醒我们。 我们还可以在枚举类中包含一些逻辑,并使用switch语句来模拟多态行为。

但也有一些缺点. 例如, 在大多数情况下, 有些你可以用枚举元素而不能用标识检查. 这不是不可能的,我们不得不非常小心. 由于我们手动创建枚举成员, 所以许多成员应该是同一个成员, 但这一点手动很难确定.

利用 SplEnum 我们解决枚举类型问题, 但是当我们用标识检查的时候不得不非常小心. 我们需要一个方法限制可以创建的多个元素, 例如  multiton (multiple singleton objects).

现在我们将看到由 Java Enum 启发并实现 multiton 的两个不同的库.

第一个是 eloquent/enumeration. 它为每个元素创建一个定义类的实例. 请注意, 没有我们的帮助, 枚举的用户仿真永远不能保证一个枚举实例, 因为我们限制它的每一步都有一个方法去避免.

这个库可以让我们用错误的方式去尝试, 例如用反射创建一个实例, 在这一点上我们可以问我们自己是否做了正确的事. 它也可以在代码的评审过程中有所帮助,因为这样的实现可以定义几个应该被遵循的规则. 如果这些规则比较简单很容易发现代码中存在的问题.

让我们看些实例.


<?php
final class YesNo extends \Eloquent\Enumeration\AbstractEnumeration {
    const NO = 0;
    const YES = 1;
}
var_dump(YesNo::YES()->key()); // YES

我们定义了一个继承  \Eloquent\Enumeration\AbstractEnumeration 的新类 YesNo . 接下来我们定义一个定义元素名和创建表现这些元素的对象的库的常量.

还有一些情况我们需要谨记,用 serialize/deserialize 在其中创建自定义对象 .

我们可以在GitHub页面上找到更多的例子和很完善的文档。

我们要展示的第二个库是 zlikavac32/php-enum. 与 eloquent/enumeration不同,这个库面向允许真正的多态行为的抽象类。 所以,我们可以用每个方法都定义一个枚举元素来实现,而不是使用switch的方法。 通过严格的规则来定义枚举,也可以相当可靠地确保每个元素只有一个实例。

这个库面向抽象类,以便将每个成员的许多实例限制为一个。 这个想法是,每个枚举必须被定义为抽象的,并枚举它的元素。 请注意,你可以通过扩展类,然后构造一个元素来滥用,但是如果你这么用了,这些是会在代码审查过程中标红的。

对于抽象类,我们知道我们不会意外地有一个枚举的新元素,因为它需要具体的实现。 通过遵循在enum本身中保持这些具体实现的规则,我们可以很容易地发现滥用。  匿名类 在这里很有用。

库强制抽象枚举类,但不能强制创建有效的元素。 这是这个库的用户的责任。 图书馆照顾其余的。

让我们看一个简单的例子。


<?php
/**
 * @method static YesNo YES
 * @method static YesNo NO
 */
abstract class YesNo extends \Zlikavac32\Enum\Enum
{
    protected static function enumerate(): array
    {
        return [
            'YES', 'NO'
        ];
    }
}
var_dump(YesNo::YES()->name()); // YES

PHPDoc注释定义了返回枚举元素的现有静态方法。 这有助于搜索和重构代码。 接下来,我们将枚举YesNo定义为抽象,并扩展\Zlikavac32\Enum\Enum并定义一个静态方法enumerate。 然后,在enumerate方法中,我们列出将被用来表示它们的元素名称。

刚刚我们提到了多态行为,那么为什么我们会使用它呢? 当我们试图限制同一个枚举元素的多个实例时会发生一件事,那就是我们不能有循环引用。 让我们想象一下,我们想拥有由NORTHSOUTHEASTWEST组成的WorldSide枚举。 我们还想有一个方法opposite():WorldSide,它返回代表相反的元素。
如果我们试图通过构造函数注入相反元素,在某一时刻,我们获得一个循环引用,这意味着,我们需要相同元素的第二个实例。 为了返回一个有效的相反世界,我们不得不用一个代理对象 或者switch语句破解。

随着多态行为,我们能做的就是让我们看到我们可定义我们需要的WorldSide枚举。

<?php
/**
 * @method static WorldSide NORTH
 * @method static WorldSide SOUTH
 * @method static WorldSide EAST
 * @method static WorldSide WEST
 */
abstract class WorldSide extends \Zlikavac32\Enum\Enum
{
    protected static function enumerate(): array
    {
        return [
            'NORTH' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    return WorldSide::SOUTH();
                }
            },
            'SOUTH' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    return WorldSide::NORTH();
                }
            },
            'EAST' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    return WorldSide::WEST();
                }
            },
            'WEST' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    return WorldSide::EAST();
                }
            }
        ];
    }
    abstract public function opposite(): WorldSide;
}
foreach (WorldSide::iterator() as $worldSide) {
    var_dump(sprintf(
        'Opposite of %s is %s', 
        (string) $worldSide, 
        (string) $worldSide->opposite()
    ));
}

enumerate 方法,我们提供了每一个枚举元素的实现。数组是用枚举元素名称来索引的。当手动的创建元素,我们定义我们元素名称作为数据的键。

我们可以用 WorldSide::iterator() 获取枚举元素的顺序迭代器,来定义和遍历他们。 每一个枚举元素都有一个默认的 __toString(): string实现返回元素的名称。

每个枚举元素返回其相反的元素。

回顾一下,常量不是枚举,枚举不是常量。每个枚举定义一个类型。如果我们有一些常数的值对我们很重要,但名字没有,我们应该坚持常数。如果我们有一些常量的价值对我们无关紧要,但是与同一群体中的其他所有人有所不同则是重要的,请使用枚举

枚举为代码提供了更多的上下文,也可以将某些检查委托给引擎本身。如果PHP有一个本地的枚举支持,这将是非常好的。语法更改可以使代码更具可读性。引擎可以为我们执行检查,并执行一些不能从用户区执行的规则。

你如何使用枚举,你对这个主题有什么想法?请在下方评论

查看原文

wonbin 关注了用户 · 2019-10-12

程序员陈彼得 @guojiang_club

码子12年的资深程序员,破“中年”大魔咒的一万种可能。

公众号:

前程序员陈彼得

PHP果酱

关注 412

wonbin 收藏了问题 · 2019-10-09

Laravel 5.5 中该怎么拦截表单校验失败重定向或者返回JSON的返回格式?

在 laravel 5.1 中 可以在 BaseController 中重写

/**
     * {@inheritdoc}
     */
    protected function buildFailedValidationResponse(Request $request, array $errors)
    {
         return new JsonResponse($errors);
    }

可以实现拦截$this->validate($request,$rules,$messages);方法的自动跳转
现在在 laravel 5.5 中没有了这个方法,现在该怎么修改?

wonbin 赞了问题 · 2019-10-09

解决Laravel 5.5 中该怎么拦截表单校验失败重定向或者返回JSON的返回格式?

在 laravel 5.1 中 可以在 BaseController 中重写

/**
     * {@inheritdoc}
     */
    protected function buildFailedValidationResponse(Request $request, array $errors)
    {
         return new JsonResponse($errors);
    }

可以实现拦截$this->validate($request,$rules,$messages);方法的自动跳转
现在在 laravel 5.5 中没有了这个方法,现在该怎么修改?

关注 5 回答 5

wonbin 赞了回答 · 2019-10-09

解决Laravel 5.5 中该怎么拦截表单校验失败重定向或者返回JSON的返回格式?

我自己回答这个问题吧,希望以后那些自己不能提出解决方案只会说教别人的嘴炮都闭嘴吧。
一. 仍然使用 控制器 中的$request->validate($rules);进行校验,在表单输入不合法的时候,会抛出一个throw new ValidationException();错误,可以在App\Exceptions\Handlerrender()方法中去捕获它

        if ($exception instanceof ValidationException) {
                    return new JsonResponse($exception->getMessage());
                }

二. 我们不使用 request 对象中的 validate 方法,自己构造一个验证

        $validator = Validator::make($request->all(), $rules);
        if ($validator->fails()) {
            return new JsonResponse($validator->getMessage());
        }

推荐使用第一个解决方案,仍然可以达到之前的效果,代码修改量也最小。

关注 5 回答 5

wonbin 收藏了文章 · 2019-09-02

使用 OAuth 2 和 JWT 为微服务提供安全保障

Part 1 - 理论相关

作者 freewolf

关键词

微服务Spring CloudOAuth 2.0JWTSpring SecuritySSOUAA

写在前面

作为从业了十多年的IT行业和程序的老司机,今天如果你说你不懂微服务,都不好意思说自己的做软件的。SOA喊了多年,无人不知,但又有多少系统开发真正的SOA了呢?但是好像一夜之间所有人都投入了微服务的怀抱。

作为目前最主流的“微服务框架”,Spring Cloud发展速度很快,成为了最全面的微服务解决方案。不管什么软件体系,什么框架,安全永远是不可能绕开的话题,我也把它作为我最近一段时间研究微服务的开篇。

老话题!“如何才能在微服务体系中保证安全?”,为了达成目标,这里采用一个简单而可行方式来保护Spring Cloud中服务的安全,也就是建立统一的用户授权中心。

这里补充说一下什么是Authentication(认证)Authorization(鉴权),其实很简单,认证关心你是谁,鉴权关心你能干什么。举个大家一致都再说的例子,如果你去机场乘机,你持有的护照代表你的身份,这是认证,你的机票就是你的权限,你能干什么。

学习微服务并不是一个简单的探索过程,这不得学习很多新的知识,其实不管是按照DDD(Domain Driven Design)领域驱动设计中领域模型的方式,还是将微服务拆分成更小的粒度。都会遇到很多新的问题和以前一直都没解决很好的问题。随着不断的思考,随着熟悉Facebook/GitHub/AWS这些机构是如何保护内部资源,答案也逐渐浮出水面。

为了高效的实现这个目标,这里采用OAuth 2JWT(JSON Web Tokens)技术作为解决方案,

为什么使用OAuth 2

尽管微服务在现代软件开发中还算一个新鲜事物,但是OAuth 2已经是一个广泛使用的授权技术,它让Web开发者在自己提供服务中,用一种安全的方式直访问Google/Facebook/GitHub平台用户信息。但在我开始阐述细节之前,我将揭开聚焦到本文真正的主题:云安全

那么在云服务中对用户访问资源的控制,我们一般都怎么做呢?然我举一些大家似乎都用过的但又不是很完美的例子。

我们可以设置边界服务器或者带认证功能的反向代理服务器,假设所有访问请求都发给它。通过认证后,转发给内部相应的服务器。一般在Spring MVC Security开发中几乎都会这样做的。但这并不安全,最重要的是,一旦是有人从内部攻击,你的数据毫无安全性。

其他方式:我们为所有服务建立统一的权限数据库,并在每次请求前对用户进行鉴权,听起来某些方面的确有点愚蠢,但实际上这确实是一个可行的安全方案。

更好的方式: 用户通过授权服务来实现鉴权,把用户访问Session映射成一个Token。所有远程访问资源服务器相关的API必须提供Token。然后资源服务器访问授权服务来识别Token,得知Token属于哪个用户,并了解通过这个Token可以访问什么资源。

这听起来是个不错的方案,对不?但是如何保证Token的安全传输?如何区分是用户访问还是其他服务访问?这肯定是我们关心的问题。

所以上述种种问题让我们选择使用OAuth 2,其实访问Facebook/Google的敏感数据和访问我们自己后端受保护数据没什么区别,并且他们已经使用这样的解决方案很多年,我们只要遵循这些方法就好了。

OAuth 2是如何工作的

如果你了解OAuth 2相关的原理,那么在部署OAuth 2是非常容易的。
让我们描述下这样一个场景,“某App希望获得TomFacebook上相关的数据”

OAuth 2 在整个流程中有四种角色:

  • 资源拥有者(Resource Owner) - 这里是Tom

  • 资源服务器(Resource Server) - 这里是Facebook

  • 授权服务器(Authorization Server) - 这里当然还是Facebook,因为Facebook有相关数据

  • 客户端(Client) - 这里是某App

Tom试图登录Facebook某App将他重定向到Facebook的授权服务器,当Tom登录成功,并且许可自己的Email和个人信息被某App获取。这两个资源被定义成一个Scope(权限范围),一旦准许,某App的开发者就可以申请访问权限范围中定义的这两个资源。

+--------+                               +---------------+
|        |--(A)- Authorization Request ->|   Resource    |
|        |                               |     Owner     |
|        |<-(B)-- Authorization Grant ---|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(C)-- Authorization Grant -->| Authorization |
| Client |                               |     Server    |
|        |<-(D)----- Access Token -------|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(E)----- Access Token ------>|    Resource   |
|        |                               |     Server    |
|        |<-(F)--- Protected Resource ---|               |
+--------+                               +---------------+

Tom允许了权限请求,再次通过重定向返回某App,重定向返回时携带了一个Access Token(访问令牌),接下来某App就可以通过这个Access TokenFacebook直接获取相关的授权资源(也就是Email和个人信息),而无需重新做Tom相关的鉴权。而且每当Tom登录了某App,都可以通过之前获得的Access Token,直接获取相关授权资源。

到目前为止,我们如何直接将以上内容用于实际的例子中?OAuth 2十分友好,并容易部署,所有交互都是关于客户端和权限范围的。

  • OAuth 2中的客户端权限范围和我们平时的用户和权限是否相同?

  • 我需要将授权映射到权限范围中或将用户映射到客户端中?

  • 为什么我需要客户端?

你也许在之前在类似的企业级开发案例中尝试映射过相关的角色。这会很棘手!

任何类型的应用都提供用户登录,登录结果是一个Access Token,所有的之后的API调用都将这个Access Token加入HTTP请求头中,被调用服务去授权服务器验证Access Token并获取该Token可访问的权限信息。这样一来,所有服务的访问都会请求另外的服务来完成鉴权。

权限范围和角色,客户端和用户

OAuth 2中,你可以定义哪个应用(网站、移动客户端、桌面应用、其他)可以访问那些资源。这里只有一个尺寸,来自哪里的哪个用户可以访问那些数据,当然也是哪个应用或者服务可以访问那些资源。换一种说法,权限范围就是控制那些端点对客户端可见,或者用户根据他的权限来获取相关的数据。

在一个在线商店中,前端可以看做一个客户端,可以访问商品、订单和客户信息,但后端可以关于物流和合同等,另一方面,用户可以访问一个服务但并不是全部的数据,这可以是因为用户正在使用Web应用,当他不能的时候,其他用户却可以。服务之间的访问时我们要讨论的另一个维度。如果你熟悉数学,我可以说在OAuth 2中,客户端-权限范围关系是线性独立于用户-权限关系。

为什么是JWT

OAuth 2并不关心去哪找Access Token和把它存在什么地方的,生成随机字符串并保存Token相关的数据到这些字符串中保存好。通过一个令牌端点,其他服务可能会关心这个Token是否有效,它可以通过哪些权限。这就是用户信息URL方法,授权服务器为了获取用户信息转换为资源服务器。

当我们谈及微服务时,我们需要找一个Token存储的方式,来保证授权服务器可以被水平扩展,尽管这是一个很复杂的任务。所有访问微服务资源的请求都在Http Header中携带Token,被访问的服务接下来再去请求授权服务器验证Token的有效性,目前这种方式,我们需要两次或者更多次的请求,但这是为了安全性也没什么其他办法。但扩展Token存储会很大影响我们系统的可扩展性,这是我们引入JWT(读jot)的原因。

+-----------+                                     +-------------+
|           |       1-Request Authorization       |             |
|           |------------------------------------>|             |
|           |     grant_type&username&password    |             |--+
|           |                                     |Authorization|  | 2-Gen
|  Client   |                                     |Service      |  |   JWT
|           |       3-Response Authorization      |             |<-+
|           |<------------------------------------| Private Key |
|           |    access_token / refresh_token     |             |
|           |    token_type / expire_in / jti     |             |
+-----------+                                     +-------------+

简短来说,响应一个用户请求时,将用户信息和授权范围序列化后放入一个JSON字符串,然后使用Base64进行编码,最终在授权服务器用私钥对这个字符串进行签名,得到一个JSON Web Token,我们可以像使用Access Token一样的直接使用它,假设其他所有的资源服务器都将持有一个RSA公钥。当资源服务器接收到这个在Http Header中存有Token的请求,资源服务器就可以拿到这个Token,并验证它是否使用正确的私钥签名(是否经过授权服务器签名,也就是验签)。验签通过,反序列化后就拿到OAuth 2的验证信息。

验证服务器返回的信息可以是以下内容:

  • access_token - 访问令牌,用于资源访问

  • refresh_token - 当访问令牌失效,使用这个令牌重新获取访问令牌

  • token_type - 令牌类型,这里是Bearer也就是基本HTTP认证

  • expire_in - 过期时间

  • jti - JWT ID

由于Access TokenBase64编码,反编码后就是下面的格式,标准的JWT格式。也就是HeaderPayloadSignature三部分。

{ 
  "alg":"RS256",
  "typ":"JWT"
}
{
  "exp": 1492873315,
  "user_name": "reader",
  "authorities": [
    "AURH_READ"
  ],
  "jti": "8f2d40eb-0d75-44df-a8cc-8c37320e3548",
  "client_id": "web_app",
  "scope": [
    "FOO"
  ]
}
&:lƧs)ۡ-[+
F"2"Kآ8ۓٞ:u9ٴ̯ޡ 9Q32Zƌ޿$ec{3mxJh0DF庖[!뀭N)㥔knVVĖV|夻ׄE㍫}Ŝf9>'<蕱굤Bۋеϵov虀DӨ8C4K}Emޢ    YVcaqIW&*uʝub!׏*Ť\՟-{ʖX܌WTq

使用JWT可以简单的传输Token,用RSA签名保证Token很难被伪造。Access Token字符串中包含用户信息和权限范围,我们所需的全部信息都有了,所以不需要维护Token存储,资源服务器也不必要求Token检查。

+-----------+                                    +-----------+
|           |       1-Request Resource           |           |
|           |----------------------------------->|           |
|           | Authorization: bearer Access Token |           |--+
|           |                                    | Resource  |  | 2-Verify
|  Client   |                                    | Service   |  |  Token
|           |       3-Response Resource          |           |<-+
|           |<-----------------------------------| Public Key|
|           |                                    |           |
+-----------+                                    +-----------+

所以,在微服务中使用OAuth 2,不会影响到整体架构的可扩展性。淡然这里还有一些问题没有涉及,例如Access Token过期后,使用Refresh Token到认证服务器重新获取Access Token等,后面会有具体的例子来展开讨论这些问题。

如果您感兴趣,后面还会有实现部分,敬请期待~

由于 http://asciiflow.com/ 流程图使用中文就无法对齐了,本文中流程图都是英文了~

查看原文

wonbin 关注了专栏 · 2019-08-29

Jensen

http://www.jianshu.com/u/7ae8671ca7f4

关注 282

wonbin 赞了文章 · 2019-08-23

Laravel 教程 - 实战 果酱社区 开源电商 API 系统

重要通知: Laravel + 小程序的开源电商版本源码已经在 github 上拉,欢迎提交 issue 和 star :)
开源电商 Server 端: Laravel API源码
开源电商 client 端:小程序源码

果酱社区 简介

IYOYO 公司于2011年在上海创立。经过8年行业积累,IYOYO 坚信技术驱动商业革新,通过提供产品和服务助力中小企业向智能商业转型升级。

基于社交店商的核心价值,在2016年9月启动 果酱社区 产品,果酱以O2O交易、会员权益、数据跟踪分析、内容体验四大体系形成战略整合方案,打造智能商业生态。

果酱社区 产品包含H5微商城、小程序商城、互动体验平台、门店导购、品牌官网打造等功能及服务,迎合场景化、社群化、个性化的新零售时代,为企业提供灵活定制的产品解决方案,让生意更智慧。

果酱社区 技术方案

果酱社区采用的技术方案有:

  1. Laravel:API + 管理后台
  2. vue.js:H5 SPA 单页应用
  3. 微信小程序
  4. docker: 所有应用 docker 化,实现快速部署 + 自动更新 + 快速扩容+ 负载均衡

果酱社区 核心模块

果酱社区 产品包含以下核心模块:

  • H5 微商城 (vue.js + Laravel API)
  • 小程序商城 (微信小程序 + Laravel API)
  • 分销功能 (vue.js + 小程序 + Laravel API)
  • 活动报名 (vue.js + Laravel API)
  • 导购小程序 (小程序 + Laravel API)
  • 微信第三方平台 (Laravel + easywechat)

API 教程

为什么想着开源和出教程,出于以下几个目的:

  1. 提升 果酱社区 产品知名度,18 年主要在自己朋友的关系圈内进行传播。19 年希望能够有更多朋友了解到 果酱 这款产品。
  2. 来源社区,回馈社区。果酱社区 产品能够在短时间内快速完成开发、上线并且稳定运营,完全是依托开源社区丰富的资源,因此我们计划开源回馈社区。
  3. 帮助更多的初学者。在面试的过程中,发现很多初学者,基础较差,在外经过培训后,仍然无法满足公司的招人要求,因此希望通过教程能够帮助一些真正想学习的初学者学习到有价值的内容。
  4. 虽然为教程,实则也为文档,能够让公司新同事快速了解 果酱社区 产品。
  5. 产品趋于稳定后,有时间来做这件事情。

能学到什么?

  1. 环境的搭建 + 服务器的部署与运维
  2. PHP 基本技能 + 编码规范
  3. Composer 的使用
  4. Laravel 基础知识 + 高级技能
  5. Laravel API 解决方案
  6. 设计模式
  7. 单元测试
  8. 电商业务

适合谁?

本教程专注后端开发,专注 Laravel 方案,我们会努力完善教程的每个细节,把入门门槛降到最低,让初学者能够快速上手。我们的教程偏向实操型,就算是刚入门变成的初学者,只要按照教程的内容一步步操作下去,也能够顺利完成学习。

  • 计算机类在校生、应届生
  • 入门级程序员
  • PHP 程序员
  • 电商类产品经理

教程特点

  1. 实战、实战、实战: 少理论,重动手,看结果。
  2. 最佳实践:良好的编码规范 + 单元测试 + 持续集成 + 文档 ,从一开始就形成良好的编码习惯。
  3. 提升思维能力:授之于鱼不如授之于渔,教你如何思考问题,解决问题。
  4. 真实的电商业务:所有的业务需求来自真实的客户,并且线上良好运营中。

教程目录

0.环境准备

1.果酱小店 登录流程(小程序)

  • 1.1 流程简介与分析
  • 1.2 验证码的发送与验证
  • 1.3 Laravel Passport 实现用户注册与登录
  • 1.4 知识小结

2.微信登录

  • 2.1 需求分析
  • 2.2 流程讲解
  • 2.3 iBrand 微信第三方平台
  • 2.4 功能开发
  • 2.5 知识小结

3. 用户系统

  • 3.1 需求分析
  • 3.2 基础信息
  • 3.3 同步微信信息
  • 3.4 收货地址管理

4.商品系统

  • 4.1 商品分类
  • 4.2 商品品牌
  • 4.3 商品规格与规格值
  • 4.4 商品属性与属性值
  • 4.5 商品模型
  • 4.6 商品信息
  • 4.7 商品列表基础开发
  • 4.8 商品列表筛选开发
  • 4.9 商品搜索开发
  • 4.10 商品详情页基础开发
  • 4.11 商品详情页选择SKU开发
  • 4.12 知识小结

5.购物车系统

  • 5.1 需求分析
  • 5.2 流程讲解
  • 5.3 商品加入购物车开发
  • 5.4 购物车展示商品开发
  • 5.5 购物车移除商品开发
  • 5.6 购物车修改商品数量开发
  • 5.7 知识小结

6.订单系统

  • 6.1 需求分析
  • 6.2 流程讲解
  • 6.3 系统设计
  • 6.4 订单列表开发
  • 6.5 订单详情开发

7.促销系统

8.优惠券系统

9.购物流程-下单

10.售后系统

11.购物流程-售后

12.Docker

13.阿里云部署

更多待完善

查看原文

赞 44 收藏 43 评论 5

认证与成就

  • 获得 2 次点赞
  • 获得 5 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-11-24
个人主页被 268 人浏览