Saya mencoba mencari cara untuk menguji unit jika URL pengontrol saya diamankan dengan benar. Untuk berjaga-jaga jika seseorang mengubah sesuatu dan secara tidak sengaja menghapus pengaturan keamanan.
Metode pengontrol saya terlihat seperti ini:
@RequestMapping("/api/v1/resource/test")
@Secured("ROLE_USER")
public @ResonseBody String test() {
return "test";
}
Saya menyiapkan WebTestEnvironment seperti ini:
import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/security.xml",
"file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
"file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {
@Resource
private FilterChainProxy springSecurityFilterChain;
@Autowired
@Qualifier("databaseUserService")
protected UserDetailsService userDetailsService;
@Autowired
private WebApplicationContext wac;
@Autowired
protected DataSource dataSource;
protected MockMvc mockMvc;
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected UsernamePasswordAuthenticationToken getPrincipal(String username) {
UserDetails user = this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities());
return authentication;
}
@Before
public void setupMockMvc() throws NamingException {
// setup mock MVC
this.mockMvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain)
.build();
}
}
Dalam tes saya yang sebenarnya, saya mencoba melakukan sesuatu seperti ini:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import eu.ubicon.webapp.test.WebappTestEnvironment;
public class CopyOfClaimTest extends WebappTestEnvironment {
@Test
public void signedIn() throws Exception {
UsernamePasswordAuthenticationToken principal =
this.getPrincipal("test1");
SecurityContextHolder.getContext().setAuthentication(principal);
super.mockMvc
.perform(
get("/api/v1/resource/test")
// .principal(principal)
.session(session))
.andExpect(status().isOk());
}
}
Saya mengambil ini di sini:
- http://java.dzone.com/articles/spring-test-mvc-junit-testing di sini:
- http://techdive.in/solutions/how-mock-securitycontextholder-perfrom-junit-tests-spring-controller atau di sini:
- Bagaimana cara JUnit menguji anotasi @PreAuthorize dan EL pegasnya yang ditentukan oleh Pengontrol MVC pegas?
Namun jika seseorang melihat lebih dekat, ini hanya membantu saat tidak mengirimkan permintaan sebenarnya ke URL, tetapi hanya saat menguji layanan pada tingkat fungsi. Dalam kasus saya, pengecualian "akses ditolak" muncul:
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
...
Dua pesan log berikut pada dasarnya patut diperhatikan mengatakan bahwa tidak ada pengguna yang diautentikasi yang menunjukkan bahwa pengaturan Principal
tidak berfungsi, atau telah ditimpa.
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
Jawaban:
Mencari jawaban Saya tidak dapat menemukan jawaban yang mudah dan fleksibel pada saat yang sama, kemudian saya menemukan Referensi Keamanan Musim Semi dan saya menyadari ada solusi yang hampir sempurna. Solusi AOP sering kali merupakan solusi terbaik untuk pengujian, dan Spring menyediakannya
@WithMockUser
,@WithUserDetails
dan@WithSecurityContext
, dalam artefak ini:Dalam kebanyakan kasus,
@WithUserDetails
kumpulkan fleksibilitas dan kekuatan yang saya butuhkan.Bagaimana cara kerja @WithUserDetails?
Pada dasarnya Anda hanya perlu membuat kustom
UserDetailsService
dengan semua kemungkinan profil pengguna yang ingin Anda uji. MisalnyaSekarang kita memiliki pengguna yang siap, jadi bayangkan kita ingin menguji kontrol akses ke fungsi pengontrol ini:
Di sini kami memiliki fungsi yang dipetakan ke rute / foo / salute dan kami menguji keamanan berbasis peran dengan
@Secured
anotasi, meskipun Anda dapat menguji@PreAuthorize
dan@PostAuthorize
juga. Mari buat dua pengujian, satu untuk memeriksa apakah pengguna yang valid dapat melihat respons hormat ini dan yang lainnya untuk memeriksa apakah itu benar-benar dilarang.Seperti yang Anda lihat, kami mengimpor
SpringSecurityWebAuxTestConfig
untuk memberikan pengujian kepada pengguna kami. Masing-masing digunakan pada kasus uji yang sesuai hanya dengan menggunakan anotasi langsung, mengurangi kode dan kompleksitas.Lebih baik gunakan @WithMockUser untuk Keamanan Berbasis Peran yang lebih sederhana
Seperti yang Anda lihat, Anda
@WithUserDetails
memiliki semua fleksibilitas yang Anda butuhkan untuk sebagian besar aplikasi Anda. Ini memungkinkan Anda untuk menggunakan pengguna khusus dengan GrantedAuthority apa pun, seperti peran atau izin. Namun jika Anda hanya bekerja dengan peran, pengujian bisa jadi lebih mudah dan Anda dapat menghindari pembuatan kustomUserDetailsService
. Dalam kasus seperti itu, tentukan kombinasi sederhana antara pengguna, sandi, dan peran dengan @WithMockUser .Anotasi menentukan nilai default untuk pengguna yang sangat dasar. Seperti dalam kasus kami, rute yang kami uji hanya mengharuskan pengguna yang diautentikasi menjadi manajer, kami dapat berhenti menggunakan
SpringSecurityWebAuxTestConfig
dan melakukan ini.Perhatikan bahwa sekarang alih-alih manajer [email protected] kita mendapatkan default yang disediakan oleh
@WithMockUser
: pengguna ; namun tidak akan peduli karena apa yang kita benar-benar peduli adalah perannya:ROLE_MANAGER
.Kesimpulan
Seperti yang Anda lihat dengan anotasi like
@WithUserDetails
dan@WithMockUser
kita dapat beralih di antara skenario pengguna terautentikasi yang berbeda tanpa membangun kelas yang terasing dari arsitektur kita hanya untuk melakukan pengujian sederhana. Ini juga merekomendasikan Anda untuk melihat bagaimana @WithSecurityContext bekerja untuk lebih banyak fleksibilitas.sumber
tom
, sedangkan yang kedua olehjerry
?BasicUser
danManager User
diberikan dalam jawaban. Konsep utamanya adalah bahwa alih-alih memedulikan pengguna, kami sebenarnya peduli dengan peran mereka, tetapi masing-masing pengujian tersebut, yang terletak di pengujian unit yang sama, sebenarnya mewakili kueri yang berbeda. dilakukan oleh pengguna yang berbeda (dengan peran berbeda) ke titik akhir yang sama.Sejak Spring 4.0+, solusi terbaik adalah memberi anotasi metode pengujian dengan @WithMockUser
Ingatlah untuk menambahkan ketergantungan berikut ke proyek Anda
sumber
Ternyata
SecurityContextPersistenceFilter
, yang merupakan bagian dari rantai filter Keamanan Musim Semi, selalu me-reset mySecurityContext
, yang saya atur panggilanSecurityContextHolder.getContext().setAuthentication(principal)
(atau dengan menggunakan.principal(principal)
metode). Filter ini menetapkanSecurityContext
dalamSecurityContextHolder
denganSecurityContext
dariSecurityContextRepository
Timpa yang saya set sebelumnya. Repositori adalah secaraHttpSessionSecurityContextRepository
default. TheHttpSessionSecurityContextRepository
memeriksa yang diberikanHttpRequest
dan mencoba untuk mengakses yang sesuaiHttpSession
. Jika ada, itu akan mencoba membacaSecurityContext
dariHttpSession
. Jika ini gagal, repositori menghasilkan kosongSecurityContext
.Jadi, solusi saya adalah meneruskan
HttpSession
permintaan, yang menyimpanSecurityContext
:sumber
getPrincipal()
yang dilindungi yang menurut saya agak menyesatkan. idealnya itu harus diberi namagetAuthentication()
. demikian pula, dalamsignedIn()
pengujian Anda , variabel lokal harus dinamaiauth
atauauthentication
sebagai penggantiprincipal
Tambahkan pom.xml:
dan digunakan
org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
untuk permintaan otorisasi. Lihat penggunaan sampel di https://github.com/rwinch/spring-security-test-blog ( https://jira.spring.io/browse/SEC-2592 ).Memperbarui:
4.0.0.RC2 berfungsi untuk keamanan pegas 3.x. Untuk keamanan pegas, uji keamanan pegas 4 menjadi bagian dari keamanan pegas ( http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test , versinya sama ).
Pengaturan diubah: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc
Contoh untuk otentikasi dasar: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication .
sumber
Berikut adalah contoh bagi mereka yang ingin Menguji Konfigurasi Keamanan MockMvc Spring menggunakan otentikasi dasar Base64.
Ketergantungan Maven
sumber
Jawaban singkat:
Setelah melakukan
formLogin
uji keamanan musim semi, setiap permintaan Anda akan secara otomatis dipanggil sebagai pengguna yang masuk.Jawaban panjang:
Periksa solusi ini (jawabannya untuk pegas 4): Cara login pengguna dengan pengujian mvc baru pegas 3.2
sumber
Opsi untuk menghindari penggunaan SecurityContextHolder dalam pengujian:
SecurityContextHolder
menggunakan beberapa perpustakaan tiruan - EasyMock misalnyaSecurityContextHolder.get...
dalam kode Anda di beberapa layanan - misalnyaSecurityServiceImpl
dengan metodegetCurrentPrincipal
yang mengimplementasikanSecurityService
antarmuka dan kemudian dalam pengujian Anda, Anda cukup membuat implementasi tiruan dari antarmuka ini yang mengembalikan prinsip yang diinginkan tanpa aksesSecurityContextHolder
.sumber