安全

安全<一个类="headerlink" href="#security" title="¶">¶

截图

你更喜欢视频教程吗?检查<一个类="reference external" href="https://symfonycasts.com/screencast/symfony-security">欧宝娱乐app下载地址Symfony安全屏幕系列

欧宝娱乐app下载地址Symfony的安全系统非常强大,但设置起来也会令人困惑。别担心!在本文中,你将学习如何逐步设置你的应用程序的安全系统:

  1. 安装安全支持
  2. 创建您的用户类
  3. 身份验证和防火墙
  4. 拒绝访问您的应用程序(授权)
  5. 获取当前的User对象

之后将讨论其他一些重要的话题。

1)安装<一个类="headerlink" href="#installation" title="¶">¶

在应用程序中使用<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/setup.html">欧宝娱乐app下载地址Symfony Flex.,请在使用之前运行此命令以安装安全功能:

1
作曲家需要symfony / se欧宝娱乐app下载地址curity-bundle

提示

一个<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/experimental_authenticators.html">新的实验安全是在Symfony 5.1中引入的,欧宝娱乐app下载地址它最终将取代Symfony 6.0中的安全性。这个系统几乎完全向后兼容当前的Symfony安全,添加这一行到你的安全配置开始使用它:欧宝娱乐app下载地址

  • yaml.
    1 2 3 4
    #配置/包/安全性.YAML安全enable_authenticator_manager真的#...
  • XML.
    12 3 4 5 6 7 8 9 10 11 12 13 14
    <!--config/packages/security.xml -->< ?xml version = " 1.0 " encoding = " utf - 8 " ?>< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”>启用 - 验证器 - 管理器=“真正的”><!--.。。-->
  • PHP.
    1 2 3 4 5
    // config / packages / security.php$容器- >loadfromextension.'安全''Enable_Authenticator_Manager'=>真的/ /……]);

2a)创建您的用户类<一个类="headerlink" href="#a-create-your-user-class" title="¶">¶

不管如何您将认证(例如登录表单或API令​​牌)或在哪里您的用户数据将被存储(数据库,单点登录),下一步始终相同:创建“用户”类。最简单的方法是使用<一个类="reference external" href="//www.oldmanjams.com/doc/current/bundles/SymfonyMakerBundle/index.html">makerbundle.

让我们假设您希望使用Doctrine将您的用户数据存储在数据库中:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
PHP BIN / CONSOLE使:用户安全用户类的名称(例如用户)[User]:>用户您想在数据库中存储用户数据(通过Doctrine)吗?(是/否)(是的):>是的输入一个属性名称,将是用户的唯一“显示”名称(例如,电子邮件,用户名,UUID [电子邮件]>电子邮件这个应用程序需要哈希/检查用户密码吗?(是/否)(是的):>是的创建:src /实体/ user.php创建:src / repository / userrepository.php更新:src /实体/ User.php更新:配置/包/安全性.YAML

就是这样!该命令询问了几个问题,以便它可以完全生成您所需要的内容。最重要的是User.php文件本身。的只有统治你的用户课是它必须实现欧宝娱乐app下载地址Symfony \ Component \ Security \ Core \ User \ User \ UserInterface.请随意添加任何您需要的其他字段或逻辑。如果你的用户类是一个实体(如在这个例子中),你可以使用<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/doctrine.html">:实体命令添加更多字段。另外,确保对新实体进行并运行迁移:

1 2
PHP BIN / CONSOLE使:迁移php bin /控制台学说:迁移:迁移

2B)“用户提供者”<一个类="headerlink" href="#b-the-user-provider" title="¶">¶

除了你的用户类,您还需要一个“用户提供商”:一个有助于一些东西的类,例如从会话和一些可选功能重新加载用户数据,如<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/remember_me.html">记得我和<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/impersonating_user.html">模拟

幸运的是,这制作:用户命令已经为您配置了一个security.yaml文件下的文件供应商钥匙:

  • yaml.
    1 2 3 4 5 6 7 8 9 10
    #配置/包/安全性.YAML安全#...供应商#用于重新加载来自会话和其他功能的用户(例如switch_user)app_user_provider实体班级App \ Entity \ User财产电子邮件
  • XML.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    <!--config/packages/security.xml -->< ?encoding="UTF-8"< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsd“><设置><! - 用于从会话和其他功能中重新加载用户(例如,切换 - 用户) - ><供应商name =“app_user_provider”><实体类=“app \ entity \用户”属性=“电子邮件”/>
  • PHP.
    12 3 4 5 6 7 8 9 10 11 12 13 14 15 16
    // config / packages / security.phpApp \ Entity \ User$容器- >loadfromextension.'安全'/ /……'提供者'=>//用于重新加载来自会话和其他功能的用户(例如switch_user)“app_user_provider”=>'实体'=>'班级'=>用户::班级'财产'=>“电子邮件”],],],]);

如果你的用户班级是一个实体,你不需要做任何其他事情。但如果你的课程是不是一个实体,那么制作:用户也会产生一个UserProvider你需要完成的课程。在这里了解更多关于用户提供商的信息:<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/user_provider.html">用户服务提供商

2C)编码密码<一个类="headerlink" href="#c-encoding-passwords" title="¶">¶

并非所有应用程序都有需要密码的“用户”。如果您的用户有密码,您可以控制这些密码是如何编码的security.yaml.的制作:用户命令将为您预先配置此操作:

  • yaml.
    1 2 3 4 5 6 7 8 9 10 11
    #配置/包/安全性.YAML安全#...编码器#在这里使用你的用户类名App \ Entity \ User#使用本机密码编码器#自动选择最好的散列算法#(即可用时的钠)。算法汽车
  • XML.
    12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    <!--config/packages/security.xml -->< ?encoding="UTF-8"< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”><设置><!--.。。--><编码器类=“app \ entity \用户”算法=“汽车”成本=“12”/><!--.。。-->
  • PHP.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    // config / packages / security.phpApp \ Entity \ User$容器- >loadfromextension.'安全'/ /……“编码器”=>用户::班级=>'算法'=>“汽车”“成本”=>12.],/ /……]);

现在Symfony欧宝娱乐app下载地址知道了如何您想对密码进行编码,可以使用userpasswordencoderInterface.在将用户保存到数据库之前,请执行此操作。

例如,使用<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/testing.html">DoctrineFixturesBundle,可以创建虚拟数据库用户:

1 2 3 4
PHP BIN / CONSOLE MATE:夹具要创建的fixture的类名(例如AppFixtures):> userfixtures.

使用此服务来编码密码:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// src / datafixtures / userfixtures.php+使用Sym欧宝娱乐app下载地址fony核心组件\ \安全\ \编码器\ UserPasswordEncoderInterface;/ /……类UserFixtures扩展Fixture {+私人passwordEncoder美元;+公共功能__construct(userpasswordencoderInterface $ passwordencoder)+ {+ $this->passwordEncoder = $passwordEncoder;+}public function load(ObjectManager $manager) {$user = new user ();/ /……+ $ user - >向setPassword ($ this - > passwordEncoder - > encodePassword (+ $用户,+'the_new_password'+));/ /……}}

您可以通过运行手动编码密码:

1
PHP BIN / CONSOLE安全:编码密码

3a)认证和防火墙<一个类="headerlink" href="#a-authentication-firewalls" title="¶">¶

5.1新版功能:懒惰的:真的选项是在Symfony 5.1中引入的。欧宝娱乐app下载地址版本5.1之前,使用匿名的:懒惰的

安全系统配置在配置/包/ security.yaml.的最多重要部分是防火墙

  • yaml.
    1 2 3 4 5 6 7 8 9
    #配置/包/安全性.YAML安全防火墙开发图案^ /(_(分析器| wdt) | css |图片| js) /安全错误的主要的匿名真的懒惰的真的
  • XML.
    12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    <!--config/packages/security.xml -->< ?encoding="UTF-8"< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”><设置><防火墙name =“开发”模式=“^ /(_(分析器| wdt) | css |图片| js) /”安全=“假”/><防火墙name =“主要”匿名=“真正的”懒惰=“真正的”/>
  • PHP.
    1 2 3 4 5 6 7 8 9 10 11 12 13
    // config / packages / security.php$容器- >loadfromextension.'安全''防火墙'=>“开发”=>“模式”=>' ^ /(_(分析器| wdt) | css |图片| js) / ''安全'=>错误的],“主要”=>'匿名的'=>真的'懒惰的'=>真的],],]);

“防火墙”是您的身份验证系统:它定义下面的配置如何你的用户将能够验证(例如登录表格,API令牌等)。

对于每个请求,只有一个防火墙是活动的:Symfony使用欧宝娱乐app下载地址图案键来查找第一个匹配项(也可以<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/firewall_restriction.html">根据主人或其他东西匹配).的开发防火墙真的是一个假防火墙:确保你不小心阻止Symfony的开发工具 - 它在像URL下生活欧宝娱乐app下载地址/ _profiler./ _wdt

所有真正的控件处理url主要的防火墙(不图案关键意味着它匹配所有url)。防火墙可以有多种身份验证模式,换句话说,有多种方式询问“您是谁?”通常,当用户第一次访问你的网站时,他们是未知的(即未登录)。的匿名Mode(如果启用)将用于这些请求。

事实上,如果你现在去主页,你可以访问,你会看到你被“认证”为anon。.防火墙验证它不知道您的身份,因此,您是匿名的:

_images / anonymous_wdt.png

这意味着任何请求都可以具有匿名令牌来访问某些资源,而某些操作(即某些页面或按钮)仍然需要特定权限。然后,用户可以访问表单登录,而不被身份验证为唯一用户(否则,将发生无限重定向循环,要求用户在尝试执行此操作时进行身份验证)。

您将学习稍后如何拒绝访问某些URL,控制器或模板的一部分。

提示

懒惰的如果不需要授权,匿名模式会导致会话启动(即,用于用户权限的显式检查)。这对于保持令人难以缓存的要求非常重要(见<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/http_cache.html">HTTP缓存).

笔记

如果没有看到工具栏,请安装<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/profiler.html">分析器:

1
编写器需要——dev symfony/prof欧宝娱乐app下载地址iler-pack

现在我们了解我们的防火墙,下一步是为您的用户进行身份验证,为您创建一种方式!

3b)用户认证<一个类="headerlink" href="#b-authenticating-your-users" title="¶">¶

Symfony的身份验证首先可以感受欧宝娱乐app下载地址到有点“魔法”。那是因为,而不是构建路线和控制器来处理登录,你将激活一个身份验证提供者:一些自动运行的代码调用控制器。

欧宝娱乐app下载地址symfony有几个<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/auth_providers.html">内置的身份验证提供者.如果您的用例匹配其中一个确切地,太好了!但是,在大多数情况下——包括登录表单——我们建议建造护罩认证器:一个允许您控制的类每一个身份验证过程的一部分(请参阅下一节)。

提示

如果您的应用程序通过第三方服务如谷歌、Facebook或Twitter(社交登录)登录用户,请检查<一个类="reference external" href="https://github.com/hwi/HWIOAuthBundle">hwioauthbundle.欧宝下载链接社区捆绑。

后卫的身份验证器<一个类="headerlink" href="#guard-authenticators" title="¶">¶

Guard验证器是一个类,它给你完全的对身份验证过程的控制。有许多不同的方法来构建验证器;以下是一些常见的用例:

限制登录尝试<一个类="headerlink" href="#limiting-login-attempts" title="¶">¶

5.2新版功能:登录节流是在Symfony 5.2中引入的。欧宝娱乐app下载地址

欧宝娱乐app下载地址Symfony提供基本保护<一个类="reference external" href="https://owasp.org/www-community/controls/Blocking_Brute_Force_Attacks">暴力登录攻击如果你正在使用<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/experimental_authenticators.html">实验验证器.必须使用login_throttling设置:

  • yaml.
    12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    #配置/包/安全性.YAML安全enable_authenticator_manager真的防火墙#...主要的#...#默认情况下,该功能允许每分钟5次登录尝试login_throttling空值#设置最大登录次数(每分钟)login_throttlingmax_attempt.3.#使用自定义的速率限制通过它的服务IDlogin_throttling限幅器app.my_login_rate_limiter.
  • XML.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 20 21 21 22 23 24 24 22
    <!--config/packages/security.xml -->< ?xml version = " 1.0 " encoding = " utf - 8 " ?>< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”>启用 - 验证器 - 管理器=“真正的”><!--.。。--><防火墙name =“主要”><!--by default, the feature allows 5 login attempts per minute -->< login-throttling / ><! - 配置最大登录尝试(每分钟) - >< login-throttling最大尝试=“3”/><!--用一个custom rate limiter via its service ID -->< login-throttling限制器=“app.my_login_rate_limiter”/>< /防火墙>
  • PHP.
    12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
    // config / packages / security.php$容器- >loadfromextension.'安全''Enable_Authenticator_Manager'=>真的'防火墙'=>/ /……“主要”=>//默认情况下,该功能允许每分钟5个登录尝试“login_throttling”=>空值//设置最大登录尝试次数(每分钟)“login_throttling”=>'max_attempts'=>3.],],],]);

默认情况下,登录尝试次数是有限的max_attempt.(默认:5)请求失败知识产权地址+用户名5max_attempt.对请求失败知识产权地址.第二个限制是防止使用多个用户名的攻击者绕过第一个限制,而不会干扰大型网络(如办公室)上的正常用户。

提示

限制失败的登录尝试只是防止暴力破解攻击的一个基本保护。的<一个类="reference external" href="https://owasp.org/www-community/controls/Blocking_Brute_Force_Attacks">OWASP蛮力攻击指导方针提及您应考虑根据所需的保护级别的其他保护。

如果需要更复杂的限制算法,创建一个实现的类欧宝娱乐app下载地址symfony \ component \ httpfoundation \ RATELIMITER \ RequestRatElimiterFace(或使用欧宝娱乐app下载地址symfony \ component \ security \ http \ Ratelimiter \ DefaultLoginRatelimiter)并设置限幅器服务ID的选项:

  • yaml.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 20 21 22 21 22 29 29 29 29 29 29 29 29 30
    #配置/包/安全性.YAML框架Rate_Limiter.定义2个速率限制(一个用户名+IP,另一个IP)USERNME_IP_LOGIN.政策token_bucket.限制5速度间隔'5分钟的ip_login.政策滑动窗口限制50间隔”15分钟的服务#我们的自定义登录速率限制器app.login_rate_limiter班级欧宝娱乐app下载地址symfony \ component \ security \ http \ Ratelimiter \ DefaultLoginRatelimiter参数# globalFactory是IP的限制者$ GlobalFactory.“@limiter.ip_login”# localFactory是用户名+IP的限制localFactory美元'@ limiter.username_ip_login'安全防火墙主要的#使用自定义的速率限制通过它的服务IDlogin_throttling限幅器app.login_rate_limiter
  • XML.
    12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
    <!--config/packages/security.xml -->< ?xml version = " 1.0 " encoding = " utf - 8 " ?>< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns:框架=“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/symfony”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/symfony.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/symfony/symfony-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”><框架:限速><! - 定义2速率限制器(一个用于用户名+ IP,另一个用于IP) - ><框架:限制器name =“username_ip_login”政策=“token_bucket”限制=“5”><框架:速度间隔=“5分钟”/>< /框架:限幅器><框架:限制器name =“ip_login”政策=“滑动窗口”限制=“50”间隔=“15分钟”/>< /框架:配置><! - 我们的自定义登录速率限制器 - >id =“app.login_rate_limiter”类=“欧宝娱乐app下载地址symfony \ component \ security \ http \ Ratelimiter \ DefaultLoginRatelimiter”><! - 第1参数是IP的限制器 - >< srv:论点类型=“服务”id =“limiter.ip_login”/><! - 第二个参数是用户名+ IP的限制器 - >< srv:论点类型=“服务”id =“limiter.username_ip_login”/>< / srv:服务><设置><防火墙name =“主要”><!--用一个custom rate limiter via its service ID -->< login-throttling限制器=“app.login_rate_limiter”/>< /防火墙>
  • PHP.
    12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
    // config / packages / security.php欧宝娱乐app下载地址symfony \ component \依赖等度incipt \ Refern欧宝娱乐app下载地址symfony \ component \ security \ http \ Ratelimiter \ DefaultLoginRatelimiter$容器- >loadfromextension.“框架”'rate_limiter'=>//定义2速率限制器(一个用于用户名+ IP,另一个用于IP)'username_ip_login'=>'政策'=>'token_bucket'“限制”=>5“速度”=>'间隔'=>'5分钟'],],“ip_login”=>'政策'=>'滑动窗口'“限制”=>50'间隔'=>'15分钟'],],]);$容器- >注册“app.login_rate_limiter”DefaultLoginRateLimiter::班级- >setArguments([//第1参数是IP的限制器参考'limiter.ip_login'),// 2nd参数是用户名+ IP的限制器参考'limiter.username_ip_login'),]);$容器- >loadfromextension.'安全''防火墙'=>“主要”=>//通过其服务ID使用自定义速率限制器“login_throttling”=>“限制器”=>“app.login_rate_limiter”],],],]);

4)拒绝访问,角色和其他授权<一个类="headerlink" href="#denying-access-roles-and-other-authorization" title="¶">¶

用户现在可以使用您的登录表单登录您的应用程序。伟大的!现在,您需要了解如何拒绝访问和使用用户对象。这就是所谓的授权而且其工作是决定用户是否可以访问某些资源(URL,模型对象,方法调用,...)。

授权过程有两个不同的侧面:

  1. 用户在登录时接收一组特定的角色(例如。ROLE_ADMIN).
  2. 你添加代码,使一个资源(例如URL,控制器)需要一个特定的“属性”(最常见的是一个角色,如ROLE_ADMIN),以便被访问。

角色<一个类="headerlink" href="#roles" title="¶">¶

当用户登录时,Symfony会调用欧宝娱乐app下载地址getroles()方法在你的用户对象确定该用户具有哪个角色。在里面用户我们之前生成的类,角色是存储在数据库中的数组,每个用户都是总是给予至少一个角色:角色_User.

/ / src /实体/ User.php/ /……班级用户/ *** @ORM \列(type = " json ")* /私人美元的角色[];/ /……公共函数将getRoles()数组美元的角色这个美元- >角色//保证每个用户至少有角色_user美元的角色[]'角色_user'返回array_unique美元的角色);

这是一个很好的默认值,但你可以做到任何您希望确定用户应该拥有的角色。以下是一些指导方针:

  • 每一个角色必须从具备ROLE_(否则,事情不会如你所愿)
  • 除了上面的规则,角色只是一个字符串,你可以创造你需要的东西。ROLE_PRODUCT_ADMIN).

您将使用这些角色授予对站点特定部分的访问权。你也可以用a<一个类="reference internal" href="#security-role-hierarchy">角色层次结构有些角色会自动赋予你其他角色。

添加拒绝访问的代码<一个类="headerlink" href="#add-code-to-deny-access" title="¶">¶

两个拒绝进入某事的方法:

  1. 在security.yaml access_control允许您保护URL模式(例如/管理/ *).更简单,但不太灵活;
  2. 在您的控制器(或其他代码)中

保护URL模式(Access_Control)<一个类="headerlink" href="#securing-url-patterns-access-control" title="¶">¶

确保应用程序部分的最基本方式是确保整个URL模式security.yaml.例如,需要ROLE_ADMIN为所有以/行政, 你可以:

  • yaml.
    12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
    #配置/包/安全性.YAML安全#...防火墙#...主要的#...访问控制#要求/ admin *-小路'^ / admin'角色ROLE_ADMIN#或require_admin或is_authenticated_flus / admin *-小路'^ / admin'角色IS_AUTHENTICATED_FULLYROLE_ADMIN# path值可以是任何有效的正则表达式#(这个匹配/api/post/7298和/api/comment/528491)-小路^ / api /(帖子|​​注释)/ \ d + $角色角色_User.
  • XML.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 20 21 21 22 23 23 29 29 29 29 29 29 32 32
    <!--config/packages/security.xml -->< ?encoding="UTF-8"< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”><设置><!--.。。--><防火墙name =“主要”><!--.。。-->< /防火墙><! - 要求/ admin *  - ><规则路径=“^ /管理”角色=“角色_admin”/><! - 要求角色_admin或is_authenticate_flus for / admin *  - ><规则路径=“^ /管理”>< >角色ROLE_ADMIN> < /角色< >角色IS_AUTHENTICATED_FULLY> < /角色<!--the 'path' value can be any valid regular expression(这个将匹配url像/api/post/7298和/api/comment/528491)——><规则路径=“^ / api /(帖子|​​注释)/ \ d + $”角色=“ROLE_USER”/>
  • PHP.
    12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
    // config / packages / security.php$容器- >loadfromextension.'安全'/ /……'防火墙'=>/ /……“主要”=>/ /……],],'访问控制'=>//需要ROLE_ADMIN为/admin*“路径”=>'^ / admin'“角色”=>“ROLE_ADMIN”],//需要ROLE_ADMIN或IS_AUTHENTICATED_FULLY“路径”=>'^ / admin'“角色”=>“ROLE_ADMIN”“IS_AUTHENTICATED_FULLY”]],//'path'值可以是任何有效的正则表达式//(这个将匹配像/ api / post / 7298和/ api / compy / 528491等网址)“路径”=>'^ / api /(帖子|​​注释)/ \ d + $'“角色”=>'角色_user'],],]);

您可以根据需要定义多个URL模式 - 每个都是正则表达式。, 只要一个将匹配每个请求:symfony在列表顶部开始,并且在找到第欧宝娱乐app下载地址一个匹配时停止停止:

  • yaml.
    1 2 3 4 5 6 7 8 9 10
    #配置/包/安全性.YAML安全#...访问控制# /管理/用户/ *匹配-小路'^ / admin /用户'角色ROLE_SUPER_ADMIN# matches /admin/*除了上面的规则-小路'^ / admin'角色ROLE_ADMIN
  • XML.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
    <!--config/packages/security.xml -->< ?encoding="UTF-8"< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”><设置><!--.。。--><规则路径=“^ / admin /用户”角色=“ROLE_SUPER_ADMIN”/><规则路径=“^ /管理”角色=“角色_admin”/>
  • PHP.
    1 2 3 4 5 6 7 8 9
    // config / packages / security.php$容器- >loadfromextension.'安全'/ /……'访问控制'=>“路径”=>'^ / admin /用户'“角色”=>'角色_super_admin'],“路径”=>'^ / admin'“角色”=>“ROLE_ADMIN”],],]);

预先完成路径意味着只有url开始与模式相匹配。例如:路径/行政(没有这一点)将匹配/管理/ foo但也会匹配你的网址/ foo /管理

每个访问控制也可以匹配IP地址,主机名和HTTP方法。还可以将用户重定向到https.URL模式的版本。看到<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/access_control.html">安全access_control如何工作?

保护控制器和其他代码<一个类="headerlink" href="#securing-controllers-and-other-code" title="¶">¶

你可以拒绝来自控制器内部的访问:

/ / src /控制器/ AdminController.php/ /……公共函数adminDashboard()响应这个美元- >denyAccessUnlessGranted“ROLE_ADMIN”);//或添加一个可选的消息-可被开发人员看到这个美元- >denyAccessUnlessGranted“ROLE_ADMIN”空值“用户尝试访问页面而不具有角色_admin”);

就是这样!如果没有授予访问权限,则特殊欧宝娱乐app下载地址Symfony \ \安全\ \例外\ AccessDeniedException核心组件被抛出,并在控制器中没有更多代码。然后,将发生两件事之一:

  1. 如果用户未登录,则会要求它们登录(例如,重定向到登录页面)。
  2. 如果是用户登录,但是不是ROLE_ADMIN角色,他们将显示403访问被拒绝的页面(您可以<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/controller/error_pages.html">定制).

感谢Sensioframeworkextrabundle,您还可以使用注释保护您的控制器:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// src/Controller/AdminController.php //…+使用sensio \ bundle \ frameworkextrabundle \ configuration \ Isgranted;+ / * *+ *在此类中需要leor_admin *每个*控制器方法。+ *+ * @isgranted(“角色_admin”)+ * /类AdminController扩展AbstractController {+ / * *+ *仅需要此控制器方法的角色_admin。+ *+ * @isgranted(“角色_admin”)+ * /public function adminashboard (): Response{//…}}

有关更多信息,请参阅<一个类="reference external" href="//www.oldmanjams.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html">FrameworkExtraBundle文欧宝体育电话档

模板中的访问控制<一个类="headerlink" href="#access-control-in-templates" title="¶">¶

如果要检查当前用户是否具有某种角色,可以使用内置的被授予()任何枝形模板中的帮助函数:

1 2 3
{%如果被授予“ROLE_ADMIN”%}<一个href“......”>删除一个>{%endif%}

确保其他服务<一个类="headerlink" href="#securing-other-services" title="¶">¶

看到<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/securing_services.html">如何保护应用程序中的任何服务或方法

设置单个用户权限<一个类="headerlink" href="#setting-individual-user-permissions" title="¶">¶

大多数应用程序需要更具体的访问规则。例如,用户应该只能编辑它们的自己的评论博客。选民允许你写任何确定访问所需的业务逻辑。使用这些投票者类似于前面章节中实现的基于角色的访问检查。读<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/voters.html">如何使用选民检查用户权限学习如何实现自己的选民。

检查用户是否已登录(IS_AUTHENTICATED_FULLY)<一个类="headerlink" href="#checking-to-see-if-a-user-is-logged-in-is-authenticated-fully" title="¶">¶

如果你只有想要检查用户是否已经登录(您不关心角色),您有两个选项。首先,如果你已经给予每一个用户角色_User.,您可以检查该角色。否则,你可以使用一个特殊的“属性”来代替角色:

/ /……公共函数adminDashboard()响应这个美元- >denyAccessUnlessGranted“IS_AUTHENTICATED_FULLY”);/ /……

您可以使用IS_AUTHENTICATED_FULLY任何使用角色的地方:例如访问控制或者在树枝。

IS_AUTHENTICATED_FULLY不是一个角色,但它有点像一个一样的行为,每个登录的用户都会有这个。实际上,有一些特殊的属性如此:

  • IS_AUTHENTICATED_REMEMBERED所有登录用户有这个功能,即使他们是因为“记住我的cookie”而登录。即使你不使用<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/remember_me.html">记住我的功能,您可以使用它来检查用户是否已经登录。
  • IS_AUTHENTICATED_FULLY:这类似于IS_AUTHENTICATED_REMEMBERED,但更强大。仅仅因为“记住我cookie”而登录的用户将拥有IS_AUTHENTICATED_REMEMBERED但不会有IS_AUTHENTICATED_FULLY
  • IS_AUTHENTICATED_ANONYMOUSLY所有用户(甚至是匿名用户)有这个-这是有用的白名单保证访问的URL - 有些细节在<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/access_control.html">安全access_control如何工作?
  • is_anonymous仅有的匿名用户由此属性匹配。
  • IS_REMEMBERED仅有的用户使用该用户验证<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/remember_me.html">记住我的功能,(即记住我的cookie)。
  • is_impersonator.:当前用户为<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/impersonating_user.html">冒充此会话中的另一个用户,此属性将匹配。

5.1新版功能:is_anonymousIS_REMEMBEREDis_impersonator.在Symfony 5.1中引入了属性。欧宝娱乐app下载地址

5a)提取用​​户对象<一个类="headerlink" href="#a-fetching-the-user-object" title="¶">¶

经过身份验证后,用户对象可以通过getUser()捷径:

公共函数指数()响应//通常您需要确保用户首先进行身份验证这个美元- >denyAccessUnlessGranted“IS_AUTHENTICATED_FULLY”);//返回您的用户对象,如果用户未被验证,则返回null//使用内联文档告诉你的编辑欧宝体育电话器你确切的用户类/** @var \App\Entity\User $ User */$用户这个美元- >getUser();//调用添加到User类中的任何方法//例如,如果添加GetFirstName()方法,则可以使用该方法。返回响应"嗨,你好"$用户- >getfirstname.());

5b)从服务中获取用户<一个类="headerlink" href="#b-fetching-the-user-from-a-service" title="¶">¶

如果您需要从服务中获取登录用户,请使用欧宝娱乐app下载地址Symfony \ \安全\ \安全核心组件服务:

// src / service / sevelieservice.php/ /……欧宝娱乐app下载地址Symfony \ \安全\ \安全核心组件班级仿照服务私人美元的安全公共函数__构造安全美元的安全//避免在构造函数中调用getUser():auth可能不是//尚未完成。相反,存储整个Security对象。这个美元- >安全美元的安全公共函数SomeMethod.()//返回用户对象,如果没有验证,则返回null$用户这个美元- >安全- >getUser();/ /……

在模板中获取用户<一个类="headerlink" href="#fetch-the-user-in-a-template" title="¶">¶

在Twig模板中,用户对象可通过app.user.变量,因为<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/templates.html">Twig Global App变量

1 2 3
{%如果被授予“IS_AUTHENTICATED_FULLY”%}<p>电子邮件:{{app.user.email}}p>{%endif%}

注销<一个类="headerlink" href="#logging-out" title="¶">¶

要启用注销,请激活登出在防火墙下配置参数:

  • yaml.
    12 3 4 5 6 7 8 9 10 11 12
    #配置/包/安全性.YAML安全#...防火墙主要的#...登出小路app_logout#在注销后在哪里重定向#目标:app_any_route
  • XML.
    12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
    <!--config/packages/security.xml -->< ?encoding="UTF-8"< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”><设置><!--.。。--><防火墙name =“secured_area”><!--.。。-->路径=“app_logout”/>< /防火墙>
  • PHP.
    1 2 3 4 5 6 7 8 9 10 11
    // config / packages / security.php$容器- >loadfromextension.'安全'/ /……'防火墙'=>'secured_area'=>/ /……“注销”=>“路径”=>“app_logout”],],],]);

接下来,你需要为这个URL创建一个路由(但不是控制器):

  • 注释
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
    / / src /控制器/ SecurityController.php名称空间App \控制器欧宝娱乐app下载地址Symfony \包\ FrameworkBundle \ \ AbstractController控制器欧宝娱乐app下载地址Symfony \ Component \ Routing \ Annotation \ Route班级SecurityController扩展AbstractController/ *** @Route(“/注销”,name = " app_logout "方法={“获得”})* /公共函数登出()无效//控制器可以是空的:它永远不会被执行!\例外'别忘了在security.yaml中激活注销');
  • 属性
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    / / src /控制器/ SecurityController.php名称空间App \控制器欧宝娱乐app下载地址Symfony \包\ FrameworkBundle \ \ AbstractController控制器欧宝娱乐app下载地址Symfony \ Component \ Routing \ Annotation \ Route班级SecurityController扩展AbstractController#(路线(“/注销”,名字:“app_logout”,方法:[的]))公共函数登出()//控制器可以是空的:它永远不会被执行!\例外'别忘了在security.yaml中激活注销');
  • yaml.
    1 2 3 4
    #配置/ routes.yamlapp_logout小路/登出方法得到
  • XML.
    1 2 3 4 5 6 7 8 9
    <! -  config / routes.xml  - >< ?encoding="UTF-8"<路线XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/routing”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/routing.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/routing/routing-1.0.xsd”><路线id =“app_logout”路径=“/注销”方法=“获得”/>> < /路线
  • PHP.
    1 2 3 4 5 6 7 8
    / /配置/ routes.php欧宝娱乐app下载地址Symfony \路由\装载机\ \组件配置器\ RoutingConfigurator返回函数RoutingConfigurator$路线$路线- >添加“app_logout”'/登出'- >方法([“得到”])};

那就是它!通过向用户发送到app_logout路线(即至/登出)S欧宝娱乐app下载地址ymfony将取消验证当前用户并将其重定向。

自定义注销<一个类="headerlink" href="#customizing-logout" title="¶">¶

5.1新版功能:LogoutEvent在Symfony 5.1中介绍。欧宝娱乐app下载地址在此版本之前,您必须使用a<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/reference/configuration/security.html">注销成功处理程序自定义注销。

在某些情况下,您需要在注销时执行额外的逻辑(例如,使一些令牌失效)或想自定义注销后发生的事情。在注销,欧宝娱乐app下载地址symfony \ component \ security \ http \ event \ logoutevent是派遣。注册一个<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/event_dispatcher.html">活动侦听器或订阅者执行自定义逻辑。以下信息在事件类中可用:

getToken ()
返回要注销的会话的安全令牌。
getRequest ()
返回当前请求。
getResponse()
返回响应,如果它已由自定义侦听器设置。用setResponse()配置自定义注销响应。

提示

每个安全防火墙都有自己的事件调度程序(security.Event_dispatcher.FireWallName.).注销事件在全局和防火墙调度程序上都被分发。如果希望侦听器只针对特定的防火墙执行,则可以在防火墙调度程序上注册。例如,如果你有api主要的防火墙,使用此配置仅在注销事件上注册主要的防火墙:

  • yaml.
    1 2 3 4 5 6 7 8
    #配置/服务.YAML服务#...App \ EventListener \ CustomLogoutSubscriber标签-名称kernel.event_subscriber.调度员security.event_dispatcher.main
  • XML.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
    <! -  config / services.xml  - >< ?encoding="UTF-8"<容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsd“><服务><!--.。。--><服务id =“app \ eventListener \ umpyLogOutsubscriber”>name =“kernel.event_subscriber”dispacher =“security.event_dispatcher.main”/>< /服务>
  • PHP.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    / /配置/ services.php名称空间欧宝娱乐app下载地址Symfony \ Component \ DependencyIngreation \ Loader \ Configuratorapp \ enceListener \ upidLogoutListenerApp \ EventListener \ CustomLogoutSubscriber欧宝娱乐app下载地址symfony \ component \ security \ http \ event \ logoutevent返回函数ContainerConfigurator$ Configurator.美元服务$ Configurator.- >服务();美元服务- >CustomLogoutSubscriber::班级- >标签'kernel.event_subscriber'“调度”=>“security.event_dispatcher.main”]);};

层次的角色<一个类="headerlink" href="#hierarchical-roles" title="¶">¶

你可以通过创建角色层次结构来定义角色继承规则,而不是为每个用户提供多个角色:

  • yaml.
    1 2 3 4 5 6 7
    #配置/包/安全性.YAML安全#...role_hierarchyROLE_ADMIN角色_User.ROLE_SUPER_ADMINROLE_ADMINrole_allowed_to_switch.
  • XML.
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
    <!--config/packages/security.xml -->< ?encoding="UTF-8"< srv:容器XMLNS =.“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security”xmlns: xsi =“http://www.w3.org/2001/xmlschema-instance”xmlns: srv =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services”xsi: schemaLocation =“http://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services.https://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/services/services-1.0.xsdhttp://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/securityhttps://欧宝娱乐app下载地址www.oldmanjams.com/schema/dic/security/security-1.0.xsd”><设置><!--.。。--><角色id =“角色_admin”>角色_User.> < /角色<角色id =“ROLE_SUPER_ADMIN”>ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH> < /角色
  • PHP.
    12 3 4 5 6 7 8 9 10 11 12
    // config / packages / security.php$容器- >loadfromextension.'安全'/ /……'角色_hierarchy'=>“ROLE_ADMIN”=>'角色_user''角色_super_admin'=>“ROLE_ADMIN”'role_allowed_to_switch'],],]);

用户提供的ROLE_ADMIN角色也会有角色_User.角色。和用户有ROLE_SUPER_ADMIN,会自动拥有ROLE_ADMINrole_allowed_to_switch.角色_User.(继承自ROLE_ADMIN).

要使角色层次结构起作用,不要尝试调用$用户 - > getroles()手动。例如,在从中延伸的控制器中<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/controller.html">基础控制器

// BAD - $user->getRoles()不知道角色层次结构$ hasacess.in_array“ROLE_ADMIN”$用户- >将getRoles());//良好 - 使用正常的安全方法$ hasacess.这个美元- >isGranted“ROLE_ADMIN”);这个美元- >denyAccessUnlessGranted“ROLE_ADMIN”);

笔记

role_hierarchy值是静态 - 例如,您不能将角色层次结构存储在数据库中。如果您需要,请创建自定义<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/voters.html">安全选民查看数据库中的用户角色。

常见问题<一个类="headerlink" href="#frequently-asked-questions" title="¶">¶

我可以有多个防火墙吗?
是的!但这通常是不必要的。每个防火墙就像一个独立的安全系统。所以,除非你有非常不同的身份验证需求,一个防火墙通常工作良好。与<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/guard_authentication.html">防护身份验证,您可以创建各种各样的方式来允许身份验证(例如表单登录、API密钥验证和LDAP),所有这些都在同一防火墙下。
我可以在防火墙之间分享身份验证吗?
是的,但只有一些配置。如果您使用多个防火墙,并且针对一个防火墙进行身份验证,您就可以做到这一点不是自动对任何其他防火墙进行身份验证。不同的防火墙就像不同的安全系统。为此,您必须明确指定相同的指定<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/reference/configuration/security.html">防火墙上下文对于不同的防火墙。但通常为大多数应用程序,有一个主要的防火墙就足够了。
安全似乎不工作在我的错误页面
随着路由的完成安全,404错误页面没有被任何防火墙覆盖。这意味着您不能检查安全性,甚至不能访问这些页面上的用户对象。看到<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/controller/error_pages.html">如何自定义错误页面更多细节。
我的身份验证似乎没有工作:没有错误,但我从未登录过
有时身份验证可能是成功的,但在重定向之后,您将立即注销,因为加载用户来自会议。要查看此问题是否为问题,请检查日志文件(var / log / dev.log)查看日志信息:
无法刷新令牌,因为用户已更改
如果你看到这个,有两个可能的原因。首先,从会话中加载User可能会出现问题。看到<一个类="reference internal" href="//www.oldmanjams.com/doc/5.2/security/user_provider.html">了解如何从会话中刷新用户.其次,如果自上一页刷新以来数据库中的某些用户信息,Symfony将故意以出于安全原因注销用户。欧宝娱乐app下载地址

这项工作包括代码样本,是在a下获得的许可<一个rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/">创作共用BY-SA 3.0许可证。