我正在使用jsonwebtoken身份验证创建MEAN Stack应用程序。我对此表示怀疑,也不确定如何为管理员组织用户角色,因为他将具有特定的角色和特权。
我想为应用程序的admin分配权限,因此只有admin有权查看用户注册选项并登录新用户,而所有其他用户都看不到注册选项。
当前,每个通过身份验证的用户都可以看到注册选项,因此我从注册表单中创建了以下用户,以后仅对应用程序的管理员可见。
我已经在User集合中添加了三个用户,如下所示
{"_id":{"$oid":"5ca6f82d46f3f33fd80069ad"},"email":"admin@edoctor.com","password":"$2b$10$5Ht6/YH.1bzZ9XPWYqDRF.gMb0bYgI3i/fB3YRY/fBX.MeuWrgtqy","isAdmin":true,"__v":{"$numberInt":"0"}}
{"_id":{"$oid":"5ca6f8cc46f3f33fd80069ae"},"email":"doctor1@edoctor.com","password":"$2b$10$jU4KOQ.n5Jm66TnV4U1T1ep1/3sU1Xq7eyndXooBDNVUBePg/BKlC","__v":{"$numberInt":"0"}}
{"_id":{"$oid":"5ca6f8dd46f3f33fd80069af"},"email":"doctor2@edoctor.com","password":"$2b$10$A9wmcSyVBCjPR6LV887gSeg5c5lnzyMQUPBVN61CMHhScsy14C01a","__v":{"$numberInt":"0"}}
{"_id":{"$oid":"5ca6f8e846f3f33fd80069b0"},"email":"doctor3@edoctor.com","password":"$2b$10$KS/HSXOibALmdTGGyWBgX.qxoDMwyk0PoMmULsem2twZgjg3UDkru","__v":{"$numberInt":"0"}}
什么是我的方案更好的解决方案?
isAdmin: true
属性中手动分配给特定用户,然后再分配给*ngIf="user.isAdmin"
,并根据该条件注册选项是否可用,{ resource: { db: "hospital", collection: "users" }, actions: [ "update", "insert" ] }
?代码:
/backend/middleware/check-auth.js
/**
* Check whether the user is authenticated or not
*/
const jwt = require('jsonwebtoken');
// Configuration file that hold environment variables
const config = require('../config/config');
module.exports = (req, res, next) => {
try {
// If user is authenticated, get a token from the incoming request
const token = req.headers.authorization.split(" ")[1];
// Decoded Token
const decodedToken = jwt.verify(token, config.jwt.key);
req.userData = { email: decodedToken.email, userId: decodedToken.userId };
next();
} catch (error) {
// If user is not authenticated
res.status(401).json({
message: 'Authentication failed!'
})
}
}
/backend/controllers/user.js
// Package that offers encryption functionalities (password hashing)
const bcrypt = require('bcrypt');
// Json Web Token package
const jwt = require('jsonwebtoken');
// Require User model
const User = require('../models/user');
// Configuration file that hold environment variables
const config = require('../config/config');
// User signup
exports.userSignup = (req, res, next) => {
// Hash method takes an input password and that is the value I want to hash
bcrypt.hash(req.body.password, 10)
.then(hash => {
// Create a new user
const user = new User({
email: req.body.email,
password: hash
});
// Save the user
user.save()
.then(result => {
// Status 201 - The request has been fulfilled and has resulted in one or more new resources being created
res.status(201).json({
message: 'User created',
result: result
})
})
.catch(err => {
/**
* Status 500 - Internal server error
* The server encountered an unexpected condition that prevented it from fulfilling the request
*/
res.status(500).json({
error: err
})
});
})
}
// User login
exports.userLogin = (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
/**
* Validate whether the credentials are valid, and if it is,
* I want to create validation JSON Web token (validate the user) and logged in the user
*
* First, I want look for a user where the email address in the database matches the email
* address which I attached to the request (entered in the email field)
*/
let fetchedUser;
User.findOne({ email: email })
.then(user => {
// If user does not exist
if (!user) {
// Authentication is denied
return res.status(401).json({
message: 'Authentication failed.'
})
}
fetchedUser = user;
/**
* If user exist, compare the password that the user entered into the login form
* with the password stored in the database
*/
return bcrypt.compare(password, user.password)
})
/**
* Get back the result of compare operation
*
* The result will be true if we did successfully compare, or false if we failed
*/
.then(result => {
if(!result) {
return res.status(401).json({
message: 'Authentication failed.'
});
}
/**
* If result is true, then we know what we have a valid password send by the user, then I will
* create a new Json Web token
*/
const token = jwt.sign(
{ email: fetchedUser.email, userId: fetchedUser._id },
config.jwt.key,
{ expiresIn: "1h" }
);
// Successfuly authenticated
res.status(200).json({
// Return token for user
token: token,
expiresIn: 3600, // 3600 seconds - 1h
isAdmin: fetchedUser.isAdmin,
// Return userId
userId: fetchedUser._id
})
})
.catch(err => {
return res.status(401).json({
message: 'Authentication failed.'
})
})
}
/ backend / models / user
const mongoose = require('mongoose');
// Models are defined through the Schema interface.
const Schema = mongoose.Schema;
/**
* Defining User schema
*
* Mongoose schema is configurator object for a Mongoose model.
* A SchemaType is then a configuration object for an individual property
*
* Each schema maps to a MongoDB collection and defines the shape of the documents within that collection
*/
const userSchema = new Schema({
email: { type: String, required: true },
password: { type: String, required: true }
});
// Compiling Schema into a model
module.exports = mongoose.model('User', userSchema);
/backend/routes/user.js
const express = require('express');
// User controller
const UserController = require('../controllers/user');
// Express router
const router = express.Router();
// Signup route which handle POST request
router.post("/registracija", UserController.userSignup);
// Login route which handle POST request
router.post('/prijava', UserController.userLogin);
// Exports the user model
module.exports = router;
/angular/auth/auth.service.ts
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Router } from "@angular/router";
import { Subject } from "rxjs";
// Authentication Data model
import { AuthData } from "./auth-data.model";
@Injectable({ providedIn: "root" })
export class AuthService {
private isAuthenticated = false;
private token: string;
private tokenTimer: any;
private userId: string;
private isAdmin: boolean;
/**
* I will use Subject from 'rxjs' to push Authentication information
* to components which are interested, because I don't need the token in my other components (except in interceptor),
* I just want to know if user is authneticated or not (true or false)
*/
private authStatusListener = new Subject<boolean>();
constructor(
private http: HttpClient,
private router: Router
) {}
getToken() {
return this.token;
}
getIsAuth() {
return this.isAuthenticated;
}
getUserId() {
return this.userId;
}
getIsAdmin() {
return this.isAdmin;
}
// Return the observable part of authentication status listener, so I can emit new value from other components
getAuthStatusListener() {
return this.authStatusListener.asObservable();
}
// Create a new User
createUser(email: string, password: string) {
const authData: AuthData = { email: email, password: password };
// Use http client to send post request
this.http
.post("http://localhost:3000/api/user/registracija", authData)
.subscribe(
response => console.log(response),
err => console.log(err)
);
}
// Login
login(email: string, password: string ) {
const authData: AuthData = { email: email, password: password };
this.http
.post<{ token: string; expiresIn: number; userId: string, isAdmin: boolean }>(
"http://localhost:3000/api/user/prijava",
authData
)
.subscribe(response => {
/**
* Use JWT token and store it to the future requests (CRUD operations)
*
* - Extract the token from the response
* - Store the token in the service
* - Authentication status is set to true after user is logged in
*/
const token = response.token;
this.token = token;
// If we have a valid token
if (token) {
const expiresInDuration = response.expiresIn;
this.setAuthTimer(expiresInDuration);
// When user logged in, authentication status is true, otherwise it's false
this.isAuthenticated = true;
this.userId = response.userId;
this.isAdmin = response.isAdmin;
this.authStatusListener.next(true);
// Today
const now = new Date();
// Token expiration date in Local Storage
const expirationDate = new Date(
now.getTime() + expiresInDuration * 1000
);
// Save token in Local Storage after user successfuly logged in
this.saveAuthData(token, expirationDate, this.userId);
// After user logged in , redirect user to the home page (list of patients)
this.router.navigate(["/"]);
}
});
}
// Automatically authenticate the user if I got the information for it in Local Storage
autoAuthUser() {
const authInformation = this.getAuthData();
if (!authInformation) {
return;
}
const now = new Date();
const expiresIn = authInformation.expirationDate.getTime() - now.getTime();
// If token expiration date is in the future ( expiration time is greather than current time )
if (expiresIn > 0) {
this.token = authInformation.token;
this.isAuthenticated = true;
this.userId = authInformation.userId;
this.setAuthTimer(expiresIn / 1000);
this.authStatusListener.next(true);
}
}
// Logout
logout() {
// Clear the JWT token
this.token = null;
// Set authentication status to false
this.isAuthenticated = false;
// Push a new authentication value after user logout (false)
this.authStatusListener.next(false);
// When user logout, reset userId
this.userId = null;
// Clear token expiration timer after logout
clearTimeout(this.tokenTimer);
// Clear the Local Storage
this.clearAuthData();
// After logout, redirect the user to the login page
this.router.navigate(["/prijava"]);
}
// Set token expiration timer
setAuthTimer(duration: number) {
this.tokenTimer = setTimeout(() => {
this.logout();
}, duration * 1000);
}
// Save the Token in the Local Storage once the user is authenticated
private saveAuthData(token: string, expirationDate: Date, userId: string) {
localStorage.setItem("token", token);
localStorage.setItem("expiration", expirationDate.toISOString());
localStorage.setItem('userId', userId);
}
// Get the token and expiration date from Local Storage
private getAuthData() {
const token = localStorage.getItem("token");
const expirationDate = localStorage.getItem("expiration");
const userId = localStorage.getItem("userId");
// If we don't have token or expiration date
if (!token || !expirationDate) {
return;
}
return {
token: token,
expirationDate: new Date(expirationDate),
userId: userId
};
}
// Clear Local Storage Token and expiration date
private clearAuthData() {
localStorage.removeItem("token");
localStorage.removeItem("expiration");
localStorage.removeItem("userId");
}
}
/angular/auth/auth.guard.ts
import {
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
Router
} from "@angular/router";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { AuthService } from "./auth.service";
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean | Observable<boolean> | Promise<boolean> {
// Information whether the user is authenticated or not
const isAuth = this.authService.getIsAuth();
// If user is not authenticated
if (!isAuth) {
// Redirect to the login page
this.router.navigate(['/prijava']);
}
// If user is authenticated
return isAuth;
}
}
/angular/auth/auth-data.model.ts
/**
* Defining how Authentication Data should looks like
*
* I will use AuthData for submitting the data to the backend
*/
export interface AuthData {
email: string;
password: string;
}
/angular/auth/login/login.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Subscription } from 'rxjs';
// Authentication service
import { AuthService } from '../auth.service';
@Component({
selector: "app-login",
templateUrl: "./login.component.html",
styleUrls: ["./login.component.scss"]
})
export class LoginComponent implements OnInit, OnDestroy {
// Spinner is loading
public loading = false;
private authStatusSubscription: Subscription;
constructor(
private authService: AuthService
) {}
ngOnInit() {
this.authStatusSubscription = this.authService.getAuthStatusListener().subscribe(
authStatus => {
this.loading = false;
}
)
}
// When user click on Login button
onLogin(form: NgForm) {
// If form is invalid
if (form.invalid) {
return;
}
// Start the spinner
this.loading = true;
// Login the user
this.authService.login(form.value.email, form.value.password);
}
ngOnDestroy() {
this.authStatusSubscription.unsubscribe();
}
}
/angular/auth/signup/signup.component.ts
import { Component, OnInit, OnDestroy } from "@angular/core";
import { NgForm } from "@angular/forms";
import { Subscription } from "rxjs";
// Authentication service
import { AuthService } from "../auth.service";
@Component({
templateUrl: "./signup.component.html",
styleUrls: ["./signup.component.scss"]
})
export class SignupComponent implements OnInit, OnDestroy {
// Spinner is loading
public loading = false;
private authStatusSubscription: Subscription;
constructor(
public authService: AuthService
) {}
ngOnInit() {
this.authStatusSubscription = this.authService.getAuthStatusListener().subscribe(
authStatus => {
this.loading = false;
}
);
}
// When user click on Register button
onSignup(form: NgForm) {
// Check whether the signup form is invalid (make sure that the user enters an email and password)
if (form.invalid) {
return;
}
this.loading = true;
// If form is not invalid, create a new user
this.authService.createUser(form.value.email, form.value.password);
}
ngOnDestroy() {
this.authStatusSubscription.unsubscribe();
}
}