Everything you wanted to know
about the Django framework

Angular и Django: аутентификация с помощью JWT

Интересует тема, как вызывать функции API Angular 6 и HttpClient? В этом учебном пособии будут показаны некоторые методы построения приложения для микро-блогов, использующего Angular 6 и Django Rest Framework (DRF). В процессе мы узнаем следующее:

  • Как сделать бэкэнд приложение с помощью Django и API Django Rest Framework
  • Создание простого одностраничного приложения Angular 6, которое может запрашивать API
  • Аутентификация пользователей с помощью JSON Web Tokens (JWT)

Готовы? Давайте начнем!

Примечание

Если вы новичек в Django, то можете посмотреть как правильно создавать проект Django и проектировать модели. А также ознакомиться с основами Django Rest Framework.

Также необходимы минимальные знания о вирутальном окружении Python и использовании pip для установки библиотек.

Используемые технологии и версии ПО

  • Angular 6.1
  • RxJS 6.0
  • Angular CLI v6.x
  • Django 2.1
  • Django Rest Framework (DRF)
  • Python 3.5 или выше.
  • Node 8.x или выше

Django и приложения

Django здесь используется в качестве бекэнда нашего разделенного приложения. Он обслуживает запросы API и отдает HTML для Angular на фронтэнде.

Мы уделим больше внимания DRF и его моделям, сериализаторам, видам (viewsets) в продолжении руководства. А пока что создадим простой проект Django с DRF и DRF JWT и установим все это.

pip install Django
pip install djangorestframework djangorestframework-jwt

Здесь мы:

  • установили Django
  • установили Django Rest Framework
  • установили Django Rest Framework JWT — дополнение к DRF, которое предоставляет аутентификацию используя JSON Web Tokens.

Создаем сам проект:

django-admin.py startproject angular_django_example

После создания проекта Django мы получаем базовый файл с настройками проекта. Для активации DRF и его JWT расширения, нужно добавить их в список приложений проекта и настроить.

angular_django_example/settings.py:

INSTALLED_APPS = [
    ...
    'rest_framework',
]
 
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

По умолчанию DRF использует Basic и Session аутентификацию. Параметр DEFAULT_AUTHENTICATION_CLASSES позволяет добавить еще один механизм утентификации: JWT.

Как это все работает?

Basic Auth: логин и пароль пользователя передается при каждом вызове API. Это минимальный уровень безопасности и данные пользователя видны в URL.

Session Auth: требует от пользователя авторизоваться используя возможности проекта перед использованием API. Он более безопасен, чем Basic Auth, но он не очень удобен при использовании одностраничного приложения фреймворка Angular.

JSON Web Tokens: промышленный стандарт для генерации токена, который может передаваться в заголовках HTTP при каждом запросе, аутентифицируя пользователя. Этот механизм мы как раз и будем использовать в проекте.

В дополнение к настройкам самого DRF, JWT также имеет свои настройки конфигурации. Мы обновим два параметра в нашем приложении:

JWT_AUTH = {
    'JWT_ALLOW_REFRESH': True,
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3600),
}

Токен JWT имеет определенное время жизни, после окончания которого становится невалидным. По умолчанию это 5 минут, но мы увеличим его до 1 часа (параметр JWT_EXPIRATION_DELTA), а параметр JWT_ALLOW_REFRESH включает возможность обновления времени жизни токена при каждом запросе.

URL

Кроме дополнений в настройках, нам нужно добавить несколько ссылок для нашего API.

angular_django_example/urls.py:

from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token 

urlpatterns = [
    ... other patterns here ...
    path(r'api-token-auth/', obtain_jwt_token),
    path(r'api-token-refresh/', refresh_jwt_token),
]

Здесь мы указываем ссылки для аутентификации и обновления токена.

Приложение микроблога

После настройки создаем приложение Django и приложение Angular в нем. В терминале выполним команду python manage.py startapp microblog, которая создаст новое приложение Microblog с основными файлами: views.py, models.py.

Теперь сделаем простое представление и шаблон, которые будут обрабатывать серверную часть одностраничного сайта.

microblog/views.py:

from django.shortcuts import render
 
def index(request, path=''):
    """
    Главная страница. Контейнер для одностраничного плижения
    """
    return render(request, 'index.html')

В соответствие с базовыми примерами Django будем использовать два файла шблонов: base.html, содержащий общий код страницы, и index.html — представляющий содержимое основной страницы.

microblog/templates/base.html:

{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Angular, Django Rest Framework и JWT токен</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
</head>
<body>
  <div class="container">
    {% block heading %}
      <h1>Angular, Django Rest Framework и JWT демо</h1>
    {% endblock %}
 
    {% block content %}{% endblock %}
  </div>
</body>
</html>

microblog/templates/index.html:

{% extends "base.html" %}
{% load staticfiles %}
 
{% block content %}
  <p>This is a mini-blog application using a back-end built on Django 2.0 and Django Rest Framework. It illustrates how to create and send JSON Web Token authentication headers with the HTTP requests.</p>
 
  <app-root>Loading the app...</app-root>
 
  <script type="text/javascript" src="{% static 'front-end/runtime.js' %}"></script>
  <script type="text/javascript" src="{% static 'front-end/polyfills.js' %}"></script>
  <script type="text/javascript" src="{% static 'front-end/styles.js' %}"></script>
  <script type="text/javascript" src="{% static 'front-end/vendor.js' %}"></script>
  <script type="text/javascript" src="{% static 'front-end/main.js' %}"></script>
 
{% endblock %}

Приложение Angular

Для установки Angular в проекте Django нам нужно лишь расположить исходный код TypeScript внутри нашего приложения Microblog.

cd microblog
ng new front-end

Эта команда создает начальное приложение Angular в каталоге microblog/front-end. Нас интересуют следующие объекты:

  • app — модули Angular, компонентны и службы
  • angular.json — конфигурация Angular CLI
  • dist — здесь Angular CLI разместит скомпилированные файлы. Мы поменяем это расположение для совместимости с Django.

Здесь у нас появляется первый конфликт между подходами к структуре приложений Django и Angular. Используемый в Django подход к размещению статических ресурсов не будет искать их в microblog/front-end/dist, где размещены собранные JavaScript ресурсы. А ищет их в каждом приложении в каталоге static. Поэтому поменяем конфигурацию в angular.json.

microblog/front-end/angular.json:

{
  ...
  "projects": {
    "ng-demo": {
      ...
      "architect": {
        "build": {
          ...
          "options": {
            "outputPath": "../static/front-end",   <-- меняем размещение статики
...

Теперь, когда мы будем запускать ng build ресурсы будут размещаться по правильному пути.

Содержимое нашего приложения Angular

Оно содержит следующее:

  • microblog/front-end/src/app/app.component.html — шаблон с формой авторизации
  • microblog/front-end/src/app/app.component.ts — основной компонент
  • microblog/front-end/src/app/user.service.ts — служба, управляющая запросы аутентификации

Модуль Angular

Начнем разработку приложения с настройки файла модуля:

microblog/front-end/src/app/app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';    // add this
import { FormsModule } from '@angular/forms';    // add this
import { AppComponent } from './app.component';
import { UserService } from './user.service';    // add this
 
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, FormsModule, HttpClientModule],    // add this
  providers: [UserService],    // add this
  bootstrap: [AppComponent]
})
export class AppModule { }

Он очень похож на модуль Angular по умолчанию. Мы только добавили встроенные HttpClientModule и FormsModule и наш UserService.

Наше приложение очень простое и содержит только один компонент.

microblog/front-end/src/app/app.component.ts:

import {Component, OnInit} from '@angular/core';
import {UserService} from './user.service';
import {throwError} from 'rxjs';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
 
  /**
   * An object representing the user for the login form
   */
  public user: any;
 
  constructor(private _userService: UserService) { }
 
  ngOnInit() {
    this.user = {
      username: '',
      password: ''
    };
  }
 
  login() {
    this._userService.login({'username': this.user.username, 'password': this.user.password});
  }
 
  refreshToken() {
    this._userService.refreshToken();
  }
 
  logout() {
    this._userService.logout();
  }
 
}

Шаблон содержит форму авторизации и сообщение, что пользователь уже авторизован.

microblog/front-end/src/app/app.component.html:

<h2>Log In</h2>
<div class="row" *ngIf="!_userService.token">
  <div class="col-sm-4">
    <label>Username:</label><br />
    <input type="text" name="login-username" [(ngModel)]="user.username">
    <span *ngFor="let error of _userService.errors.username"><br />
    {{ error }}</span></div>
  <div class="col-sm-4">
    <label>Password:</label><br />
    <input type="password" name="login-password" [(ngModel)]="user.password">
    <span *ngFor="let error of _userService.errors.password"><br />
    {{ error }}</span>
  </div>
  <div class="col-sm-4">
    <button (click)="login()" class="btn btn-primary">Log In</button
  </div>
  <div class="col-sm-12">
    <span *ngFor="let error of _userService.errors.non_field_errors">{{ error }}<br /></span>
  </div>
</div>
<div class="row" *ngIf="_userService.token">
  <div class="col-sm-12">You are logged in as {{ _userService.username }}.<br />
    Token Expires: {{ _userService.token_expires }}<br />
    <button (click)="refreshToken()" class="btn btn-primary">Refresh Token</button>
    <button (click)="logout()" class="btn btn-primary">Log Out</button>
  </div>
</div>

Когда пользователь впервые попадает на страницу, он увидит форму авторизации. После успешного прохождения авторизации, он увидит приветственное сообщение и логин. В целях демонстрации также выведем время истечения жизни токена авторизации JWT.

Теперь перейдем к обработке вызовов API авторизации и управления токенами JWT.

microblog/front-end/src/app/user.service.ts:

import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
 
@Injectable()
export class UserService {
 
  private httpOptions: any;
 
  // текущий JWT токен
  public token: string;
 
  // время окончания жизни токена
  public token_expires: Date;
 
  // логин пользователя
  public username: string;
 
  // сообщения об ошибках авторизации
  public errors: any = [];
 
  constructor(private http: HttpClient) {
    this.httpOptions = {
      headers: new HttpHeaders({'Content-Type': 'application/json'})
    };
  }
 
  // используем http.post() для получения токена
  public login(user) {
    this.http.post('/api-token-auth/', JSON.stringify(user), this.httpOptions).subscribe(
      data => {
        this.updateData(data['token']);
      },
      err => {
        this.errors = err['error'];
      }
    );
  }
 
  // обновление JWT токена
  public refreshToken() {
    this.http.post('/api-token-refresh/', JSON.stringify({token: this.token}), this.httpOptions).subscribe(
      data => {
        this.updateData(data['token']);
      },
      err => {
        this.errors = err['error'];
      }
    );
  }
 
  public logout() {
    this.token = null;
    this.token_expires = null;
    this.username = null;
  }
 
  private updateData(token) {
    this.token = token;
    this.errors = [];
 
    // декодирование токена для получения логина и времени жизни токена
    const token_parts = this.token.split(/\./);
    const token_decoded = JSON.parse(window.atob(token_parts[1]));
    this.token_expires = new Date(token_decoded.exp * 1000);
    this.username = token_decoded.username;
  }
 
}

Помните, когда мы устанавливали djangorestframework-jwt, то добавляли два URL в urls.py: "/api-token-auth" и "/api-token-refresh"? Здесь мы используем модуль HttpClient из Angular для отправки POST запроса по этим ссылкам.

Метод login() из UserService отправляет логин и пароль по адресу "/api-token-auth" и получает токен в ответ. Если авторизация не прошла, то мы получим сообщение об ошибке(this.errors), которое будет показано пользователю.

Метод refreshToken() из UserService отправляет токен по адресу "/api-token-refresh" и получает новый токен для пользователя с новым сроком жизни.

Получение времени жизни из JWT токена

Итак, что содержится в самом JWT токене? Мы получим примерно такой токен:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE1MjgwNjg4NDEsInVzZXJfaWQiOjEsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MTUyODA3MjQ0MX0.oxqzt-d2l5Bl473KwObsUetlKS2uOMXn7vqcHcSX5Gg

Он выглядит как случайный набор знаков, но, на самом деле, содержит серию строк base64. Полезные данные находятся между первой и второй точками в формате JSON.

Разобъем строку по точкам и вытащим данные:

const token_parts = this.token.split(/\./);
const token_decoded = JSON.parse(window.atob(token_parts[1]));
console.log(token_decoded);
 
// output:
// {
//   "orig_iat": 1528071221,
//   "exp": 1528074821,
//   "username": "user1",
//   "email": "user1@example.com",
//   "user_id": 2
// }

Здесь мы видим логин, email и id из БД, а также время окончания токена в формате Unix timestamp. Преобразовать его в формат даты JavaScipt можно так: this.token_expires = new Date(token_decoded.exp * 1000); и уже показать пользователю в удобном формате.

Использование токенов в подзапросах API

Перейдем к следующему этапу разработки микроблога. Модели Django и свой API мы обсудим в следующей статье, а пока посмотрим код.

microblog/front-end/app/blog_post.service.ts:

import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {UserService} from './user.service';
 
@Injectable()
export class BlogPostService {
 
  constructor(private http: HttpClient, private _userService: UserService) {
  }
 
  // отправка POST запроса для создания нового сообщения в блоге
  create(post, token) {
    let httpOptions = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'JWT ' + this._userService.token
      })
    };
    return this.http.post('/api/posts', JSON.stringify(post), httpOptions);
  }
 
}

Обратите внимание как мы отправляем JWT токен в заголовке Authorization. Таким образом мы будем "узнавать" пользователя при каждом запросе и позволим Django знать, кто сделал запрос.

Часть 2: Angular и Django: создание приложения микро-блога

Оригинал: https://www.metaltoad.com/blog/angular-api-calls-django-authentication-jwt

Поделитесь с другими:

Представления-классы
(Class-Based Views)

Детальное описание и структура классов Django.

Выпуск Django 3.0

Команда Django рада объявить о выпуске Django 3.0: движение к тому, чтобы сделать Django полностью асинхронным, предоставляя поддержку для работы в качестве приложения ASGI, теперь официально поддерживает MariaDB 10.1 и выше, а также много других новых функций и возможностей.

Выпущены релизы безопасности Django: 2.2.8 и 2.1.15

В соответствии с политикой безопасности, команда Django выпускает Django 2.2.8 и Django 2.1.15. Этот выпуск решает проблему безопасности, подробно описанную ниже. Мы призываем всех пользователей Django обновиться как можно скорее.

Путь от request до response в Джанго

Веб-приложение или веб-сайт вращаются вокруг цикла запрос-ответ, и приложения Django не являются исключением. Но это не просто двухэтапный процесс. Наши приложения Django должны пройти различные стадии, чтобы вернуть конечному пользователю результат. Чтобы лучше понять структуру Django, мы должны понимать, как инициируются запросы и как конечный результат передается конечному пользователю. В статье объясняются различные этапы запросов и используемое там программное обеспечение или код.

Выпущен релиз-кандидат Django 3.0

Кандидат 1 релиза Django 3.0 - это последняя возможность для вас испытать множество новых функций перед выпуском Django 3.0.

Django DetailView - основы использования

Django позволяет создавать приложения очень легко. Если приложение должно быть выпущено быстро и является относительно общим, то эта среда Python идеально подходит для этого. В течение нескольких лет я профессионально работал в этой среде и часто рылся внутри, поэтому знаю почти всё, и сегодня я представлю вам все, что нужно знать, чтобы эффективно использовать универсальный DetailView в Django.

Выпущены исправления Django: 2.2.7, 2.1.14 и 1.11.26

Сегодня команда разработчиков Django выпустила версии 2.2.7, 2.1.14 и 1.11.26 с исправлениями ошибок. Пакет и контрольные суммы доступны на странице загрузок, а также из индекса пакетов Python. Идентификатор ключа PGP, использованный в этом выпуске: Mariusz Felisiak: 2EF56372BA48CD1B.

Как заставить request.is_ajax() работать с JS fetch()

Объект запроса Django request имеет изящный небольшой метод is_ajax(). Он позволяет определить, поступил ли запрос от JS-фреймворка (он же ajax старой школы). Хотя он отлично работает с некоторыми библиотеками JS, включая почтенный jQuery, он не будет работать с современным встроенным в JS fetch().

Практика программирования на Python 3, лекция №5

Лекции о Python 3 от Тимофея Хирьянова при поддержке Московского физико-технического института. Лекция №5.

Практика программирования на Python 3, лекция №4

Лекции о Python 3 от Тимофея Хирьянова при поддержке Московского физико-технического института. Лекция №4.

Практика программирования на Python 3, лекция №3

Лекции о Python 3 от Тимофея Хирьянова при поддержке Московского физико-технического института. Лекция №3.