Boopack with Spring Security part 1/5

Phai Panda
6 min readJul 26, 2019

--

สร้างโปรเจกต์ชื่อ Boopack ร้านค้าออนไลน์ขายหนังสือ ให้ backend ใช้ Spring ส่วน frontend ใช้ Angular

http://www.freepik.com Designed by katemangostar / Freepik

โค้ดทั้งหมดบน GitHub

Project Description

โปรเจกต์นี้จะประยุกต์ใช้ความรู้ที่มี backend เลือก Spring Boot 2 ส่วน frontend เลือก Angular 8 อยากสร้างร้านขายหนังสือออนไลน์เล็กๆที่ประกอบไปด้วย

  • หน้าเว็บขายหนังสือ (ตอนนี้เอาแค่หนังสือก่อน)
  • หน้าเว็บของ Admin เพื่อดูสถิติการซื้อขายหนังสือของลูกค้า (มี Admin ได้หลายคน)
  • ลูกค้าสมัครสมาชิกด้วย email เป็นสำคัญ และต้องยืนยัน email
  • เรื่อง payment ตัดไปก่อน หลังจากลูกค้ายืนยัน email ถูกต้อง ลูกค้าจะได้รับเงินจาก wallet ของร้านค้าจำนวนหนึ่งไว้ใช้สอย

เท่านี้ก่อน มากไปอาจทำไม่จบใน 5 parts

Tools

*** Init Backend ***

Google ค้นไปว่า spring init

https://start.spring.io/
  • Project: Gradle Project
  • Language: Java
  • Spring Boot: 2.1.6
  • Group: com.pros
  • Artifact: boopack
  • Name (Option): boopack
  • Description (Option): -
  • Package Name (Option): com.pros.boopack
  • Packaging: Jar
  • Java: 8 (1.8)
  • Dependencies: Spring Web Starter, Spring Data JPA, Spring Security

โหลดแล้วเปิดด้วย IntelliJ แล้วรอ Gradle ทำงาน

รันโปรแกรม

Description:Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.Reason: Failed to determine a suitable driver class

คือผมยังไม่มีฐานข้อมูล

ฐานข้อมูลให้เลือกเป็นประเภท SQL ตามใจชอบครับ ส่วนผมเลือก Microsoft SQL Server 2017 ติดตั้งโดย Docker

docker images
microsoft/mssql-server-linux latest 7 months ago 1.35GB

สำหรับเพื่อนที่โหลด image แล้ว เรียก Microsoft SQL Server 2017 ทำงาน

docker run --name mssqlserver2017 -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password1234' -p 1433:1433 -d microsoft/mssql-server-linux

ดูความพร้อมของฐานข้อมูล

docker ps -a

หา Microsoft SQL Server Driver แล้วบอกมันกับ build.gradle

runtime 'com.microsoft.sqlserver:mssql-jdbc'

เขียน configuration บอกกับ application.properties

ดูเพิ่มเติมเกี่ยวกับ Hibernate Dialect

สร้างฐานข้อมูล

CREATE DATABASE boopackCOLLATE Thai_CI_AS;

รันโปรแกรม

สำเร็จไปหนึ่ง

สังเกตตรงนี้คือผลของ Spring Security เมื่อผมได้บอกกับ Gradle ว่าต้องการใช้ spring boot starter security มันจะสร้างรหัสผ่านอย่างนี้ให้ (รันแต่ละครั้งก็ต่างกันไป)

Using generated security password: 0c10d317-7e1e-4f70-9248-0e7dd14af3cc

ด้วยผลของ spring boot starter web โปรเจกต์นี้จะมี Tomcat Server แถมมาให้เลย ทำงานกับ port หมายเลข 8080

เปิด Browser ทดสอบ

http://localhost:8080

จะได้หน้าแรก (login) มาให้กรอก username กับ password ครับ

มันสร้างให้เลย

username: user
password: ตามที่มันสร้างเลย

เอกสารอ้างอิง

*** Init Frontend ***

เรื่องของ backend กับ database เรียบร้อย ทีนี้มาถึง Angular 8

ตรวจสอบว่าพร้อม

ng --version
ผมพร้อมสำหรับ Angular 8

นิดหน่อยก่อนจะเริ่มต้น backend ผมตั้งชื่อโปรเจกต์ว่า boopack ส่วน frontend ก็จะชื่อ boopack ไม่ต่างกัน ดังนั้นผมจะแยก folder ระหว่าง backend กับ frontend แทน

ng new boopack

เข้าในในโปรเจกต์ boopack (Angular project) นี้แล้วรัน

npm start

มันจะทำงานบน port 4200 เปิด Browser ทดสอบ

http://localhost:4200
เวิร์ก

เพื่อนจะเห็นว่าทั้ง backend และ frontend ใช้ port ต่างกัน (โดย default) กล่าวคือ

  • backend ใช้ 8080
  • frontend ใช้ 4200

ผมต้องการให้ frontend คุยกับ backend ได้ โดยให้ backend ทำตัวเป็น endpoint ในขั้นต้นนี้เมื่อผมบอกกับ Angular ว่า

http://localhost:4200/login

จะต้องหมายถึง /login ที่ฝั่ง backend

Angular สร้างหน้า Login

UI เปล่าๆแบบ HTML เลยยังไม่สวยครับ ขอใช้ Materialize เป็น Theme นะครับ

ติดตั้งในโปเจกต์ boopack frontend

npm install materialize-css@next

กำหนดค่าใน angular.json ให้อ่าน CSS และ JS ของ Materialize ในส่วนของ

projects/architect/build/options/stylesprojects/architect/build/options/scripts

สร้าง Login Component

ng generate component login

สร้าง App Routing (ถ้ายังไม่มีไฟล์นี้)

ng generate module app-routing --flat --module=app

อ่านเพิ่มเติม

จะได้ไฟล์ชื่อ app-routing.module.ts เพิ่มรายละเอียดลงไป

แก้ไข app.component.html ให้เหลือเพียงเท่านี้

<router-outlet></router-outlet>

ทดสอบเรียก

เวิร์ก

ปั้นหน้า Login

เพื่อนคนไหนอยากเล่นกับ icons (Material Icons)

อ่านเพิ่มเติม

ตามคำแนะนำให้ clone มาก่อน

git clone https://github.com/google/material-design-icons.git

จากนั้นเข้าไปที่ folder ชื่อ iconfont แล้ว copy ทั้งหมดไปใส่ไว้ใน src/assets/fonts/material-icons ของโปรเจกต์ (ไม่มีก็สร้างเอง)

ตรงไปบอกกับ angular.json เพิ่ม material-icons.css ในส่วนของ styles

"src/assets/fonts/material-icons/material-icons.css"

ผมเพิ่ม icons เข้าไปที่ login.component.html (login template)

<i class="material-icons prefix">email</i>

ทวนกันเล็กน้อยก่อนจะไปต่อ ตอนนี้ frontend กับ backend ต่างคนต่างคุยกันคนละ port ผมจะยึด frontend เป็นหลักให้พยายามคุยกับ backend และด้วยความสามารถของ Angular มันมีเรื่อง proxy มาให้เล่น

อ่านเพิ่มเติม

สร้างไฟล์ชื่อ proxy.conf.json ภายใต้โปรเจกต์ (ไว้ใน src แล้วหาไม่เจอ)

สิ่งที่ทำคือเมื่อไรก็ตามที่ฝั่ง frontend ร้องขอ URI ที่ขึ้นต้นด้วย /api มันจะเปลี่ยน /api นี้เป็นสตริงว่าง (ตัดออก) สมมติ /api/login ก็จะกลายเป็น /login จึงได้ว่าเมื่อไรเรียก localhost:4200/login เท่ากับว่า localhost:8080/login นั่นเอง

หน้าบ้านคุยกับหลังบ้านได้แล้วนะ

หน้า Login นี้สิ่งที่ frontend จะส่งไปยัง backend คือ email กับ password เพื่อทำ Basic Authentication ระบุตัวตนว่ามีอยู่จริง สิ่งที่ได้กลับมาก็ควรจะเป็น user นั้นๆ

พื้นฐานการจัดการ Forms ของ Angular แนะนำให้อ่านเรื่อง FormsModule และ ReactiveFormsModule อ่านเพิ่มเติม

พื้นฐาน Angular ใช้ HTTP เพื่อร้องขอไปยัง backend อ่านเพิ่มเติม

ปรับปรุง login.component.html

ปรับปรุง login.component.ts

ปรับปรุง app.module.ts เพิ่มการจัดการ Forms

ปรับปรุง package.json ในส่วนของ scripts เพื่อเรียก proxy ให้ทำงาน

"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},

Spring Boot สร้าง POST ด้วย Basic Authentication

เนื้อหาการใช้ Spring Boot ทำ RESTful Web Service ผมเคยเขียนไปแล้ว สามารถอ่านได้ที่

และอ่านเพิ่มเติมเรื่อง Exception กับ RESTful Web Service ได้ที่

เริ่มที่การแบ่ง package กันก่อน ปกติผมจะแบ่งแบบ MVC แต่หนนี้จะแบ่งตามเรื่องไป

ให้ resource แรกเป็น User ดังนั้นสร้าง package ชื่อ user ด้านในประกอบด้วย

  • User (Entity)
  • UserController (REST Controller)
  • UserRepository (CRUD repository)

หมายเหตุ เนื่องจาก Microsoft SQL Server 2017 ที่ใช้นี้ user เป็นคำสงวน ดังนั้นจะใช้ชื่อตารางว่า users แทน

ลองมาเรียกด้วย Postman ดูหน่อย

เรียกไม่ได้เพราะติด Basic Authentication
เลือก Authorization แล้วระบุ user กับ password ที่มันสร้างให้ ปรากฏว่าเรียกได้

หากอยากเปลี่ยน username กับ password ที่ Spring Security จัดเตรียมไว้ให้ล่ะ

เปิดไฟล์ application.properties แล้วเพิ่ม

spring.security.user.name=adminspring.security.user.password=1234

rerun แล้วเรียกใหม่

เรียกได้

สร้าง user สักคน (ไว้ทดสอบ) ให้เปิดคลาส BoopackApplication (คลาสที่มีเมธอด main) แล้ว implement CommandLineRunner

ดูตัวอย่างเพิ่มเติม

เพราะ user แต่ละคนจำต้องมี email และ password ซึ่ง password ควรที่จะเข้ารหัสทุกครั้งแม้เก็บลงฐานข้อมูล Spring ก็มีทางออกของเรื่องนี้ครับ

เย้ ได้แล้ว!

ทีนี้กลับไปเรียกด้วย Postman

กลับได้ 401 Unauthorized เฉยเลย

เหตุเพราะ Spring Security ทราบว่าเราใช้ PasswordEncoder เมื่อเป็นอย่างนี้มันจึงเข้าใจว่า 1234 (password สมมติข้างต้น) ก็ต้องถูก encode ด้วย PasswordEncoder โปรดสังเกตที่ Log

Encoded password does not look like BCrypt

แก้ไขอย่างไร?

Spring Security มีคลาส WebSecurityConfigurerAdapter ทำหน้าที่รับผิดชอบกระบวนการ Authentication โดยเมธอดชื่อ

configure(AuthenticationManagerBuilder auth)

ดังนั้นเราจะ override เมธอดนี้ เพื่อบอกกับ Spring Security ว่าเข้ารหัสด้วย PasswordEncoder นะ

สร้าง package ใหม่ชื่อว่า security จากนั้นสร้างคลาส CustomWebSecurityConfigurerAdapter สืบทอดคลาส WebSecurityConfigurerAdapter

เมื่อมีตรงนี้แล้วในไฟล์ application.properties ก็ลบหรือ comment สองบรรทัดนี้เสีย

#spring.security.user.name=admin#spring.security.user.password=1234

rerun แล้วทดสอบใหม่!

สำเร็จ!

เอาล่ะ ยาวไปจึงขอตัด part นี้เท่านี้ก่อน มาสรุปว่าเราได้อะไรไปบ้าง

  • frontend ใช้ Angular เราเตรียมหน้า Login ไว้ เราต้องการส่ง email กับ password ผ่านเข้าไปทาง /login ซึ่งขณะนี้ยังไม่ทำที่ Spring Security
  • backend ใช้ Spring Security ทำ Basic Authentication การขอ resources ใดจากมันจะต้องระบุ Authorization แบบ Basic Authentication ไปด้วยเสมอ
  • และเมื่อทำ Basic Authentication สำเร็จจะได้ JSESSIONID กลับมาในรูปของ Cookies
  • ใน part ถัดไปเราจะใช้ email และ password ที่ได้บันทึกไว้ในฐานข้อมูลทำ Basic Authentication (แทน admin กับ 1234)
  • และสร้างหน้า Register ด้วย

เรื่องใหม่และท้าทายสำหรับผมต่อโปรเจกต์นี้คือ Spring Security Session Management พูดว่า Spring มีระบบคิดและจัดการอย่างไรบ้าง ก็หวังว่าเหล่านี้ที่ทำจะเป็นประโยชน์ต่อเพื่อนที่เข้ามาอ่านนะครับ

--

--

No responses yet