diff --git a/app/Http/Controllers/Api/Client/V1Controller.php b/app/Http/Controllers/Api/Client/V1Controller.php new file mode 100644 index 00000000..49c65786 --- /dev/null +++ b/app/Http/Controllers/Api/Client/V1Controller.php @@ -0,0 +1,89 @@ +middleware('auth:api', ['except' => ['login', 'register']]); + auth()->shouldUse('api'); + } + + public function login(Request $request) + { + $validator = Validator::make($request->all(), [ + 'email' => 'required|email', + 'password' => 'required|string|min:6', + ]); + + if ($validator->fails()) { + return response()->json($validator->errors(), 422); + } + + if (! $token = auth()->attempt($validator->validated())) { + return response()->json(['error' => 'Unauthorized'], 401); + } + + return $this->createNewToken($token); + } + + public function register(Request $request) + { + $validator = Validator::make($request->all(), [ + 'name' => 'required|string|between:2,100', + 'email' => 'required|string|email|max:100|unique:users', + 'password' => 'required|string|confirmed|min:6', + ]); + + if ($validator->fails()) { + return response()->json($validator->errors()->toJson(), 400); + } + + $user = User::create(array_merge( + $validator->validated(), + ['password' => $request->password] + )); + + return response()->json([ + 'message' => 'User successfully registered', + 'user' => $user, + ], 201); + } + + public function logout() + { + auth()->logout(); + + return response()->json(['message' => 'User successfully signed out']); + } + + public function refresh() + { + return $this->createNewToken(auth()->refresh()); + } + + public function userProfile() + { + return response()->json(auth()->user()); + } + + public function nodeList() + { + return response()->json(auth()->user()->userAccessNodes()); + } + + protected function createNewToken($token) + { + return response()->json([ + 'access_token' => $token, + 'token_type' => 'bearer', + 'expires_in' => auth()->factory()->getTTL() * 60, + 'user' => auth()->user(), + ]); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index bc222214..ab94319a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,11 +10,12 @@ use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Spatie\Permission\Traits\HasRoles; +use Tymon\JWTAuth\Contracts\JWTSubject; /** * 用户信息. */ -class User extends Authenticatable +class User extends Authenticatable implements JWTSubject { use Notifiable, HasRoles; @@ -279,4 +280,14 @@ class User extends Authenticatable { return $this->hasMany(Order::class); } + + public function getJWTIdentifier() + { + return $this->getKey(); + } + + public function getJWTCustomClaims() + { + return []; + } } diff --git a/config/auth.php b/config/auth.php index ba1a4d8c..7d520915 100644 --- a/config/auth.php +++ b/config/auth.php @@ -42,7 +42,7 @@ return [ ], 'api' => [ - 'driver' => 'token', + 'driver' => 'jwt', 'provider' => 'users', 'hash' => false, ], diff --git a/config/jwt.php b/config/jwt.php new file mode 100644 index 00000000..8b7843b6 --- /dev/null +++ b/config/jwt.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + + /* + |-------------------------------------------------------------------------- + | JWT Authentication Secret + |-------------------------------------------------------------------------- + | + | Don't forget to set this in your .env file, as it will be used to sign + | your tokens. A helper command is provided for this: + | `php artisan jwt:secret` + | + | Note: This will be used for Symmetric algorithms only (HMAC), + | since RSA and ECDSA use a private/public key combo (See below). + | + */ + + 'secret' => env('JWT_SECRET'), + + /* + |-------------------------------------------------------------------------- + | JWT Authentication Keys + |-------------------------------------------------------------------------- + | + | The algorithm you are using, will determine whether your tokens are + | signed with a random string (defined in `JWT_SECRET`) or using the + | following public & private keys. + | + | Symmetric Algorithms: + | HS256, HS384 & HS512 will use `JWT_SECRET`. + | + | Asymmetric Algorithms: + | RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below. + | + */ + + 'keys' => [ + + /* + |-------------------------------------------------------------------------- + | Public Key + |-------------------------------------------------------------------------- + | + | A path or resource to your public key. + | + | E.g. 'file://path/to/public/key' + | + */ + + 'public' => env('JWT_PUBLIC_KEY'), + + /* + |-------------------------------------------------------------------------- + | Private Key + |-------------------------------------------------------------------------- + | + | A path or resource to your private key. + | + | E.g. 'file://path/to/private/key' + | + */ + + 'private' => env('JWT_PRIVATE_KEY'), + + /* + |-------------------------------------------------------------------------- + | Passphrase + |-------------------------------------------------------------------------- + | + | The passphrase for your private key. Can be null if none set. + | + */ + + 'passphrase' => env('JWT_PASSPHRASE'), + + ], + + /* + |-------------------------------------------------------------------------- + | JWT time to live + |-------------------------------------------------------------------------- + | + | Specify the length of time (in minutes) that the token will be valid for. + | Defaults to 1 hour. + | + | You can also set this to null, to yield a never expiring token. + | Some people may want this behaviour for e.g. a mobile app. + | This is not particularly recommended, so make sure you have appropriate + | systems in place to revoke the token if necessary. + | Notice: If you set this to null you should remove 'exp' element from 'required_claims' list. + | + */ + + 'ttl' => env('JWT_TTL', 60), + + /* + |-------------------------------------------------------------------------- + | Refresh time to live + |-------------------------------------------------------------------------- + | + | Specify the length of time (in minutes) that the token can be refreshed + | within. I.E. The user can refresh their token within a 2 week window of + | the original token being created until they must re-authenticate. + | Defaults to 2 weeks. + | + | You can also set this to null, to yield an infinite refresh time. + | Some may want this instead of never expiring tokens for e.g. a mobile app. + | This is not particularly recommended, so make sure you have appropriate + | systems in place to revoke the token if necessary. + | + */ + + 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), + + /* + |-------------------------------------------------------------------------- + | JWT hashing algorithm + |-------------------------------------------------------------------------- + | + | Specify the hashing algorithm that will be used to sign the token. + | + | See here: https://github.com/namshi/jose/tree/master/src/Namshi/JOSE/Signer/OpenSSL + | for possible values. + | + */ + + 'algo' => env('JWT_ALGO', 'HS256'), + + /* + |-------------------------------------------------------------------------- + | Required Claims + |-------------------------------------------------------------------------- + | + | Specify the required claims that must exist in any token. + | A TokenInvalidException will be thrown if any of these claims are not + | present in the payload. + | + */ + + 'required_claims' => [ + 'iss', + 'iat', + 'exp', + 'nbf', + 'sub', + 'jti', + ], + + /* + |-------------------------------------------------------------------------- + | Persistent Claims + |-------------------------------------------------------------------------- + | + | Specify the claim keys to be persisted when refreshing a token. + | `sub` and `iat` will automatically be persisted, in + | addition to the these claims. + | + | Note: If a claim does not exist then it will be ignored. + | + */ + + 'persistent_claims' => [ + // 'foo', + // 'bar', + ], + + /* + |-------------------------------------------------------------------------- + | Lock Subject + |-------------------------------------------------------------------------- + | + | This will determine whether a `prv` claim is automatically added to + | the token. The purpose of this is to ensure that if you have multiple + | authentication models e.g. `App\User` & `App\OtherPerson`, then we + | should prevent one authentication request from impersonating another, + | if 2 tokens happen to have the same id across the 2 different models. + | + | Under specific circumstances, you may want to disable this behaviour + | e.g. if you only have one authentication model, then you would save + | a little on token size. + | + */ + + 'lock_subject' => true, + + /* + |-------------------------------------------------------------------------- + | Leeway + |-------------------------------------------------------------------------- + | + | This property gives the jwt timestamp claims some "leeway". + | Meaning that if you have any unavoidable slight clock skew on + | any of your servers then this will afford you some level of cushioning. + | + | This applies to the claims `iat`, `nbf` and `exp`. + | + | Specify in seconds - only if you know you need it. + | + */ + + 'leeway' => env('JWT_LEEWAY', 0), + + /* + |-------------------------------------------------------------------------- + | Blacklist Enabled + |-------------------------------------------------------------------------- + | + | In order to invalidate tokens, you must have the blacklist enabled. + | If you do not want or need this functionality, then set this to false. + | + */ + + 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), + + /* + | ------------------------------------------------------------------------- + | Blacklist Grace Period + | ------------------------------------------------------------------------- + | + | When multiple concurrent requests are made with the same JWT, + | it is possible that some of them fail, due to token regeneration + | on every request. + | + | Set grace period in seconds to prevent parallel request failure. + | + */ + + 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), + + /* + |-------------------------------------------------------------------------- + | Cookies encryption + |-------------------------------------------------------------------------- + | + | By default Laravel encrypt cookies for security reason. + | If you decide to not decrypt cookies, you will have to configure Laravel + | to not encrypt your cookie token by adding its name into the $except + | array available in the middleware "EncryptCookies" provided by Laravel. + | see https://laravel.com/docs/master/responses#cookies-and-encryption + | for details. + | + | Set it to true if you want to decrypt cookies. + | + */ + + 'decrypt_cookies' => false, + + /* + |-------------------------------------------------------------------------- + | Providers + |-------------------------------------------------------------------------- + | + | Specify the various providers used throughout the package. + | + */ + + 'providers' => [ + + /* + |-------------------------------------------------------------------------- + | JWT Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to create and decode the tokens. + | + */ + + 'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class, + + /* + |-------------------------------------------------------------------------- + | Authentication Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to authenticate users. + | + */ + + 'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class, + + /* + |-------------------------------------------------------------------------- + | Storage Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to store tokens in the blacklist. + | + */ + + 'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class, + + ], + +]; diff --git a/routes/api.php b/routes/api.php index 7ca897f3..5e67c1ed 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,7 +1,7 @@ 'Api\WebApi', 'middleware' => ['webApi']], function () { +Route::group(['namespace' => 'Api\WebApi', 'middleware' => 'webApi'], function () { // VNet后端WEBAPI V1版 Route::group(['prefix' => 'web/v1'], function () { Route::get('node/{id}', 'VNetController@getNodeInfo'); // 获取节点信息 @@ -47,3 +47,13 @@ Route::group(['namespace' => 'Api\WebApi', 'middleware' => ['webApi']], function Route::post('trigger/{id}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 }); }); + +// 客户端API +Route::group(['namespace' => 'Api\Client', 'middleware' => 'api', 'prefix' => 'client/v1'], function () { + Route::post('login', 'V1Controller@login'); // 登录 + Route::post('logout', 'V1Controller@logout'); // 退出 + Route::post('refresh', 'V1Controller@refresh'); // 刷新令牌 + Route::post('profile', 'V1Controller@userProfile'); // 获取账户信息 + Route::post('nodeList', 'V1Controller@nodeList'); // 获取账户节点 + Route::post('register', 'V1Controller@register'); // 注册 +});