Symfony2 细节小计3

安全认证

认证设置(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


你可能感兴趣的:(Symfony2 细节小计3)