開発環境
やりたいこと
新規登録、ログイン(username, password)、ログアウト
実装
/var/www/cake
$ bin/cake bake migration CreateUsers \
username:string[30] \
filename:string[255] \
password:string[255] \
role:string[20] \
created modified
config/Migrations/xxxxxxxxx_CreateUsers.php
$table->addColumn('password', 'string', [
'default' => null,
'limit' => 255,
'null' => true, // falseからtrueに変える
]);
/var/www/cake
$ bin/cake migrations migrate
$ bin/cake bake model Users
モデル
src/Model/Table/UsersTable.php
class UsersTable extends Table
{
/**
* Initialize method
*
* @param array $config The configuration for the Table.
* @return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Articles', [
'foreignKey' => 'user_id'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmptyString('id', null, 'create');
$validator
->scalar('username')
->maxLength('username', 30)
->requirePresence('username', 'create')
->notEmptyString('username');
$validator
->scalar('filename')
->maxLength('filename', 255)
->notEmptyString('filename');
$validator
->scalar('password')
->maxLength('password', 255)
->requirePresence('password', 'create')
->notEmptyString('password');
$validator
->scalar('role')
->maxLength('role', 20)
->requirePresence('role', 'create')
->notEmptyString('role');
return $validator;
}
パスワードはハッシュ化して保存
src/Model/Entity/User.php
protected function _setPassword($password)
{
if (mb_strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}
コントローラ
/var/www/cake
$ bin/cake bake controller UsersController
src/Controller/AppController.php
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler', [
'enableBeforeRedirect' => false,
]);
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authorize' => ['Controller'],
// 認証後のリダイレクト先
'loginRedirect' => [
'controller' => 'Articles',
'action' => 'index'
],
// ログアウト後のリダイレクト先
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login'
]
]);
/*
* Enable the following component for recommended CakePHP security settings.
* see https://book.cakephp.org/3/en/controllers/components/security.html
*/
//$this->loadComponent('Security');
}
public function beforeFilter(Event $event)
{
// index, showメソッドではログイン不要
$this->Auth->allow(['index', 'show', 'display']);
}
src/Controller/UsersController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Collection\Collection;
use Cake\Event\Event;
use Cake\Filesystem\Folder;
use Cake\Filesystem\File;
/**
* Users Controller
*
* @property \App\Model\Table\UsersTable $Users
*
* @method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
*/
class UsersController extends AppController
{
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
$this->Auth->allow(['register', 'logout']);
}
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error(__('ログインIDまたはパスワードが一致しませんでした'));
}
}
public function logout()
{
return $this->redirect($this->Auth->logout());
}
/**
* Register method
*
* @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise.
*/
public function register()
{
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->getData());
if ($this->Users->save($user)) {
$this->Flash->success(__('ユーザーが保存されました。'));
return $this->redirect(['controller' => 'articles', 'action' => 'index']);
}
$this->Flash->error(__('ユーザーの保存に失敗しました。 お手数ですが、再度お試しください。'));
}
$this->set(compact('user'));
}
}
ビュー
ログイン画面
src/Template/Users/login.ctp
<div class="form my-4">
<?= $this->Flash->render() ?>
<fieldset class="card p-5">
<?= $this->Form->create(); ?>
<legend class="mb-5"><?= __('ログイン') ?></legend>
<div class="form-outline input text mb-5">
<input type="text" name="username" id="username" value="<?= $this->request->getData('username') ?? ''; ?>" class="form-control" />
<label class="form-label" for="username">ログインID</label>
</div>
<div class="form-outline input text mb-5">
<input type="password" name="password" id="password" value="<?= $this->request->getData('password') ?? ''; ?>" class="form-control" />
<label class="form-label" for="password">パスワード</label>
</div>
<div class="text-center">
<?= $this->Form->button(__('ログイン'), [
'class' => 'btn btn-primary btn-rounded'
]); ?>
</div>
<?= $this->Form->end(); ?>
</fieldset>
</div>
新規登録
src/Template/Users/register.ctp
<div class="form my-4">
<?= $this->Flash->render() ?>
<fieldset class="card p-5">
<?= $this->Form->create(); ?>
<legend class="mb-5"><?= __('新規登録') ?></legend>
<div class="form-outline mb-5">
<input type="text" name="username" id="username" value="<?= $this->request->getData('username') ?? ''; ?>" class="form-control" />
<label class="form-label" for="username">ログインID</label>
</div>
<div class="form-outline mb-5">
<input type="password" name="password" id="password" value="<?= $this->request->getData('password') ?? ''; ?>" class="form-control" />
<label class="form-label" for="password">パスワード</label>
</div>
<div class="col-12 mb-5">
<label class="visually-hidden" for="inlineFormSelectPref">権限</label>
<select name="role" class="form-select">
<option value="admin">管理者</option>
<option value="author">一般</option>
</select>
</div>
<div class="text-center">
<?= $this->Form->button(__('登録'), [
'class' => 'btn btn-primary btn-rounded'
]); ?>
</div>
<?= $this->Form->end(); ?>
</fieldset>
</div>
ロール
- 記事の追加は認証者全員ができる
- 記事の編集、削除は所有者ができる
- Adminロールのユーザは所有者でなくても記事の編集、削除ができる
src/Model/Table/ArticlesTable.php
public function isOwnedBy($articleId, $userId)
{
return $this->exists(['id' => $articleId, 'user_id' => $userId]);
}
src/Controller/AppController.php
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler', [
'enableBeforeRedirect' => false,
]);
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authorize' => ['Controller'],
// 認証後のリダイレクト先
'loginRedirect' => [
'controller' => 'Articles',
'action' => 'index'
],
// ログアウト後のリダイレクト先
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login'
]
]);
/*
* Enable the following component for recommended CakePHP security settings.
* see https://book.cakephp.org/3/en/controllers/components/security.html
*/
//$this->loadComponent('Security');
}
public function beforeFilter(Event $event)
{
// index, showメソッドではログイン不要
$this->Auth->allow(['index', 'show', 'display']);
}
// 追加
public function isAuthorized($user)
{
if (isset($user['role']) && $user['role'] === 'admin') {
return true;
}
return false;
}
src/Controller/ArticlesController.php
class ArticlesController extends AppController
{
public function isAuthorized($user)
{
if (in_array($this->request->getParam('action'), ['add'])) {
return true;
}
if (in_array($this->request->getParam('action'), ['edit', 'delete'])) {
$articleId = (int)$this->request->getParam('pass')[0];
if ($this->Articles->isOwnedBy($articleId, $user['id'])) {
return true;
}
}
return parent::isAuthorized($user);
}
}