spring-boot-jwt/
│
├── src/main/java/
│ └── murraco
│ ├── configuration
│ │ └── SwaggerConfig.java
│ │
│ ├── controller
│ │ └── UserController.java
│ │
│ ├── dto
│ │ ├── UserDataDTO.java
│ │ └── UserResponseDTO.java
│ │
│ ├── exception
│ │ ├── CustomException.java
│ │ └── GlobalExceptionController.java
│ │
│ ├── model
│ │ ├── AppUserRole.java
│ │ └── AppUser.java
│ │
│ ├── repository
│ │ └── UserRepository.java
│ │
│ ├── security
│ │ ├── JwtTokenFilter.java
│ │ ├── JwtTokenFilterConfigurer.java
│ │ ├── JwtTokenProvider.java
│ │ ├── MyUserDetails.java
│ │ └── WebSecurityConfig.java
│ │
│ ├── service
│ │ └── UserService.java
│ │
│ └── JwtAuthServiceApp.java
│
├── src/main/resources/
│ └── application.yml
│
├── .gitignore
├── LICENSE
├── mvnw/mvnw.cmd
├── README.md
└── pom.xml
Sekadar memberikan latar belakang, kami memiliki pengantar yang bagus, milik jwt.io ! Mari kita lihat:
JSON Web Token (JWT) adalah standar terbuka (RFC 7519) yang mendefinisikan cara yang ringkas dan mandiri untuk mentransmisikan informasi antar pihak dengan aman sebagai objek JSON. Informasi ini dapat diverifikasi dan dipercaya karena ditandatangani secara digital. JWT dapat ditandatangani menggunakan rahasia (dengan algoritma HMAC) atau pasangan kunci publik/pribadi menggunakan RSA.
Mari kita jelaskan beberapa konsep definisi ini lebih lanjut.
Ringkas : Karena ukurannya yang lebih kecil, JWT dapat dikirim melalui URL, parameter POST, atau di dalam header HTTP. Selain itu, ukurannya yang lebih kecil berarti transmisinya cepat.
Mandiri : Payload berisi semua informasi yang diperlukan tentang pengguna, sehingga menghindari kebutuhan untuk menanyakan database lebih dari sekali.
Berikut adalah beberapa skenario dimana JSON Web Token berguna:
Otentikasi : Ini adalah skenario paling umum untuk menggunakan JWT. Setelah pengguna masuk, setiap permintaan berikutnya akan menyertakan JWT, yang memungkinkan pengguna mengakses rute, layanan, dan sumber daya yang diizinkan dengan token tersebut. Single Sign On adalah fitur yang banyak menggunakan JWT saat ini, karena overhead yang kecil dan kemampuannya untuk mudah digunakan di berbagai domain.
Pertukaran Informasi : Token Web JSON adalah cara yang baik untuk mengirimkan informasi antar pihak dengan aman. Karena JWT dapat ditandatangani—misalnya, menggunakan pasangan kunci publik/pribadi—Anda dapat yakin bahwa pengirimnya memang sesuai dengan yang mereka nyatakan. Selain itu, karena tanda tangan dihitung menggunakan header dan payload, Anda juga dapat memverifikasi bahwa konten belum dirusak.
JSON Web Token terdiri dari tiga bagian yang dipisahkan oleh titik (.) , yaitu:
Oleh karena itu, JWT biasanya terlihat seperti berikut.
xxxxx
. yyyyy
. zzzzz
Mari kita uraikan bagian-bagiannya.
Tajuk
Header biasanya terdiri dari dua bagian: jenis token, yaitu JWT, dan algoritma hashing yang digunakan, seperti HMAC SHA256 atau RSA.
Misalnya:
{
"alg" : " HS256 " ,
"typ" : " JWT "
}
Kemudian, JSON ini dikodekan Base64Url untuk membentuk bagian pertama JWT.
Muatan
Bagian kedua dari token adalah payload, yang berisi klaim. Klaim adalah pernyataan tentang suatu entitas (biasanya pengguna) dan metadata tambahan. Ada tiga jenis klaim: klaim yang dilindungi undang-undang, klaim publik, dan klaim pribadi.
Perhatikan bahwa nama klaim hanya terdiri dari tiga karakter karena JWT dimaksudkan agar ringkas.
Klaim publik : Klaim ini dapat ditentukan sesuka hati oleh mereka yang menggunakan JWT. Namun untuk menghindari tabrakan, mereka harus didefinisikan dalam IANA JSON Web Token Registry atau didefinisikan sebagai URI yang berisi namespace tahan tabrakan.
Klaim pribadi : Ini adalah klaim khusus yang dibuat untuk berbagi informasi antara pihak-pihak yang setuju untuk menggunakannya.
Contoh payload dapat berupa:
{
"sub" : " 1234567890 " ,
"name" : " John Doe " ,
"admin" : true
}
Payload tersebut kemudian dikodekan Base64Url untuk membentuk bagian kedua dari JSON Web Token.
Tanda tangan
Untuk membuat bagian tanda tangan Anda harus mengambil header yang disandikan, payload yang disandikan, rahasia, algoritma yang ditentukan dalam header, dan menandatanganinya.
Misalnya jika ingin menggunakan algoritma HMAC SHA256, maka tanda tangan akan dibuat dengan cara sebagai berikut:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Tanda tangan digunakan untuk memverifikasi bahwa pengirim JWT memang benar dan untuk memastikan bahwa pesan tidak diubah selama proses tersebut. Menyatukan semuanya
Outputnya berupa tiga string Base64 yang dipisahkan oleh titik-titik yang dapat dengan mudah diteruskan di lingkungan HTML dan HTTP, sekaligus lebih ringkas jika dibandingkan dengan standar berbasis XML seperti SAML.
Berikut ini menunjukkan JWT yang memiliki header dan payload sebelumnya yang dikodekan, dan ditandatangani dengan sebuah rahasia. JWT yang dikodekan
Dalam autentikasi, ketika pengguna berhasil masuk menggunakan kredensialnya, Token Web JSON akan dikembalikan dan harus disimpan secara lokal (biasanya di penyimpanan lokal, namun cookie juga dapat digunakan), bukan pendekatan tradisional yang membuat sesi di server dan mengembalikan cookie.
Kapan pun pengguna ingin mengakses rute atau sumber daya yang dilindungi, agen pengguna harus mengirimkan JWT, biasanya di header Otorisasi menggunakan skema Bearer. Isi headernya akan terlihat seperti berikut:
Authorization: Bearer <token>
Ini adalah mekanisme autentikasi tanpa kewarganegaraan karena status pengguna tidak pernah disimpan dalam memori server. Rute server yang dilindungi akan memeriksa JWT yang valid di header Otorisasi, dan jika ada, pengguna akan diizinkan untuk mengakses sumber daya yang dilindungi. Karena JWT bersifat mandiri, semua informasi yang diperlukan ada di sana, sehingga mengurangi kebutuhan untuk menanyakan database beberapa kali.
Hal ini memungkinkan Anda untuk sepenuhnya mengandalkan API data yang tidak memiliki kewarganegaraan dan bahkan membuat permintaan ke layanan downstream. Tidak masalah domain mana yang menyajikan API Anda, jadi Cross-Origin Resource Sharing (CORS) tidak akan menjadi masalah karena tidak menggunakan cookie.
Diagram berikut menunjukkan proses ini:
Skema autentikasi berbasis token menjadi sangat populer akhir-akhir ini, karena memberikan manfaat penting jika dibandingkan dengan sesi/cookie:
Beberapa trade-off harus dilakukan dengan pendekatan ini:
Alur Otentikasi JWT sangat sederhana
Penting untuk diperhatikan bahwa klaim otorisasi akan disertakan dengan token Akses. Mengapa ini penting? Katakanlah klaim otorisasi (misalnya hak pengguna dalam database) diubah selama masa pakai token Access. Perubahan tersebut tidak akan berlaku sampai token Akses baru dikeluarkan. Dalam kebanyakan kasus, hal ini bukan masalah besar, karena token Akses hanya berumur pendek. Jika tidak, gunakan pola token buram.
Mari kita lihat bagaimana kita bisa mengimplementasikan autentikasi berbasis token JWT menggunakan Java dan Spring, sambil mencoba menggunakan kembali perilaku default keamanan Spring jika kita bisa. Kerangka kerja Keamanan Musim Semi dilengkapi dengan kelas plug-in yang sudah menangani mekanisme otorisasi seperti: cookie sesi, HTTP Basic, dan HTTP Digest. Namun demikian, dukungan asli untuk JWT kurang, dan kita perlu bekerja keras untuk membuatnya berfungsi.
Demo ini saat ini menggunakan database H2 yang disebut test_db sehingga Anda dapat menjalankannya dengan cepat dan langsung tanpa banyak konfigurasi. Jika Anda ingin terhubung ke database lain, Anda harus menentukan koneksi di file application.yml
di dalam direktori sumber daya. Perhatikan bahwa hibernate.hbm2ddl.auto=create-drop
akan dihapus dan membuat database bersih setiap kali kami menerapkan (Anda mungkin ingin mengubahnya jika Anda menggunakan ini dalam proyek nyata). Berikut ini contoh dari proyek tersebut, lihat betapa mudahnya Anda menukar komentar pada properti url
dan dialect
untuk menggunakan database MySQL Anda sendiri:
spring :
datasource :
url : jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
# url: jdbc:mysql://localhost:3306/user_db
username : root
password : root
tomcat :
max-wait : 20000
max-active : 50
max-idle : 20
min-idle : 15
jpa :
hibernate :
ddl-auto : create-drop
properties :
hibernate :
dialect : org.hibernate.dialect.H2Dialect
# dialect: org.hibernate.dialect.MySQL8Dialect
format_sql : true
id :
new_generator_mappings : false
JwtTokenFilter
JwtTokenFilterConfigurer
JwtTokenProvider
MyUserDetails
WebSecurityConfig
Filter JwtToken
Filter JwtTokenFilter
diterapkan ke setiap API ( /**
) dengan pengecualian titik akhir token masuk ( /users/signin
) dan titik akhir singup ( /users/signup
).
Filter ini memiliki tanggung jawab sebagai berikut:
JwtTokenProvider
jika tidak, berikan pengecualian autentikasi Harap pastikan bahwa chain.doFilter(request, response)
dipanggil setelah otentikasi berhasil. Anda ingin pemrosesan permintaan dilanjutkan ke filter berikutnya, karena filter terakhir FilterSecurityInterceptor#doFilter bertanggung jawab untuk benar-benar memanggil metode di pengontrol Anda yang menangani sumber daya API yang diminta.
String token = jwtTokenProvider . resolveToken (( HttpServletRequest ) req );
if ( token != null && jwtTokenProvider . validateToken ( token )) {
Authentication auth = jwtTokenProvider . getAuthentication ( token );
SecurityContextHolder . getContext (). setAuthentication ( auth );
}
filterChain . doFilter ( req , res );
JwtTokenFilterConfigurer
Menambahkan JwtTokenFilter
ke DefaultSecurityFilterChain
keamanan booting pegas.
JwtTokenFilter customFilter = new JwtTokenFilter ( jwtTokenProvider );
http . addFilterBefore ( customFilter , UsernamePasswordAuthenticationFilter . class );
Penyedia JwtToken
JwtTokenProvider
memiliki tanggung jawab berikut:
Detail Pengguna Saya
Mengimplementasikan UserDetailsService
untuk mendefinisikan fungsi loadUserbyUsername kustom kita sendiri. Antarmuka UserDetailsService
digunakan untuk mengambil data terkait pengguna. Ia memiliki satu metode bernama loadUserByUsername yang menemukan entitas pengguna berdasarkan nama pengguna dan dapat diganti untuk menyesuaikan proses pencarian pengguna.
Ini digunakan oleh DaoAuthenticationProvider
untuk memuat detail tentang pengguna selama otentikasi.
Konfigurasi Keamanan Web
Kelas WebSecurityConfig
memperluas WebSecurityConfigurerAdapter
untuk menyediakan konfigurasi keamanan khusus.
Kacang berikut dikonfigurasi dan dipakai di kelas ini:
JwtTokenFilter
PasswordEncoder
Selain itu, di dalam metode WebSecurityConfig#configure(HttpSecurity http)
kita akan mengonfigurasi pola untuk menentukan titik akhir API yang dilindungi/tidak dilindungi. Harap dicatat bahwa kami telah menonaktifkan perlindungan CSRF karena kami tidak menggunakan Cookie.
// Disable CSRF (cross site request forgery)
http . csrf (). disable ();
// No session will be created or used by spring security
http . sessionManagement (). sessionCreationPolicy ( SessionCreationPolicy . STATELESS );
// Entry points
http . authorizeRequests () //
. antMatchers ( "/users/signin" ). permitAll () //
. antMatchers ( "/users/signup" ). permitAll () //
// Disallow everything else..
. anyRequest (). authenticated ();
// If a user try to access a resource without having enough permissions
http . exceptionHandling (). accessDeniedPage ( "/login" );
// Apply JWT
http . apply ( new JwtTokenFilterConfigurer ( jwtTokenProvider ));
// Optional, if you want to test the API from a browser
// http.httpBasic();
Pastikan Anda telah menginstal Java 8 dan Maven
Garpu repositori ini dan kloning
$ git clone https://github.com/<your-user>/spring-boot-jwt
$ cd spring-boot-jwt
$ mvn install
$ mvn spring-boot:run
http://localhost:8080/swagger-ui.html
di browser Anda untuk memeriksa semuanya berfungsi dengan benar. Anda dapat mengubah port default di file application.yml
server :
port : 8080
/users/me
untuk memeriksa bahwa Anda tidak diautentikasi. Anda akan menerima respons dengan pesan 403
dengan Access Denied
karena Anda belum menyetel token JWT yang valid $ curl -X GET http://localhost:8080/users/me
/users/signin
dengan pengguna admin default yang kami buat secara terprogram untuk mendapatkan token JWT yang valid $ curl -X POST 'http://localhost:8080/users/signin?username=admin&password=admin'
/users/me
lagi $ curl -X GET http://localhost:8080/users/me -H 'Authorization: Bearer <JWT_TOKEN>'
{
"id" : 1 ,
"username" : "admin" ,
"email" : "[email protected]" ,
"roles" : [
"ROLE_ADMIN"
]
}