Perusahaan saya telah mengevaluasi Spring MVC untuk menentukan apakah kami harus menggunakannya di salah satu proyek kami berikutnya. Sejauh ini saya menyukai apa yang saya lihat, dan sekarang saya melihat pada modul Spring Security untuk menentukan apakah itu sesuatu yang bisa / harus kita gunakan.
Persyaratan keamanan kami sangat mendasar; pengguna hanya perlu dapat memberikan nama pengguna dan kata sandi untuk dapat mengakses bagian-bagian tertentu dari situs (seperti untuk mendapatkan info tentang akun mereka); dan ada beberapa halaman di situs (FAQ, Dukungan, dll) di mana pengguna anonim harus diberi akses.
Dalam prototipe yang saya buat, saya telah menyimpan objek "LoginCredentials" (yang hanya berisi nama pengguna dan kata sandi) di Session untuk pengguna yang diautentikasi; beberapa pengendali memeriksa untuk melihat apakah objek ini dalam sesi untuk mendapatkan referensi ke nama pengguna yang masuk, misalnya. Saya ingin mengganti logika buatan sendiri ini dengan Spring Security, yang akan bermanfaat untuk menghapus segala jenis "bagaimana cara melacak pengguna yang masuk?" dan "bagaimana kami mengautentikasi pengguna?" dari controller / kode bisnis saya.
Sepertinya Spring Security menyediakan objek "konteks" (per-utas) untuk dapat mengakses info nama pengguna / utama dari mana saja di aplikasi Anda ...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... yang tampaknya sangat tidak Spring seperti objek ini adalah singleton (global).
Pertanyaan saya adalah ini: jika ini adalah cara standar untuk mengakses informasi tentang pengguna terotentikasi di Spring Security, apa cara yang diterima untuk menyuntikkan objek Otentikasi ke dalam SecurityContext sehingga tersedia untuk pengujian unit saya ketika tes unit membutuhkan pengguna terautentikasi?
Apakah saya perlu menghubungkan ini dalam metode inisialisasi setiap test case?
protected void setUp() throws Exception {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
...
}
Ini sepertinya terlalu bertele-tele. Apakah ada cara yang lebih mudah?
The SecurityContextHolder
obyek itu sendiri tampaknya sangat un-Spring-seperti ...
Lakukan saja dengan cara biasa dan kemudian sisipkan menggunakannya
SecurityContextHolder.setContext()
di kelas tes Anda, misalnya:Pengendali:
Uji:
sumber
Authentication a
harus ditambahkan di controller? Seperti yang saya bisa mengerti dalam setiap doa metode? Apakah saya boleh untuk "cara semi" hanya untuk menambahkannya, daripada menyuntikkan?@BeforeEach
(JUnit5) atau@Before
(JUnit 4). Bagus dan sederhana.Tanpa menjawab pertanyaan tentang cara membuat dan menyuntikkan objek Otentikasi, Spring Security 4.0 memberikan beberapa alternatif selamat datang ketika datang ke pengujian. The
@WithMockUser
penjelasan memungkinkan pengembang untuk menentukan pengguna mock (dengan otoritas opsional, username, password dan peran) dengan cara yang rapi:Ada juga opsi untuk digunakan
@WithUserDetails
untuk meniru yangUserDetails
dikembalikan dariUserDetailsService
, misalnyaRincian lebih lanjut dapat ditemukan di @WithMockUser dan @WithUserDetails bab dalam dokumen referensi Spring Security (dari mana contoh di atas disalin)
sumber
Anda cukup benar untuk khawatir - panggilan metode statis sangat bermasalah untuk pengujian unit karena Anda tidak dapat dengan mudah mengejek dependensi Anda. Apa yang akan saya tunjukkan kepada Anda adalah bagaimana membiarkan wadah Spring IoC melakukan pekerjaan kotor untuk Anda, meninggalkan Anda dengan rapi, kode yang dapat diuji. SecurityContextHolder adalah kelas kerangka kerja dan meskipun mungkin ok untuk kode keamanan tingkat rendah Anda terikat padanya, Anda mungkin ingin mengekspos antarmuka yang lebih rapi ke komponen UI Anda (yaitu pengontrol).
cliff.meyers menyebutkan satu cara untuk mengatasinya - buat tipe "prinsipal" Anda sendiri dan suntikkan instance ke konsumen. Tag < aop: scoped-proxy /> Spring diperkenalkan pada 2.x dikombinasikan dengan definisi lingkup kacang permintaan, dan dukungan metode pabrik mungkin menjadi tiket ke kode yang paling mudah dibaca.
Ini bisa bekerja seperti berikut:
Sejauh ini tidak ada yang rumit, bukan? Bahkan Anda mungkin harus melakukan sebagian besar dari ini. Selanjutnya, dalam konteks kacang Anda, tentukan kacang lingkup permintaan untuk memegang kepala sekolah:
Berkat keajaiban aop: scoped-proxy tag, metode statis getUserDetails akan dipanggil setiap kali permintaan HTTP baru masuk dan referensi ke properti currentUser akan diselesaikan dengan benar. Sekarang pengujian unit menjadi sepele:
Semoga ini membantu!
sumber
Secara pribadi saya hanya akan menggunakan Powermock bersama dengan Mockito atau Easymock untuk mengejek SecurityContextHolder.getSecurityContext () yang statis dalam unit / tes integrasi misalnya
Harus diakui ada sedikit kode pelat ketel di sini yaitu mengejek objek Otentikasi, mengejek SecurityContext untuk mengembalikan Otentikasi dan akhirnya mengejek SecurityContextHolder untuk mendapatkan SecurityContext, namun sangat fleksibel dan memungkinkan Anda untuk menguji unit untuk skenario seperti objek Otentikasi null dll. tanpa harus mengubah kode (non tes) Anda
sumber
Menggunakan statis dalam hal ini adalah cara terbaik untuk menulis kode aman.
Ya, statika umumnya buruk - umumnya, tetapi dalam kasus ini, statis adalah yang Anda inginkan. Karena konteks keamanan mengaitkan Principal dengan utas yang saat ini berjalan, kode yang paling aman akan mengakses statis dari utas secara langsung. Menyembunyikan akses di belakang kelas pembungkus yang disuntikkan memberikan penyerang poin lebih banyak untuk diserang. Mereka tidak memerlukan akses ke kode (yang akan sulit diubah jika toples ditandatangani), mereka hanya perlu cara untuk mengesampingkan konfigurasi, yang dapat dilakukan saat runtime atau memasukkan beberapa XML ke classpath. Bahkan menggunakan injeksi anotasi akan dapat ditimpa dengan XML eksternal. XML semacam itu dapat menyuntikkan sistem yang sedang berjalan dengan prinsip jahat.
sumber
Saya mengajukan pertanyaan yang sama sendiri di sini , dan baru saja mengirim jawaban yang baru saya temukan. Jawaban singkatnya adalah: menyuntikkan
SecurityContext
, dan merujukSecurityContextHolder
hanya pada konfigurasi Spring Anda untuk mendapatkanSecurityContext
sumber
Umum
Sementara itu (sejak versi 3.2, pada tahun 2013, terima kasih kepada SEC-2298 ) otentikasi dapat disuntikkan ke dalam metode MVC menggunakan penjelasan @AuthenticationPrincipal :
Tes
Dalam pengujian unit Anda, Anda dapat memanggil Metode ini secara langsung. Dalam tes integrasi menggunakan
org.springframework.test.web.servlet.MockMvc
Anda dapat menggunakanorg.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user()
untuk menyuntikkan pengguna seperti ini:Namun ini hanya akan langsung mengisi SecurityContext. Jika Anda ingin memastikan bahwa pengguna dimuat dari sesi dalam pengujian Anda, Anda dapat menggunakan ini:
sumber
Saya akan melihat kelas tes abstrak Spring dan benda-benda tiruan yang dibicarakan di sini . Mereka menyediakan cara yang kuat untuk pengkabelan otomatis objek yang dikelola Spring Anda membuat pengujian unit dan integrasi lebih mudah.
sumber
Otentikasi adalah properti dari utas di lingkungan server dengan cara yang sama seperti itu adalah properti dari proses di OS. Memiliki contoh kacang untuk mengakses informasi otentikasi akan menjadi konfigurasi yang tidak nyaman dan overhead kabel tanpa manfaat.
Mengenai otentikasi tes ada beberapa cara bagaimana Anda dapat membuat hidup Anda lebih mudah. Favorit saya adalah membuat anotasi khusus
@Authenticated
dan menguji pendengar eksekusi, yang mengaturnya. PeriksaDirtiesContextTestExecutionListener
inspirasi.sumber
Setelah cukup banyak bekerja saya dapat mereproduksi perilaku yang diinginkan. Saya telah meniru login melalui MockMvc. Ini terlalu berat untuk sebagian besar tes unit tetapi membantu untuk tes integrasi.
Tentu saja saya bersedia melihat fitur-fitur baru di Spring Security 4.0 yang akan membuat pengujian kami lebih mudah.
sumber