1. Ikhtisar
Spring Security menawarkan sistem otentikasi yang berbeda, seperti melalui database dan UserDetailService .
Daripada menggunakan lapisan persistensi JPA, kami mungkin juga ingin menggunakan, misalnya, repositori MongoDB. Dalam tutorial ini, kita akan melihat cara mengautentikasi pengguna menggunakan Spring Security dan MongoDB.
2. Otentikasi Keamanan Musim Semi dengan MongoDB
Mirip dengan menggunakan repositori JPA, kita dapat menggunakan repositori MongoDB . Namun, kita perlu mengatur konfigurasi yang berbeda untuk menggunakannya.
2.1. Ketergantungan Maven
Untuk tutorial ini, kita akan menggunakan Embedded MongoDB . Namun, instance MongoDB dan Testcontainer bisa menjadi pilihan yang valid untuk lingkungan produksi. Pertama, mari tambahkan spring-boot-starter-data-mongodb dan de.flapdoodle.embed.mongo dependensi:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>3.3.1</version>
</dependency>
2.2. Konfigurasi
Setelah kita mengatur dependensi, kita dapat membuat konfigurasi kita:
@Configuration
public class MongoConfig {
private static final String CONNECTION_STRING = "mongodb://%s:%d";
private static final String HOST = "localhost";
@Bean
public MongoTemplate mongoTemplate() throws Exception {
int randomPort = SocketUtils.findAvailableTcpPort();
ImmutableMongodConfig mongoDbConfig = MongodConfig.builder()
.version(Version.Main.PRODUCTION)
.net(new Net(HOST, randomPort, Network.localhostIsIPv6()))
.build();
MongodStarter starter = MongodStarter.getDefaultInstance();
MongodExecutable mongodExecutable = starter.prepare(mongoDbConfig);
mongodExecutable.start();
return new MongoTemplate(MongoClients.create(String.format(CONNECTION_STRING, HOST, randomPort)), "mongo_auth");
}
}
Kita juga perlu mengonfigurasi AuthenticationManager dengan, misalnya, autentikasi dasar HTTP:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(@Autowired AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.and()
.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
2.3. Domain Pengguna dan Repositori
Pertama, mari kita definisikan pengguna sederhana dengan peran untuk otentikasi kita. Kami akan memintanya mengimplementasikan UserDetails antarmuka untuk menggunakan kembali metode umum dari Principal objek:
@Document
public class User implements UserDetails {
private @MongoId ObjectId id;
private String username;
private String password;
private Set<UserRole> userRoles;
// getters and setters
}
Sekarang setelah kita memiliki pengguna, mari kita definisikan repositori sederhana:
public interface UserRepository extends MongoRepository<User, String> {
@Query("{username:'?0'}")
User findUserByUsername(String username);
}
2.4. Layanan Otentikasi
Terakhir, mari implementasikan UserDetailService our kami untuk mengambil pengguna dan memeriksa apakah itu diautentikasi :
@Service
public class MongoAuthUserDetailService implements UserDetailsService {
// ...
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
com.baeldung.mongoauth.domain.User user = userRepository.findUserByUsername(userName);
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
user.getAuthorities()
.forEach(role -> {
grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole()
.getName()));
});
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
}
}
2.5. Uji Otentikasi
Untuk menguji aplikasi kita, mari kita definisikan pengontrol sederhana. Sebagai contoh, kami telah menetapkan dua peran berbeda untuk menguji autentikasi dan otorisasi untuk titik akhir tertentu:
@RestController
public class ResourceController {
@RolesAllowed("ROLE_ADMIN")
@GetMapping("/admin")
public String admin() {
return "Hello Admin!";
}
@RolesAllowed({ "ROLE_ADMIN", "ROLE_USER" })
@GetMapping("/user")
public String user() {
return "Hello User!";
}
}
Mari kita selesaikan semuanya dalam Tes Boot Musim Semi untuk memeriksa apakah otentikasi kita berfungsi. Seperti yang dapat kita lihat, kami mengharapkan kode 401 untuk seseorang yang memberikan kredensial yang tidak valid atau yang tidak ada di sistem kami :
class MongoAuthApplicationTest {
// set up
@Test
void givenUserCredentials_whenInvokeUserAuthorizedEndPoint_thenReturn200() throws Exception {
mvc.perform(get("/user").with(httpBasic(USER_NAME, PASSWORD)))
.andExpect(status().isOk());
}
@Test
void givenUserNotExists_whenInvokeEndPoint_thenReturn401() throws Exception {
mvc.perform(get("/user").with(httpBasic("not_existing_user", "password")))
.andExpect(status().isUnauthorized());
}
@Test
void givenUserExistsAndWrongPassword_whenInvokeEndPoint_thenReturn401() throws Exception {
mvc.perform(get("/user").with(httpBasic(USER_NAME, "wrong_password")))
.andExpect(status().isUnauthorized());
}
@Test
void givenUserCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn403() throws Exception {
mvc.perform(get("/admin").with(httpBasic(USER_NAME, PASSWORD)))
.andExpect(status().isForbidden());
}
@Test
void givenAdminCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn200() throws Exception {
mvc.perform(get("/admin").with(httpBasic(ADMIN_NAME, PASSWORD)))
.andExpect(status().isOk());
mvc.perform(get("/user").with(httpBasic(ADMIN_NAME, PASSWORD)))
.andExpect(status().isOk());
}
}