安全认证
认证设置(app/config/security.yml)
security:
a) firewalls防火墙(决定如何身份认证:LoginForm, Http or ApiToken), 没有pattern键意味着防火墙应用于所有URL
b) access_control
c) provider 账号数据源(有硬编码的in memory provider, doctrine entity provider及customer provider等)
d) encoders 指定用户类及密码加密算法(除了memory provider用自带user类, 其他需用自己的user类)
1)明文加密 Symfony\Component\Security\Core\User\User: plaintext
2)#bcrypt加密,最好的加密算法, php>=5.5
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt //使用该加密算法后无需再salt随机盐,该算法内部已实现salt
cost: 12
http认证
firewalls下启用http_basic(认证配置)
access_control下添加一条资源的权限约束(如: - { path: ^/admin, roles: ROLE_ADMIN }) (鉴权配置)
检查登录 $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY');
Role, 所有角色名必须以ROLE_开头
鉴权用户会被授予一下一种权限
IS_AUTHENTICATED_ANONYMOUSLY 授予所有用户, 包括那些匿名用户。(常用于设定白名单URL)
IS_AUTHENTICATED_REMEMBERED 授予所有记住我登录用户, 包括以上一类用户。
IS_AUTHENTICATED_FULLY 授予标准登录的用户, 包括以上两类用户
控制器中获取用户对象
$user = $this->get('security.token_storage')->getToken()->getUser(); //未登录返回null
可简写为: $user = $this->getUser(); //不要用此法检测登录
模板中获取用户对象
{{ app.user.username }}
provider从doctrine ORM加载用户对象
a) 创建User实体
需实现UserInterface,并更新表
若需要禁用不活跃用户(需实现高级用户接口AdvancedUserInterface)
实现四个接口:
isAccountNonExpired() 账号过期
isAccountNonLocked() 账号锁定
isCredentialsNonExpired() 密码过期
isEnabled() 账号启用
任意接口返回false, 则账户就无法登录
支持同时通过username和email登录
UserRepository需实现UserProviderInterface接口
在loadUserByUsername()方法中写用户查询逻辑, 并将security:providers:our_db_provider:entity:property这个键剔除
refreshUser()方法可以再次查库用户, 与session中的user比较, 确保没有过期。 或者也可以直接返回参数传入的user。
b) 安全配置
# app/config/security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
providers:
our_db_provider:
entity:
class: AppBundle:User
property: username //指定依赖usename字段去查询用户
firewalls:
default:
provider: our_db_provider //这行没有配置则自动取自定义providers中的第一条
登录 和 注销
# 1安全配置app/config/security.yml
security:
firewalls:
login_firewall: # 登录地址可匿名访问
pattern: ^/login$ #路由匹配时若先匹配到这,则进入该防火墙
anonymous: ~ #允许匿名
secured_area: # 受限地址要求登录
http_basic: ~
pattern: ^/
form_login:
login_path: /login
check_path: /login_check
logout:
path: /logout
target: / #注销后, 重定向地址
# 2路由配置app/config/routing.yml
login_route:
path: /login
defaults: { _controller: AppBundle:Security:login }
login_check:
path: /login_check #不需要控制器
logout:
path: /logout #不需要控制器;访问该路径可注销
配置success_handler键,从而在注销事件后回调一个LogoutSuccessHandlerInterface接口的服务
密码加密
$user = new AppBundle\Entity\User();
$plainPassword = 'ryanpass';
$encoder = $this->container->get('security.password_encoder');
$encoded = $encoder->encodePassword($user, $plainPassword); //使用encoder配置中指定的$user类下的加密算法加密
$user->setPassword($encoded);
密码校验
$encoder->isPasswordValid($user, $plainpassword);
角色继承
# app/config/security.yml
security:
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
无状态身份认证(没有cookie)
当采用证书认证,http认证时可用,此时将不会生成cookie
# app/config/security.ymlsecurity:
firewalls:
main:
http_basic: ~
stateless: true
鉴权方法:
多种拒绝访问方式
鉴权结果 - 未登录用户被重定向到登录页, 登录用户鉴权失败则返回403页面
a) URL鉴权方式,security.yml中配置access_control(简单, 大范围保护一部分的路径,弹性较差)
access_control
可以定义多条,进入匹配的第一条
可以匹配URL, IP, HostName, HttpMethod
可以重定向到https模式上
c) 注解命令式(精细到action的鉴权):
控制器action头部注解:
@Security("has_role('ROLE_ADMIN')")
b) 主动鉴权方式,应用代码中调用security.authorization_checker服务(精细到代码细节的鉴权)
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
内部实现即
if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
throw $this->createAccessDeniedException('Unable to access this page!');
}
返回403响应
d) 模板中鉴权
{% if is_granted('ROLE_ADMIN') %} //模板所在url必须处于防火墙下, 否则抛出异常
Prod环境下, 当在Layout或ErrorPage中时, 需要检查下app.user {% if app.user and is_granted('ROLE_ADMIN') %}
模板中使用表达式
{% if is_granted( expression('
"ROLE_ADMIN" in roles or (user and user.isSuperAdmin())
') ) %}
其他鉴权方法:
Voter(对于细小的限制,又难以集中写到模型的鉴权逻辑,定义一个security voter):
isGranted()被调后, 所有voter都将被调用, 各个voter自行判断是否支持该属性, 若支持则进行鉴权。
voter是对完整授权系统ACL的权限补充方案,简化方案
主要鉴权逻辑集中在vote方法中
可选的传入一个鉴权对象,及被鉴权属性
例子:
namespace AppBundle\Security;
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
use Symfony\Component\Security\Core\User\UserInterface;
class PostVoter extends AbstractVoter
{
const CREATE = 'create';
const EDIT = 'edit';
protected function getSupportedAttributes()
{
return array(self::CREATE, self::EDIT);
}
protected function getSupportedClasses()
{
return array('AppBundle\Entity\Post');
}
protected function isGranted($attribute, $post, $user = null)
{
if (!$user instanceof UserInterface) return false;
if ($attribute === self::CREATE && in_array('ROLE_ADMIN', $user->getRoles(), true)) return true;
if ($attribute === self::EDIT && $user->getEmail() === $post->getAuthorEmail()) return true;
return false;
}
}
注册服务从而 启用该voter
# app/config/services.yml
services:
post_voter:
class: AppBundle\Security\PostVoter
public: false
tags:
- { name: security.voter } #标注为voter
注解命令使用该voter鉴权: * @Security("is_granted('edit', post)")
或者php手工鉴权: $this->denyAccessUnlessGranted('edit', $post);
其他鉴权方法:
ACL(对于通过admin界面来限制用户访问对象时,使用ACL):
保护实体对象,描述对具体资源对象的操作权限
#配置数据库链接 app/config/security.yml
security:
acl:
connection: default
初始化acl表结构
app/console init:acl
缓存系统
1.网关缓存(或称反向代理缓存,http加速器)
如 Varnish, Squid, Symfony反向代理.
(其它缓存类型:浏览器缓存, 代理缓存)
覆写 AppCache::getOptions方法调整配置symfony内嵌网关缓存
2.http缓存头
http响应指定了四个缓存相关头部
Cache-Control
Expires
ETag
Last-Modified
Cache-Control:private,max-age=0,must-revalidate抽象为如下:
$response->setPublic(); $response->setPrivate();
$response->setMaxAge(600); $response->setSharedMaxAge(600);
$response->headers->addCacheControlDirective('must-revalidate', true);
symfony默认设置缓存为私有, 要使用共享缓存必须显式声明为public从而充分利用网关缓存的优点
( FOSHttpCacheBundle提供了基于url匹配和其他请求属性的缓存设置)
http缓存仅为get, head方法工作
3.http过期和校验
过期模型(直接在网关缓存服务器上比较过期):
a.Expires头部
$date = new DateTime();
$date->modify('+600 seconds');
$response->setExpires($date);
最终效果类似于 Expires: Thu, 01 Mar 2011 16:00:00 GMT
b.cache-control头部
$response->setMaxAge(600); //设置全部缓存使用的max-age
$response->setSharedMaxAge(600); //设置仅仅共享缓存使用的s-maxage
最终效果类似于 Cache-Control: max-age=600, s-maxage=600
校验模型(返回304码,Response::isNotModified(Request)方法来校验):
a.ETag头部
比较指纹
b.Last-Modified头部
比较更新时间
为同一URI下的不同资源类型启用缓存:
$response->setVary('Accept-Encoding');
$response->setVary(array('Accept-Encoding', 'User-Agent'));
Response其他方法:
Response->expire(); //标志缓存为过期
Response->setNotModified(); //强制为304响应
Respoinse->setCache(array(
'etag' => $etag,
'last_modified' => $date,
'max_age' => 10,
's_maxage' => 10,
'public' => true,
// 'private' => true,
)); //底层缓存设置方法
4.ESI页面片段缓存(Edge Side Includes)
# app/config/config.yml
framework
esi: { enabled: true } #启用ESI
fragments: { path: /_fragment } #自动生成ESI标签索引Action的路由
模板编辑:
{{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }}
{{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}
render_esi助手还支持参数alt, ignore_errors
$response->setShareMaxAge(60); //嵌入的action可在实现代码中独立指定自己的缓存规则, 这里必须用s-maxage指令, 不能max-age