laravel + vue.js 认证

用户模型

如果使用laravel默认的User模型则几乎不需额外的配置,这里使用已存在的数据库进行说明,假设用户表名”admin”,用户字段”username”,”password”。

生成模型

php artisan make:model Admin

或直接复制laravel默认的User模型并修改类名为Admin即可(因为JWT需要Auth,所以需要实现AuthenticatableContract等接口)。

 protected $table = 'admin';
 protected $fillable = ['username', 'password'];
 protected $hidden = ['password'];
 public $timestamps = false;

修改auth配置文件

'model' => App\Admin::class,

laravel jwt-auth 配置

安装

composer require tymon/jwt-auth

新增provider

Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,

新增alias(可选)

'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class

生成配置文件

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

生成secret key

php artisan jwt:generate

修改jwt配置文件

'user' => App\Admin::class,

中间件

不想使用jwt默认的中间件,这里我们自己创建一个

php artisan make:middleware AuthAdmin
class AuthAdmin
{
 protected $auth;
 public function __construct(JWTAuth $auth) {
 $this->auth = $auth;
 }
 public function handle($request, Closure $next)
 {
 if (! $token = $this->auth->setRequest($request)->getToken()) {
 return response('token not provided', 401);
 }
 try {
 $user = $this->auth->authenticate($token);
 } catch (\Exception $e) {
 return response('token not invalid', 401);
 }
 if (! $user) {
 return response('user not found', 401);
 }
 return $next($request);
 }
}

注册中间件

修改Kernel文件,在routeMiddleware中新增

'auth.admin' => \App\Http\Middleware\AuthAdmin::class

路由配置

使用php artisan make:controller 命令生成若干需要的控制器,比如”Admin/PublicController”

编辑routes.php

Route::get('/admin', function() {
 return view('admin.home');
});
Route::controller('admin/public', 'Admin\PublicController');
Route::group(['prefix' => 'admin/api', 'middleware' => 'auth.admin'], function() {
 Route::get('test', function() {
 return 'test';
 });
});

说明:views/admin/home.php为前端入口的视图,admin/api将使用中间件auth.admin,PublicController主要预留写通用的方法,如登陆

home.php

<!DOCTYPE html>
<html lang="zh-cn">
<head>
 <title>Daiba</title>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <link rel="stylesheet" href="/css/app.css">
</head>
<body>
<div id="app"></div>
<script src="/js/admin.js"></script>
</body>
</html>

PublicController.php

class PublicController extends Controller
{
 public function postLogin(Request $request) {
 $data = $request->only(['username', 'password']);
 if ($user = Admin::whereUsername($data['username'])->wherePassword(md5($data['password']))->first()) {
 try {
 if ($token = \JWTAuth::fromUser($user)) {
 return response(compact('token'));
 }
 } catch (\Exception $e) {
 return response('could not create token', 500);
 }
 }
 return response('invalid credentials', 401);
 }
}

前端

gulp && elixir

npm i gulp laravel-elixir laravel-elixir-vueify --save-dev

bootstrap && font-awesome

npm i bootstrap-sass font-awesome --save-dev

vue

npm i vue vue-router vue-resource

编辑gulpfile

var elixir = require('laravel-elixir');
require('laravel-elixir-vueify');
elixir(function (mix) {
 mix
 .sass('app.scss')
 //.copy('./node_modules/bootstrap-sass/assets/fonts/bootstrap', 'public/fonts')
 .copy('./node_modules/font-awesome/fonts', 'public/fonts')
 .browserify('admin/main.js', 'public/js/admin.js')
 ;
});

编辑resources/assets/sass/app.scss

$icon-font-path: '../fonts';
@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
@import "../../../node_modules/font-awesome/scss/font-awesome";

编辑browserify入口文件resources/assets/js/admin/main.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
import App from './views/layouts/app.vue'
import Admin from './views/layouts/admin.vue'
import Index from './views/index.vue'
import Members from './views/members/index.vue'
import News from './views/news/index.vue'
import Orders from './views/orders/index.vue'
import Login from './views/public/login.vue'
Vue.use(VueRouter)
Vue.use(VueResource)
Vue.http.options.root = '/admin/api'
Vue.http.options.error = function(data, status) { // if not auth while request
 401 == status && this.$route.router.go('/login')
}
const router = new VueRouter()
router.map({
 '/login': {
 component: Login,
 gust: true
 },
 '/logout': {
 component: Login,
 gust: true
 },
 '/': {
 component: Admin,
 subRoutes: {
 '/home': {
 component: Index
 },
 '/members': {
 component: Members
 },
 '/news': {
 component: News
 },
 '/orders': {
 component: Orders
 }
 }
 }
})
router.redirect({
 '/': '/home',
 '*': '/home'
})
router.beforeEach((transition) => {
 let token = localStorage.getItem('token')
 if (!transition.to.gust && (!token || token === null)) { // need to auth but token is not set
 transition.redirect('/login')
 }
 Vue.http.headers.common['Authorization'] = 'Bearer ' + token; // common token for vue-resource
 transition.next()
});
router.start(App, '#app')

说明:views下的个文件为组件,路径均为resources/assets/js/views下对应的目录中

layouts/app.vue

<template>
 <router-view></router-view>
</template>
layouts/admin.vue
<template>
 <nav class="navbar navbar-default" role="navigation">
 <div class="container">
 <div class="navbar-header">
 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
 <span class="sr-only">Toggle navigation</span>
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 </button>
 <a class="navbar-brand" href="#"><span class="fa fa-cog fa-spin"></span></a>
 </div>
<div class="collapse navbar-collapse navbar-ex1-collapse">
 <ul class="nav navbar-nav">
 <li>
 <a v-link="{path: '/members'}">Members</a>
 </li>
 <li>
 <a v-link="{path: '/news'}">News</a>
 </li>
 <li>
 <a v-link="{path: '/orders'}">Orders</a>
 </li>
 </ul>
 <p class="navbar-text pull-right">
 <button class="btn btn-danger btn-xs" @click="logout">Logout</button>
 </p>
 </div>
 </div>
 </nav>
 <div class="container">
 <div class="row">
 <div class="col-xs-12">
 <router-view></router-view>
 </div>
 </div>
 </div>
</template>
<script>
 module.exports = {
 methods: {
 logout: function () {
 localStorage.removeItem('token');
 this.$route.router.go('/login');
 }
 }
 }
</script>

public/login.vue

<template>
 <div class="container" style="margin-top: 100px;">
 <div class="row">
 <div class="col-sm-6 col-sm-offset-3">
<form method="post" class="form-horizontal" role="form" @submit.prevent="login">
 <div class="form-group">
 <label for="inputUsername" class="col-sm-2 control-label">Username:</label>
 <div class="col-sm-10">
 <input type="text" name="username" id="inputUsername" class="form-control" v-model="username" />
 </div>
 </div>
 <div class="form-group">
 <label for="inputPassword" class="col-sm-2 control-label">Password:</label>
 <div class="col-sm-10">
 <input type="password" name="password" id="inputPassword" class="form-control" v-model="password" />
 </div>
 </div>
 <div class="form-group">
 <div class="col-sm-10 col-sm-offset-2">
 <button type="submit" class="btn btn-primary btn-block">Login</button>
 </div>
 </div>
 </form>
 </div>
 </div>
 </div>
</template>
<script>
 module.exports = {
 data: function() {
 return {
 username: null,
 password: null
 }
 },
 methods: {
 login: function() {
 let data = {username: this.username, password: this.password};
 this.$http.post('/admin/public/login', data, function(data) {
 localStorage.setItem('token', data.token);
 this.$route.router.go('/');
 }.bind(this));
 }
 }
 }
</script>

说明

1.(main.js)使用router.beforeEach来为每个前端路由注入到钩子,从localStorage中的token判断是否已经登陆,如果已登陆,我们为每个http请求设置Authorization通用参数:

Vue.http.headers.common[‘Authorization’] = ‘Bearer ‘ + token

2.(login.vue)login方法

login: function() {
let data = {username: this.username, password: this.password};
this.$http.post(‘/admin/public/login’, data, function(data) {
localStorage.setItem(‘token’, data.token);
this.$route.router.go(‘/’);
}.bind(this));
}

点击登陆按钮,将向后台发送一个post请求,如果登陆成功将会返回token,存储到本地存储中。

3.(admin.vue)logout方法

logout: function () {
localStorage.removeItem(‘token’);
this.$route.router.go(‘/login’);
}

点击登出按钮,销毁本地token。