1. Tổng quan
Spring Security cung cấp các hệ thống xác thực khác nhau, chẳng hạn như thông qua cơ sở dữ liệu và UserDetailService .
Thay vì sử dụng lớp liên tục JPA, chúng tôi cũng có thể muốn sử dụng, ví dụ:kho lưu trữ MongoDB. Trong hướng dẫn này, chúng ta sẽ xem cách xác thực người dùng bằng Spring Security và MongoDB.
2. Xác thực bảo mật mùa xuân với MongoDB
Tương tự như sử dụng kho lưu trữ JPA, chúng tôi có thể sử dụng kho lưu trữ MongoDB . Tuy nhiên, chúng tôi cần đặt một cấu hình khác để sử dụng nó.
2.1. Sự phụ thuộc của Maven
Đối với hướng dẫn này, chúng tôi sẽ sử dụng MongoDB được nhúng . Tuy nhiên, một phiên bản MongoDB và Testcontainer có thể là các tùy chọn hợp lệ cho môi trường sản xuất. Trước tiên, hãy thêm spring-boot-starter-data-mongodb và de.flapdoodle.embed.mongo phụ thuộc:
<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. Cấu hình
Khi chúng tôi đặt các phần phụ thuộc, chúng tôi có thể tạo cấu hình của mình:
@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");
}
}
Chúng tôi cũng cần định cấu hình AuthenticationManager chẳng hạn với xác thực cơ bản 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. Tên miền và kho lưu trữ của người dùng
Đầu tiên, hãy xác định một người dùng đơn giản với các vai trò để xác thực của chúng tôi. Chúng tôi sẽ yêu cầu nó triển khai Chi tiết người dùng giao diện để sử dụng lại các phương thức commons của một Hiệu trưởng đối tượng:
@Document
public class User implements UserDetails {
private @MongoId ObjectId id;
private String username;
private String password;
private Set<UserRole> userRoles;
// getters and setters
}
Bây giờ chúng ta có người dùng của mình, hãy xác định một kho lưu trữ đơn giản:
public interface UserRepository extends MongoRepository<User, String> {
@Query("{username:'?0'}")
User findUserByUsername(String username);
}
2.4. Dịch vụ xác thực
Cuối cùng, hãy triển khai UserDetailService của chúng tôi để truy xuất người dùng và kiểm tra xem người dùng đó đã được xác thực chưa :
@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. Kiểm tra xác thực
Để kiểm tra ứng dụng của chúng ta, hãy xác định một bộ điều khiển đơn giản. Ví dụ:chúng tôi đã xác định hai vai trò khác nhau để kiểm tra xác thực và ủy quyền cho các điểm cuối cụ thể:
@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!";
}
}
Hãy kết thúc tất cả trong Kiểm tra khởi động mùa xuân để kiểm tra xem xác thực của chúng tôi có hoạt động hay không. Như chúng ta có thể thấy, chúng tôi đang mong đợi một mã 401 cho ai đó cung cấp thông tin đăng nhập không hợp lệ hoặc người không tồn tại trong hệ thống của chúng tôi :
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());
}
}