JSON 웹 토큰을 사용하여 안전한 웹 응용 프로그램 만들기
132736 단어 angularjavascriptsecuritynode
트위터에 팔로우 해주세요.
자세한 내용은 https://medium.com/@hohanga으로 문의하십시오.
한 페이지의 전단 응용 프로그램과 모바일 응용 프로그램이 그 어느 때보다 인기를 끌면서 전단과 후단이 분리되었다.거의 모든 네트워크 응용 프로그램이 인증을 필요로 하기 때문에, 전방이나 이동 응용 프로그램은 사용자의 신분 데이터를 안전하게 저장할 수 있어야 한다.
JSON 웹 영패(JWT)는 전방 응용 프로그램에 인증 데이터를 저장하는 가장 일반적인 방법 중의 하나이다.Node를 사용합니다.js에는 일부 유행하는 라이브러리에서 JWT를 생성하고 검증할 수 있습니다. 방법은 백엔드에 저장된 키를 검사함으로써 JWT의 진실성을 검사하고 유효기간을 검사하는 것입니다.
영패는 대부분의 응용 프로그램이 이해할 수 있는 표준 형식으로 인코딩된다.일반적으로 사용자 ID, 사용자 이름 등의 사용자 ID 데이터가 포함되어 있습니다. 인증이 성공적으로 완료되면 사용자에게 제공됩니다.
본 논문에서 우리는 JWT를 사용하여 인증 데이터를 저장하는 응용 프로그램을 구축할 것이다.백엔드에서는 노드에서 실행되는 Express 프레임워크를 사용합니다.js, 그리고 영패를 생성하고 검증하는 데 사용되는
jsonwebtoken
패키지입니다.프런트엔드의 경우 Angular 프레임워크와 @auth0/angular-jwt
모듈을 사용하여 Angular를 구현합니다.Google 프로그램에서 사용자가 사용자 이름과 비밀번호를 입력하고 데이터베이스에 있을 때 Google 키는 JWT를 생성하여 사용자에게 되돌려주고 전방 프로그램의 로컬 저장소에 저장합니다.사용자가 백엔드에서 인증된 루트를 방문해야 할 때마다, 그들은 영패를 필요로 한다.백엔드 프로그램에middleware라는 기능이 있습니다. 유효한 영패를 검사하는 데 사용됩니다.유효 영패는 기한이 지나지 않은 영패를 가리키며, 우리의 키에 따라 유효성을 검증합니다.로그인 페이지 외에 등록 페이지와 사용자 자격 증명 설정 페이지도 있습니다.이제 우리는 프런트엔드와 백엔드 프로그램 폴더를 만드는 것부터 시작할 계획을 세웠다.한 사람당 하나씩 만들다.그리고 우리는 백엔드 프로그램을 작성하기 시작했다.우선, 소프트웨어 패키지를 설치하고 Express 프레임워크 코드를 생성합니다.우리는
npx express-generator
을 실행하여 코드를 생성합니다.그리고 우리는 소프트웨어 패키지를 설치해야 한다.우리는 npm i @babel/register express-jwt sequelize bcrypt sequelize-cli dotenv jsonwebtoken body-parser cors
을 운행함으로써 이 점을 실현하였다.@babel/register
에서는 최신 JavaScript 기능을 사용할 수 있습니다.express-jwt
은 JWT를 생성하고 비밀에 따라 이를 검증한다.bcrypt
은 우리의 비밀번호에 대해 해시와 염분을 진행한다.sequelize
은 저희가 묵은 때를 만드는 ORM입니다.cors
에서는 Angular 애플리케이션이 도메인 간 통신을 통해 백엔드와 통신할 수 있습니다.dotenv
에서는 환경 변수를 .env
파일에 저장할 수 있습니다.Express는 JSON 요청을 해결하기 위해 body-parser
이 필요합니다.그리고 데이터베이스 이전을 진행합니다.
npx sequelize-cli init
을 실행하여 데이터베이스에서 대상에 비치는 프레임워크 코드를 생성합니다.그런 다음 다음,npx sequelize-cli model:generate --name User --attributes username:string, password:string, email:string
우리는 또 다른 마이그레이션을 수행했으며 다음을 제안했습니다.'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return Promise.all([
queryInterface.addConstraint(
"Users",
["email"],
{
type: "unique",
name: 'emailUnique'
}),
queryInterface.addConstraint(
"Users",
["userName"],
{
type: "unique",
name: 'userNameUnique'
}),
},
down: (queryInterface, Sequelize) => {
return Promise.all([
queryInterface.removeConstraint(
"Users",
'emailUnique'
),
queryInterface.removeConstraint(
"Users",
'userNameUnique'
),
])
}
};
이것은 우리가 같은 사용자 이름이나 전자메일의 중복 항목이 없다는 것을 확보할 수 있다.이것은
User
모델을 만들고 Users
을 실행한 후에 npx sequelize-cli db:migrate
표를 만들 것이다.코드를 좀 쓰겠습니다.
app.js
에 다음을 추가합니다.require("babel/register");
require("babel-polyfill");
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const user = require('./controllers/userController');
const app = express();
app.use(cors())
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use((req, res, next) => {
res.locals.session = req.session;
next();
});
app.use('/user', user);
app.get('*', (req, res) => {
res.redirect('/home');
});
app.listen((process.env.PORT || 8080), () => {
console.log('App running on port 8080!');
});
필요한 사항:require("babel/register");
require("babel-polyfill");
JavaScript의 최신 기능을 사용합니다.필요한 사항:
require('dotenv').config();
.env
파일의 구성을 읽습니다.이것은 입구점이다.곧
userController
폴더에 controllers
을 만들겠습니다.app.use(‘/user’, user);
은 user
으로 시작하는 모든 URL을 userController
파일로 라우팅합니다.다음으로
userController.js
파일을 추가합니다.const express = require('express');
const bcrypt = require('bcrypt');
const router = express.Router();
const models = require('../models');
const jwt = require('jsonwebtoken');
import { authCheck } from '../middlewares/authCheck';
router.post('/login', async (req, res) => {
const secret = process.env.JWT_SECRET;
const userName = req.body.userName;
const password = req.body.password;
if (!userName || !password) {
return res.send({
error: 'User name and password required'
})
}
const users = await models.User.findAll({
where: {
userName
}
})
const user = users[0];
if (!user) {
res.status(401);
return res.send({
error: 'Invalid username or password'
});
}
try {
const compareRes = await bcrypt.compare(password, user.hashedPassword);
if (compareRes) {
const token = jwt.sign(
{
data: {
userName,
userId: user.id
}
},
secret,
{ expiresIn: 60 * 60 }
);
return res.send({ token });
}
else {
res.status(401);
return res.send({
error: 'Invalid username or password'
});
}
}
catch (ex) {
logger.error(ex);
res.status(401);
return res.send({
error: 'Invalid username or password'
});
}});
router.post('/signup', async (req, res) => {
const userName = req.body.userName;
const email = req.body.email;
const password = req.body.password;
try {
const hashedPassword = await bcrypt.hash(password, 10)
await models.User.create({
userName,
email,
hashedPassword
})
return res.send({ message: 'User created' });
}
catch (ex) {
logger.error(ex);
res.status(400);
return res.send({ error: ex });
}
});
router.put('/updateUser', authCheck, async (req, res) => {
const userName = req.body.userName;
const email = req.body.email;
const token = req.headers.authorization;
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const userId = decoded.data.userId;
try {
await models.User.update({
userName,
email
}, {
where: {
id: userId
}
})
return res.send({ message: 'User created' });
}
catch (ex) {
logger.error(ex);
res.status(400);
return res.send({ error: ex });
}});
router.put('/updatePassword', authCheck, async (req, res) => {
const token = req.headers.authorization;
const password = req.body.password;
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const userId = decoded.data.userId;
try {
const hashedPassword = await bcrypt.hash(password, saltRounds)
await models.User.update({
hashedPassword
}, {
where: {
id: userId
}
})
return res.send({ message: 'User created' });
}
catch (ex) {
logger.error(ex);
res.status(400);
return res.send({ error: ex });
}});
module.exports = router;
login
루트에서 사용자 항목을 검색하고 찾으면 compare
함수 bcrypt
을 사용하여 해시 비밀번호를 검사합니다.둘 다 성공하면 JWT가 생성됩니다.signup
라우팅은 사용자 이름과 암호에 대한 JSON 로드를 가져오고 저장합니다.저장하기 전에 비밀번호에 해시와 소금 얼룩이 있음을 주의하세요.암호는 일반 텍스트로 저장해서는 안 된다.bcrypt.hash
은 두 개의 매개 변수를 받아들인다.첫 번째는 순수한 텍스트 비밀번호이고, 두 번째는salt 라운드수입니다.updatePassword
라우팅은 인증된 라우팅입니다.이것은 영패를 검사할 것입니다. 만약 유효하다면, 계속해서 사용자 ID와 디코딩 영패가 일치하는 User
을 검색해서 사용자 비밀번호를 저장할 것입니다.다음에 우리는 authCheck
중간부품을 추가할 것이다.middlewares
폴더를 만들고 authCheck.js
폴더를 만듭니다.const jwt = require('jsonwebtoken');
const secret = process.env.JWT_SECRET;export const authCheck = (req, res, next) => {
if (req.headers.authorization) {
const token = req.headers.authorization;
jwt.verify(token, secret, (err, decoded) => {
if (err) {
res.send(401);
}
else {
next();
}
});
}
else {
res.send(401);
}
}
코드를 반복하지 않고 검증된 루트의 인증을 검사할 수 있습니다.가져오기 및 참조를 통해 인증된 각 라우팅의 URL과 마스터 라우팅 코드 사이에 if
을 배치합니다..env
의 백엔드 애플리케이션 폴더 루트 파일을 만들었습니다. 여기에는 다음이 포함됩니다.DB_HOST='localhost'
DB_NAME='login-app'
DB_USERNAME='db-username'
DB_PASSWORD='db-password'
JWT_SECRET='secret'
백엔드 프로그램이 완료되었습니다.이제 프런트엔드 Angular 애플리케이션으로 이동합니다.프런트엔드 응용 프로그램 폴더로 전환합니다.Angular 애플리케이션을 구축하려면 Angular CLI가 필요합니다.
설치하려면 노드에서
npm i -g @angular/cli
을 실행하십시오.js 명령 프롬프트.그리고 ng new frontend
을 실행하여 전방 응용 프로그램에 프레임워크 코드를 생성합니다.또한 Angular documentation에 따라
@angular/material
을 설치합니다.그런 다음 기본
app.module.ts
을 다음과 같이 바꿉니다.import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
MatButtonModule,
MatCheckboxModule,
MatInputModule,
MatMenuModule,
MatSidenavModule,
MatToolbarModule,
MatTableModule,
MatDialogModule,
MAT_DIALOG_DEFAULT_OPTIONS,
MatDatepickerModule,
MatSelectModule,
MatCardModule
} from '@angular/material';
import { MatFormFieldModule } from '[@angular/material](http://twitter.com/angular/material)/form-field';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { reducers } from './reducers';
import { FormsModule } from '@angular/forms';
import { TopBarComponent } from './top-bar/top-bar.component';
import { HomePageComponent } from './home-page/home-page.component';
import { LoginPageComponent } from './login-page/login-page.component';
import { SignUpPageComponent } from './sign-up-page/sign-up-page.component';
import { SettingsPageComponent } from './settings-page/settings-page.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { SessionService } from './session.service';
import { HttpReqInterceptor } from './http-req-interceptor';
import { UserService } from './user.service';
import { CapitalizePipe } from './capitalize.pipe';
@NgModule({
declarations: [
AppComponent,
TopBarComponent,
HomePageComponent,
LoginPageComponent,
SignUpPageComponent,
SettingsPageComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
StoreModule.forRoot(reducers),
BrowserAnimationsModule,
MatButtonModule,
MatCheckboxModule,
MatFormFieldModule,
MatInputModule,
MatMenuModule,
MatSidenavModule,
MatToolbarModule,
MatTableModule,
FormsModule,
HttpClientModule,
MatDialogModule,
MatDatepickerModule,
MatMomentDateModule,
MatSelectModule,
MatCardModule,
NgxMaterialTimepickerModule
],
providers: [
SessionService,
{
provide: HTTP_INTERCEPTORS,
useClass: HttpReqInterceptor,
multi: true
},
UserService,
{
provide: MAT_DIALOG_DEFAULT_OPTIONS,
useValue: { hasBackdrop: false }
},
],
bootstrap: [AppComponent],
})
export class AppModule { }
이것은 우리가 추가할 모든 의존 항목과 구성 요소를 만들 것입니다.토큰을 사용하여 인증된 요청을 단순화하기 위해 http-req-interceptor.ts
을 생성하여 HTTP 요청 차단기를 만들었습니다.import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpResponse,
HttpErrorResponse,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../environments/environment'
import { map, filter, tap } from 'rxjs/operators';
import { Router } from '@angular/router';
@Injectable()
export class HttpReqInterceptor implements HttpInterceptor {
constructor(
public router: Router
) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let modifiedReq = req.clone({});
if (localStorage.getItem('token')) {
modifiedReq = modifiedReq.clone({
setHeaders: {
authorization: localStorage.getItem('token')
}
});
}
return next.handle(modifiedReq).pipe(tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {}
});
}
}
로그인 요청을 제외하고 우리는 모든 요청에 영패를 설치했다.environments/environment.ts
에는 다음과 같은 기능이 있습니다.export const environment = {
production: false,
apiUrl: 'http://localhost:8080'
};
이것은 우리 백엔드의 URL을 가리킨다.지금 우리는 사이드 네비게이션을 해야 한다.우리는 측면 내비게이션의 상태를 저장하기 위해
@ngrx/store
을 추가하고 싶습니다.우리는 npm install @ngrx/store --save
을 운행해서 이 가방을 설치한다.우리는 ng add @ngrx/store
을 운행함으로써 감속기를 추가했다.flux store에서 상태를 중앙 집중식으로 설정하기 위해
menu-reducers.ts
을 추가했습니다.const TOGGLE_MENU = 'TOGGLE_MENU';function menuReducer(state, action) {
switch (action.type) {
case TOGGLE_MENU:
state = action.payload;
return state;
default:
return state
}
}
export { menuReducer, TOGGLE_MENU };
Reducer를 응용 프로그램의 다른 섹션에 링크하려면 index.ts
에 다음을 입력합니다.import { menuReducer } from './menu-reducer';
import { tweetsReducer } from './tweets-reducer';export const reducers = {
menu: menuReducer,
};
재료 디자인의 모양새를 보려면 style.css
에 다음을 추가하십시오./* You can add global styles to this file, and also import other style files */
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
body {
font-family: "Roboto", sans-serif;
margin: 0;
}
form {
mat-form-field {
width: 95vw;
margin: 0 auto;
}
}
.center {
text-align: center;
}
head
의 index.html
태그 사이에 다음을 추가합니다.<link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
그리고 우리는 ng g service user
을 실행함으로써 사용자 함수에 서비스를 추가합니다.user.service.ts
이 생성됩니다.그리고 우리는 다음과 같이 말했다.import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { Router } from '[@angular/router](http://twitter.com/angular/router)';
import { JwtHelperService } from "@auth0/angular-jwt";
const helper = new JwtHelperService();
@Injectable({
providedIn: 'root'
})
export class UserService { constructor(
private http: HttpClient,
private router: Router
) { }
signUp(data) {
return this.http.post(`${environment.apiUrl}/user/signup`, data);
}
updateUser(data) {
return this.http.put(`${environment.apiUrl}/user/updateUser`, data);
}
updatePassword(data) {
return this.http.put(`${environment.apiUrl}/user/updatePassword`, data);
}
login(data) {
return this.http.post(`${environment.apiUrl}/user/login`, data);
}
logOut() {
localStorage.clear();
this.router.navigate(['/']);
}
isAuthenticated() {
try {
const token = localStorage.getItem('token');
const decodedToken = helper.decodeToken(token);
const isExpired = helper.isTokenExpired(token);
return !!decodedToken && !isExpired;
}
catch (ex) {
return false;
}
}}
토큰의 유효성을 검사하는 데 사용되는 isAuthenticated
함수를 제외하고 각 함수는 HTTP 요청에 가입할 것을 요청합니다.웹 페이지를 볼 수 있을 때 아래에 열거한 웹 주소로 이동할 수 있도록 프로그램의 루트가 필요합니다.
app-routing.module.ts
에 제시된 내용은 다음과 같습니다.import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomePageComponent } from './home-page/home-page.component';
import { LoginPageComponent } from './login-page/login-page.component';
import { SignUpPageComponent } from './sign-up-page/sign-up-page.component';
import { TweetsPageComponent } from './tweets-page/tweets-page.component';
import { SettingsPageComponent } from './settings-page/settings-page.component';
import { PasswordResetRequestPageComponent } from './password-reset-request-page/password-reset-request-page.component';
import { PasswordResetPageComponent } from './password-reset-page/password-reset-page.component';
import { IsAuthenticatedGuard } from './is-authenticated.guard';const routes: Routes = [
{ path: 'login', component: LoginPageComponent },
{ path: 'signup', component: SignUpPageComponent },
{ path: 'settings', component: SettingsPageComponent, canActivate: [IsAuthenticatedGuard] },
{ path: '**', component: HomePageComponent }];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
이제 위 파일에서 참조하는 부품을 생성합니다.우리는 사람들이 영패가 없는 상황에서 신분 검증을 거친 경로를 방문하는 것을 방지해야 하기 때문에 수위가 필요하다.우리는 ng g guard isAuthenticated
을 운행함으로써 이 점을 실현하였다.이는 is-authenticated.guard.ts
을 생성합니다.is-authenticated.guard.ts
에 입력한 내용은 다음과 같습니다.import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class IsAuthenticatedGuard implements CanActivate {
constructor(
private userService: UserService,
private router: Router
) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
const isAuthenticated = this.userService.isAuthenticated();
if (!isAuthenticated) {
localStorage.clear();
this.router.navigate(['/']);
}
return isAuthenticated;
}}
이것은 isAuthenticated
의 UserService
함수를 사용하여 유효한 영패를 검사할 것이다.만약 무효라면, 우리는 그것을 지우고 홈 페이지로 다시 지정합니다.현재, 우리는 로그인에 사용할 폼을 만들고, 로그인 후의 사용자 데이터를 설정합니다.
ng g component homePage
, ng g component loginPage
, ng g component topBar
, ng g component signUpPage
및 ng g component settingsPage
을 실행하고 있습니다.이것들은 창과 위쪽 표시줄 구성 요소에 사용됩니다.홈 페이지는 정적 페이지일 뿐입니다.우리는 이전 단락의 명령을 실행한 후에
home-page.component.html
과 home-page.component.ts
을 생성해야 한다.home-page.component.html
에 제시된 내용은 다음과 같습니다.<div class="center">
<h1>Home Page</h1>
</div>
이제 로그인 페이지를 만듭니다.login-page.component.ts
에 제시된 내용은 다음과 같습니다.<div class="center">
<h1>Log In</h1>
</div>
<form #loginForm='ngForm' (ngSubmit)='login(loginForm)'>
<mat-form-field>
<input matInput placeholder="Username" required #userName='ngModel' name='userName'
[(ngModel)]='loginData.userName'>
<mat-error *ngIf="userName.invalid && (userName.dirty || userName.touched)">
<div *ngIf="userName.errors.required">
Username is required.
</div>
</mat-error>
</mat-form-field>
<br>
<mat-form-field>
<input matInput placeholder="Password" type='password' required #password='ngModel' name='password'
[(ngModel)]='loginData.password'>
<mat-error *ngIf="password.invalid && (password.dirty || password.touched)">
<div *ngIf="password.errors.required">
Password is required.
</div>
</mat-error>
</mat-form-field>
<br>
<button mat-raised-button type='submit'>Log In</button>
<a mat-raised-button routerLink='/passwordResetRequest'>Reset Password</a>
</form>
login-page.component.ts
에 제시된 내용은 다음과 같습니다.import { Component, OnInit } from '@angular/core';
import { UserService } from '../user.service';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
[@Component](http://twitter.com/Component)({
selector: 'app-login-page',
templateUrl: './login-page.component.html',
styleUrls: ['./login-page.component.scss']
})
export class LoginPageComponent implements OnInit {
loginData: any = <any>{};
constructor(
private userService: UserService,
private router: Router
) { }
ngOnInit() {
}
login(loginForm: NgForm) {
if (loginForm.invalid) {
return;
}
this.userService.login(this.loginData)
.subscribe((res: any) => {
localStorage.setItem('token', res.token);
this.router.navigate(['/settings']);
}, err => {
alert('Invalid username or password');
})
}
}
모든 필드가 채워져 있는지 확인합니다.만약 그렇다면 로그인 데이터를 보내고 인증에 성공하면 영패를 로컬 저장소에 저장합니다.그렇지 않으면 오류 경고가 표시됩니다.그리고 등록 페이지
sign-up-page.component.html
에<div class="center">
<h1>Sign Up</h1>
</div>
<br>
<form #signUpForm='ngForm' (ngSubmit)='signUp(signUpForm)'>
<mat-form-field>
<input matInput placeholder="Username" required #userName='ngModel' name='userName'
[(ngModel)]='signUpData.userName'>
<mat-error *ngIf="userName.invalid && (userName.dirty || userName.touched)">
<div *ngIf="userName.errors.required">
Username is required.
</div>
</mat-error>
</mat-form-field>
<br>
<mat-form-field>
<input pattern="\S+@\S+\.\S+" matInput placeholder="Email" required #email='ngModel' name='email'
[(ngModel)]='signUpData.email'>
<mat-error *ngIf="email.invalid && (email.dirty || email.touched)">
<div *ngIf="email.errors.required">
Email is required.
</div>
<div *ngIf="email.invalid">
Email is invalid.
</div>
</mat-error>
</mat-form-field>
<br>
<mat-form-field>
<input matInput placeholder="Password" type='password' required #password='ngModel' name='password'
[(ngModel)]='signUpData.password'>
<mat-error *ngIf="password.invalid && (password.dirty || password.touched)">
<div *ngIf="password.errors.required">
Password is required.
</div>
</mat-error>
</mat-form-field>
<br>
<button mat-raised-button type='submit'>Sign Up</button>
</form>
sign-up-page.component.ts
년에 우리는 다음과 같이 주장했다.import { Component, OnInit } from '[@angular/core](http://twitter.com/angular/core)';
import { UserService } from '../user.service';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import _ from 'lodash';
@Component({
selector: 'app-sign-up-page',
templateUrl: './sign-up-page.component.html',
styleUrls: ['./sign-up-page.component.scss']
})
export class SignUpPageComponent implements OnInit {
signUpData: any = <any>{}; constructor(
private userService: UserService,
private router: Router
) { }
ngOnInit() {
}
signUp(signUpForm: NgForm) {
if (signUpForm.invalid) {
return;
}
this.userService.signUp(this.signUpData)
.subscribe(res => {
this.login();
}, err => {
console.log(err);
if (
_.has(err, 'error.error.errors') &&
Array.isArray(err.error.error.errors) &&
err.error.error.errors.length > 0
) {
alert(err.error.error.errors[0].message);
}
})
}
login() {
this.userService.login(this.signUpData)
.subscribe((res: any) => {
localStorage.setItem('token', res.token);
this.router.navigate(['/tweets']);
})
}
}
이 두 단락의 코드는 등록 데이터를 가져와 백엔드에 보내고, 모두 유효하면 백엔드에 파일을 저장합니다.마찬가지로
settings-page.component.html
에서는<div class="center">
<h1>Settings</h1>
</div>
<br>
<div>
<h2>Update User Info</h2>
</div>
<br>
<form #updateUserForm='ngForm' (ngSubmit)='updateUser(updateUserForm)'>
<mat-form-field>
<input matInput placeholder="Username" required #userName='ngModel' name='userName'
[(ngModel)]='updateUserData.userName'>
<mat-error *ngIf="userName.invalid && (userName.dirty || userName.touched)">
<div *ngIf="userName.errors.required">
Username is required.
</div>
</mat-error>
</mat-form-field>
<br>
<mat-form-field>
<input pattern="\S+@\S+\.\S+" matInput placeholder="Email" required #email='ngModel' name='email'
[(ngModel)]='updateUserData.email'>
<mat-error *ngIf="email.invalid && (email.dirty || email.touched)">
<div *ngIf="email.errors.required">
Email is required.
</div>
<div *ngIf="email.invalid">
Email is invalid.
</div>
</mat-error>
</mat-form-field>
<br>
<button mat-raised-button type='submit'>Update User Info</button>
</form>
<br><div>
<h2>Update Password</h2>
</div>
<br>
<form #updatePasswordForm='ngForm' (ngSubmit)='updatePassword(updatePasswordForm)'>
<mat-form-field>
<input matInput placeholder="Password" type='password' required #password='ngModel' name='password'
[(ngModel)]='updatePasswordData.password'>
<mat-error *ngIf="password.invalid && (password.dirty || password.touched)">
<div *ngIf="password.errors.required">
Password is required.
</div>
</mat-error>
</mat-form-field>
<br>
<button mat-raised-button type='submit'>Update Password</button>
</form>
<br>
<div *ngIf='currentTwitterUser.id' class="title">
<h2>Connected to Twitter Account</h2>
<div>
<button mat-raised-button (click)='redirectToTwitter()'>Connect to Different Twitter Account</button>
</div>
</div>
<div *ngIf='!currentTwitterUser.id' class="title">
<h2>Not Connected to Twitter Account</h2>
<div>
<button mat-raised-button (click)='redirectToTwitter()'>Connect to Twitter Account</button>
</div>
</div>
settings-page.component.html
에 제시된 내용은 다음과 같습니다.import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { SessionService } from '../session.service';
import { NgForm } from '@angular/forms';
import { UserService } from '../user.service';
@Component({
selector: 'app-settings-page',
templateUrl: './settings-page.component.html',
styleUrls: ['./settings-page.component.scss']
})
export class SettingsPageComponent implements OnInit {
currentTwitterUser: any = <any>{};
elements: any[] = [];
displayedColumns: string[] = ['key', 'value'];
updateUserData: any = <any>{};
updatePasswordData: any = <any>{}; constructor(
private sessionService: SessionService,
private userService: UserService,
private router: Router
) {
}
ngOnInit() {
}
updateUser(updateUserForm: NgForm) {
if (updateUserForm.invalid) {
return;
}
this.userService.updateUser(this.updateUserData)
.subscribe(res => {
alert('Updated user info successful.');
}, err => {
alert('Updated user info failed.');
})
}
updatePassword(updatePasswordForm: NgForm) {
if (updatePasswordForm.invalid) {
return;
}
this.userService.updatePassword(this.updatePasswordData)
.subscribe(res => {
alert('Updated password successful.');
}, err => {
alert('Updated password failed.');
})
}
}
다른 페이지와 유사하게 사용자 데이터와 비밀번호를 변경하는 요청 부하를 백엔드로 보냅니다.마지막으로
top-bar.component.html
에는 다음과 같은 내용이 추가되었습니다.<mat-toolbar>
<a (click)='toggleMenu()' class="menu-button">
<i class="material-icons">
menu
</i>
</a>
Twitter Automator
</mat-toolbar>
top-bar.component.ts
년:import { Component, OnInit } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { TOGGLE_MENU } from '../reducers/menu-reducer';
@Component({
selector: 'app-top-bar',
templateUrl: './top-bar.component.html',
styleUrls: ['./top-bar.component.scss']
})
export class TopBarComponent implements OnInit {
menuOpen: boolean; constructor(
private store: Store<any>
) {
store.pipe(select('menu'))
.subscribe(menuOpen => {
this.menuOpen = menuOpen;
})
}
ngOnInit() {
}
toggleMenu() {
this.store.dispatch({ type: TOGGLE_MENU, payload: !this.menuOpen });
}
}
app.component.ts
에 제시된 내용은 다음과 같습니다.import { Component, HostListener } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { TOGGLE_MENU } from './reducers/menu-reducer';
import { UserService } from './user.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
menuOpen: boolean;
constructor(
private store: Store<any>,
private userService: UserService
) {
store.pipe(select('menu'))
.subscribe(menuOpen => {
this.menuOpen = menuOpen;
})
}
isAuthenticated() {
return this.userService.isAuthenticated();
}
@HostListener('document:click', ['$event'])
public onClick(event) {
const isOutside = !event.target.className.includes("menu-button") &&
!event.target.className.includes("material-icons") &&
!event.target.className.includes("mat-drawer-inner-container")
if (isOutside) {
this.menuOpen = false;
this.store.dispatch({ type: TOGGLE_MENU, payload: this.menuOpen });
}
}
logOut() {
this.userService.logOut();
}
}
app.component.html
년에 우리는 다음과 같이 말했다.<mat-sidenav-container class="example-container">
<mat-sidenav mode="side" [opened]='menuOpen'>
<ul>
<li>
<b>
Twitter Automator
</b>
</li>
<li>
<a routerLink='/login' *ngIf='!isAuthenticated()'>Log In</a>
</li>
<li>
<a routerLink='/signup' *ngIf='!isAuthenticated()'>Sign Up</a>
</li>
<li>
<a href='#' (click)='logOut()' *ngIf='isAuthenticated()'>Log Out</a>
</li>
<li>
<a routerLink='/tweets' *ngIf='isAuthenticated()'>Tweets</a>
</li>
<li>
<a routerLink='/settings' *ngIf='isAuthenticated()'>Settings</a>
</li>
</ul>
</mat-sidenav>
<mat-sidenav-content>
<app-top-bar></app-top-bar>
<div id='content'>
<router-outlet></router-outlet>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
이렇게 하면 측면 탐색 메뉴를 전환할 수 있습니다.다음을 참조하십시오.@HostListener('document:click', ['$event'])
public onClick(event) {
const isOutside = !event.target.className.includes("menu-button") &&
!event.target.className.includes("material-icons") &&
!event.target.className.includes("mat-drawer-inner-container")
if (isOutside) {
this.menuOpen = false;
this.store.dispatch({ type: TOGGLE_MENU, payload: this.menuOpen });
}
}
측면 탐색의 찰칵 소리를 감지합니다.만약 우리가 외부를 클릭한다면, 이러한 종류의 요소를 클릭하지 않으면, 우리는 메뉴를 닫을 것이다.this.store.dispatch
은 닫힌 상태를 모든 구성 요소에 전파합니다.
Reference
이 문제에 관하여(JSON 웹 토큰을 사용하여 안전한 웹 응용 프로그램 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/aumayeung/use-json-web-tokens-to-make-a-secure-web-app-4ff8텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)