• Tidak ada hasil yang ditemukan

Menerapkan Pendaftaran

Dalam dokumen Dr. Joseph Teguh Santoso, M.Kom. (Halaman 101-107)

BAB 5 PENGEMBANGAN BACKEND

5.15 Menerapkan Pendaftaran

Untuk latihan ini gunakan proyek "kursus-tidak lengkap". Apakah Anda ingat fitur

“Formulir Pendaftaran” dari Bab 3? Mari implementasikan bagian backend dari awal sekarang sehingga Anda memiliki perasaan tentang bagaimana menerapkan fitur ujung ke ujung di backend. Jadi, kami diminta untuk memungkinkan pengguna mendaftarkan diri di platform. Anda mungkin sudah memperhatikan bahwa kami memiliki dua ranah berbeda di API: aman dan publik. Mereka cukup jelas; publik tidak memerlukan otentikasi dan yang aman melakukannya. Karena tentu saja pengguna tidak dapat mengautentikasi sebelum didaftarkan, titik akhir ini harus bersifat publik. Buka file PublicUserController.java. Sekarang kami perlu memperkenalkan Anda lagi pada beberapa informasi baru. Saat merancang REST API, kami mengandalkan metode HTTP untuk jenis operasi tertentu:

1. GET: Digunakan untuk mengambil data. Bagaimanapun, Anda tidak boleh mengubah data saat memanggil permintaan tersebut, kecuali jika Anda ingin melakukan semacam pelacakan, tetapi tidak pernah terkait dengan data itu sendiri. Klien yang membuat permintaan GET TIDAK AKAN PERNAH berharap bahwa data dapat berubah. Titik akhir GET adalah nullipoten, artinya memanggilnya tidak akan pernah memiliki efek samping.

2. POST: Kami menggunakan yang ini untuk membuat entitas baru. Artinya, jika Anda mencoba membuat entitas lain yang sama—misalnya, dua pengguna dengan nama pengguna yang sama—harus gagal. Lebih lanjut tentang itu nanti dalam contoh.

3. PUT: Digunakan untuk memperbarui entitas penuh. Ini berarti bahwa ketika pengguna melakukan panggilan seperti itu, diharapkan seluruh objek diganti dengan yang baru yang dikirim.

4. PATCH: Mengganti elemen non-null dalam entitas. Itu harus digunakan, misalnya, jika kita hanya ingin memperbarui nama pengguna, tetapi tidak seluruh pengguna.

Ini adalah cara yang paling disukai untuk memperbarui data. Secara umum, kami tidak menyarankan orang untuk menggunakan PUT dan menggunakan PATCH untuk kepentingannya.

5. DELETE: Gunakan metode ini untuk menghapus entitas. Terkadang penghapusan sebenarnya bukan penghapusan fisik, tetapi hanya pembatalan yang membuatnya tidak terlihat lagi untuk didaftar atau diambil (GET); mungkin berfungsi sebagai semacam bendera untuk kotor.

Oke, kembali ke contoh kita! Kami ingin menerapkan metode buat pengguna baru, jadi kami perlu mengekspos metode POST.

@RequestMapping(method = RequestMethod.POST)

@ResponseStatus(HttpStatus.CREATED)

public UserV1Dto create(@RequestBody UserV1Dto userV1Dto) {

return transformationsV1.user2Dto(userService.create(userV1Dto.toUser()));

}

Apa yang kami lakukan di sini adalah sebagai berikut: kami mengubah UserV1Dto menjadi objek pengguna yang dipahami oleh lapisan layanan kami (dan yang di bawah)—

userV1Dto.toUser(). Kemudian kami mengirim objek pengguna ini ke lapisan layanan kami melalui api layanan kami. Kita akan membahasnya sebentar lagi; mari kita fokus pada apa yang terjadi di sini sekarang. Kemudian layanan ini merespons dengan objek yang sama yang diperkaya dengan lebih banyak informasi, seperti id pengguna (karena id pengguna dibuat hanya ketika kami menyimpan pengguna di database).

Kami mengambil hasil ini dan mengubahnya lagi menjadi UserV1Dto dan mengirimkannya kembali ke pengguna dengan status respons default CREATED (201). Bila memungkinkan penggunaan dan penyalahgunaan kode HTTP yang tepat; klien frontend akan berterima kasih untuk itu. Anda mungkin telah memperhatikan bahwa kami tidak menentukan titik akhir, tetapi jangan khawatir—sebenarnya, kami menetapkannya pada tingkat kelas "/api/v1/public/users". Yang terjadi dalam hal ini adalah endpoint untuk membuat entitas sama dengan root dari endpoint, tetapi tentunya dengan metode POST.

Sekarang mari kita lihat implementasi lapisan layanan. Apa yang perlu kita lakukan di sini adalah sebagai berikut: periksa apakah pengguna belum ada dan gagal jika ada;

memvalidasi input—terutama nama pengguna dan kata sandi; dan menyimpan informasi di database mendapatkan kembali dengan id sehingga pengguna dapat direferensikan nanti.

Untuk memvalidasi alamat email pengguna, mari gunakan ekspresi reguler (regex).

Untungnya, kita tidak perlu memikirkan ini, karena banyak orang sudah melakukannya, dan jujur saja, hal semacam ini adalah tempat Anda biasanya memperkenalkan bug... jadi mari kita gunakan sesuatu yang sudah dibuat (dari http://emailregex.com/).

(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e- \x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-

9])?\.)+[a-z0-9](?:[a-z0-9]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-

9]?)\.){3}(?:25[05]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])

Cukup besar bukan? Mungkin bukan ide yang baik secara umum untuk merangkul petualangan membuat regex ini sendiri. Kedua regex sudah ada dalam kode sehingga Anda tidak perlu melakukan apa pun. Mari kita buat kode logika untuk membuat pengguna.

@Transactional

@Override

public User create(User user) throws AlreadyExistsServiceException { validateUserCreation(user);

if (userRepository.findByUsernameIgnoreCase(user.getUsername()) != null) {throw new AlreadyExistsServiceException("Username already exists.");

}

final String salt = UUID.randomUUID().toString();

user.setPassword(passwordEncoder.encode(user.getPassword()+salt));

user.setSalt(salt);

return userRepository.save(user);

}

Garam? Apa-apaan itu? Hal pertama yang pertama! Kami memanggil validasi—kami akan membahas lebih detail setelahnya—dan kemudian hal pertama yang perlu kami ketahui setelah validasi selesai adalah apakah kami sudah memiliki pengguna dengan nama pengguna yang sama, dalam kasus kami, alamat email. Jika itu masalahnya, kami memberikan pengecualian. Jenis pengecualian yang kami berikan di sini penting karena kami telah menyiapkan pengontrol terjemahan pengecualian pada lapisan rest-api yang akan menerjemahkan pengecualian ini ke kode kesalahan HTTP yang benar (Anda dapat memeriksanya di DefaultExceptionHandler.java). Setelah kami baik-baik saja dengan pemeriksaan itu, kami akan mengerjakan kata sandi pengguna.

Teknik garam adalah teknik yang baik untuk mengacak sedikit lebih banyak kata sandi di database kami. Ini berguna karena ketika kita meng-hash password pada database, input akan selalu menghasilkan output yang sama, itulah mengapa hal ini bisa dilakukan seperti itu. Jadi, bayangkan seseorang entah bagaimana mencuri data database kita. Hal pertama yang akan mereka coba adalah mengelompokkan semua kata sandi hash yang sama.

Kemudian mereka akan mencoba untuk mencari tahu yang paling populer, yang merupakan kandidat yang paling rentan karena itu adalah hal yang umum. Setelah peretas kami yang baik mengetahui kata sandi populer itu, itu berarti semua pengguna lain dengan hash yang sama akan memiliki kata sandi itu!

Dengan menambahkan beberapa informasi acak ke kata sandi, kata sandi yang di-hash akan selalu berbeda, jadi jika karena alasan tertentu peretas ini tiba-tiba mendapatkan satu kata sandi, bahkan jika pengguna lain memiliki kata sandi yang sama, itu akan membuat hidup mereka lebih sulit. Keren, bukan? Namun begitu sederhana. Ini tentu saja berarti bahwa ketika kita mengautentikasi pengguna, kita perlu mempertimbangkan hal ini dan melakukan hal yang sama persis: tambahkan garam ke input pengguna dan bandingkan dengan kata sandi hash yang kita miliki di database. Setelah selesai, kami mengatur semua

data yang kami buat, kata sandi hash, dan garam bersama-sama dan menyimpan pengguna.

Adapun validasinya, jika Anda masih tertarik, sesederhana itu:

protected void validateUserCreation(User user) {

Preconditions.checkNotNull(user, "User cannot be null.");

Preconditions.checkNotNull(user.getUsername(), "Username cannot be null.");

Preconditions.checkNotNull(user.getPassword(), "Password cannot be null.");

if (!EMAIL_REGEX.matcher(user.getUsername()).matches()) { throw new PreconditionFailedServiceException("Username is not valid.");

}

if (!PASSWORD_REGEX.matcher(user.getPassword()).matches()) { throw new PreconditionFailedServiceException("Password is not valid.");

} }

Mari kita coba! Kompilasi semuanya (mvn install -DskipTests di root proyek). Kemudian mari kita lakukan beberapa uji coba:

curl -v -X POST localhost:8080/api/v1/public/users -H "Content-type: application/json" -d '{"username":"test@example.","password":"secret"}'

Responnya:

{"reason":"Precondition failed: Username is not valid."}

Yang lainnya:

curl -v -X POST localhost:8080/api/v1/public/users -H "Content-type: application/json" -d '{"username":"[email protected]","password":"secret"}'

{"reason":"Precondition failed: Password is not valid."}

curl -v -X POST localhost:8080/api/v1/public/users -H "Content- type: application/json" -d '{"username":"[email protected]","password":"w1secret$"}'

{"id":1,"password":null,"name":null,"age":null,"gender":null,"username":

"[email protected]"}

Dan sekarang jika Anda mencoba lagi dengan pengguna yang sama:

curl -v -X POST localhost:8080/api/v1/public/users -H "Content- type: application/json" -d '{"username":"[email protected]","password":"w1secret$"}'

{"reason":"That resource already exists: Username already exists."}

Sebelum melanjutkan dengan lebih banyak contoh, izinkan kami memberi tahu Anda cara kerja struktur URL saat Anda menggunakan REST. Kami sudah membahas metodenya. Ini penting karena metode bersama dengan URL menentukan kumpulan operasi. Secara umum, ini adalah praktik yang baik bahwa URL Anda adalah kata benda dalam bentuk jamak ketika mereka menunjuk ke sumber daya yang sebenarnya, seperti pengguna, kursus, program, dll.

Tidak ada konsensus dalam hal tindakan; beberapa menggunakan kata kerja, yang lain menggunakan garis bawah dan kata kerja (mis., _search, _start, _stop). Secara umum, kami menyukai pendekatan dengan garis bawah, karena dengan jelas menyatakan bahwa Anda melakukan beberapa operasi dan tidak berurusan dengan sumber daya secara umum. Mari kita tentukan tabel dengan operasi dan struktur URL dan hasilnya. Contohnya adalah dengan sumber daya pengguna, seperti yang ditunjukkan pada Tabel 5-1.

Tabel 5-1. Metode REST pada Sumber Daya pengguna

URL GET POST PUT PATCH DELETE

../users mengambil daftar pengguna (dilarang)

Membuat pengguna baru

Mengganti daftar pengguna dengan yang baru (tidak digunakan dan agak berbahaya)

Tidak digunakan Menghapus koleksi pengguna

../users/1 Dapatkan pengguna dengan id 1

Tidak digunakan

mengganti data pengguna (dengan id 1) dengan yang baru

Mengubah data spesifik untuk pengguna dengan id 1

Menghapus user

dengan id 1

Jangan terlalu stres sekarang! Anda akan mempelajarinya seiring kebutuhan Anda berkembang. Mari sertakan fungsionalitas PATCH. Apakah Anda ingat tentang hal itu? Ini adalah salah satu yang digunakan untuk memperbarui informasi entitas, tetapi hanya elemen yang kita inginkan (yang bukan nol). Jadi, panggilan ini harus dilakukan dengan pengguna yang sudah diautentikasi sehingga kita perlu melakukannya di cabang "aman" dari pohon URL kita. Buka file SecuredUserController.java dan mulai coding PATCH.

@RequestMapping(value= "/{id}", method = RequestMethod.PATCH)

public UserV1Dto patch(@AuthenticationPrincipal UserDetailsImpl user,

@PathVariable("id") Long id,

@RequestBody UserV1Dto userV1Dto) { if (!id.equals(user.getId())) {

throw new ForbiddenServiceException("Not your user.");

}

return transformationsV1.user2Dto(userService.patch(id, userV1Dto. toUser()));

}

Mungkin Anda tidak menyadarinya, tetapi ada dua hal yang perlu diperhatikan dalam implementasi ini. Yang pertama adalah jika pengguna dalam sesi, mengapa kita harus melewati id? Sebenarnya kami tidak perlu melewatinya, tetapi dengan cara ini, Anda memenuhi standar REST. Jadi, kami sangat menganjurkan Anda untuk melakukannya seperti itu meskipun tidak diperlukan. Namun demikian, jika Anda benar-benar melakukannya, berhati-hatilah. Dalam kasus kami, kami menambahkan validasi untuk memeriksa apakah pengguna yang mencoba untuk diedit adalah pemilik sebenarnya dari sumber daya dan jika bukan itu masalahnya, kami hanya membuang pengecualian terlarang yang akan dipetakan ke kode kesalahan 403 (Terlarang). Dengan cara ini Anda membuat API Anda siap untuk masa depan. Ingat bahwa untuk saat ini kami hanya akan mengizinkan siswa, tetapi di masa mendatang, kami mungkin ingin mengaktifkan layanan pelanggan untuk mengedit beberapa informasi pengguna, dan dengan cara ini sudah setengah jadi!

Adapun sisa kode, tampaknya cukup jelas; kami hanya akan mengatakan bahwa Anda menentukan variabel (id dalam kasus ini) di dalam tanda kurung dan Anda menggunakan

@PathVariable untuk menyuntikkan nilainya ke dalam variabel id Panjang. Jadi, bagaimana

dengan lapisan layanan? Untuk saat ini, kami hanya mengimplementasikan PATCH untuk nama pengguna, tetapi kami dapat memperluasnya ke semua atribut yang dapat diedit (id tentu saja tidak termasuk!). Berikan perhatian khusus pada kata sandi, yang membutuhkan pemrosesan tambahan dan bukan hanya penggantian belaka.

@Override @Transactional public User patch(Long id, User user) { // Remember that if the user doesn't exist, this throws not found!

final User storedUser = get(id);

if (user.getName() != null) {

storedUser.setName(user.getName());

}

// Add more in the future!

return userRepository.save(storedUser);

}

Itu mudah, bukan? Mari kita coba!

curl -v -X PATCH localhost:8080/api/v1/secured/users/2 -H "Content-type: application/json" -H

"Authorization: bearer 35d8cda1-0f54-411e-a2a1b57c8cf42b75" -d '{"name": "test"}'

Kami baru saja mencoba memodifikasi pengguna yang bukan milik kami, jadi kami mendapatkan:

{"reason":"Forbidden: Not your user."}

Sekarang mari kita coba dengan pengguna kami:

curl -v -X PATCH localhost:8080/api/v1/secured/users/1 -H "Content-type: application/json" -H

"Authorization: bearer 35d8cda1-0f54-411e-a2a1b57c8cf42b75" -d '{"name": "test"}' Kami mendapatkan pembaruan!

{"id":1,"password":null,"name":"test","age":null,"gender":null,"username":"

[email protected]"}

Seperti yang Anda lihat, namanya sekarang "test". Mari kita verifikasi apakah itu benar-benar bertahan.

curl -v -X GET localhost:8080/api/v1/secured/users/me -H "Content-type: application/json" -H

"Authorization: bearer 35d8cda1-0f54-411e-a2a1b57c8cf42b75"

Dan memang itu!

{"id":1,"password":null,"name":"test","age":null,"gender":null,"username":"

[email protected]"}

Kami harap Anda mendapatkan pemahaman tentang cara kerja berbagai hal.

Sebagian besar kasusnya hampir sama. Kami bukan penggemar berat PUT; kami lebih suka mengkodekan semuanya dengan PATCH, tetapi mungkin ada beberapa kasus penggunaan.

Bagaimanapun, metode PUT dapat menyebabkan bug jika Anda lupa mengimplementasikan sesuatu; semuanya akan ditimpa. Adapun DELETE, pada dasarnya sama — kami menerima id sebagai variabel jalur dan kami melakukan operasi, yang biasanya tidak berarti penghapusan aktual tetapi hanya penonaktifan. Kami telah menyiapkan rangkaian pengujian bagi Anda untuk mulai mengkodekan beberapa pengujian. Mari kita beralih ke topik itu sekarang!

Dalam dokumen Dr. Joseph Teguh Santoso, M.Kom. (Halaman 101-107)