Spring Security: Nâng cấp WebSecurityConfigurerAdapter không được chấp nhận

Người dịch: Nguyễn Thành Trung - Học viên lớp Java08
Email liên hệ: nguyenthanhtrung.nlk58@gmail.com
Bài viết gốc: https://www.baeldung.com/spring-deprecated-websecurityconfigureradapter

1. Tổng quát

Spring Security cho phép tùy chỉnh bảo mật HTTP cho các tính năng như ủy quyền endpoint hoặc cấu hình trình quản lý xác thực bằng cách mở rộng lớp WebSecurityConfigurerAdapter . Tuy nhiên, kể từ các phiên bản gần đây, Spring không dùng phương pháp này nữa và khuyến khích cấu hình bảo mật dựa trên thành phần.

Trong hướng dẫn này, chúng ta sẽ thấy một ví dụ về cách chúng ta có thể thay thế việc ngừng sử dụng này trong ứng dụng Spring Boot và chạy một số thử nghiệm MVC.

2. Spring Security không có WebSecurityConfigurerAdapter

Chúng ta thường thấy các lớp cấu hình bảo mật Spring HTTP mở rộng một lớp WebSecurityConfigureAdapter .

Tuy nhiên, kể từ phiên bản 5.7.0-M2, Spring không sử dụng WebSecurityConfigureAdapter và đề xuất tạo cấu hình mà không có nó.

Chúng ta sẽ tạo một ứng dụng Spring Boot ví dụ sử dụng xác thực trong bộ nhớ để hiển thị loại cấu hình mới này.

Đầu tiên, hãy xác định lớp cấu hình của chúng ta:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

    // config

}

Chúng ta đang thêm chú thích bảo mật phương pháp để cho phép xử lý dựa trên các vai trò khác nhau.

2.1. Cấu hình xác thực

Với WebSecurityConfigureAdapter, chúng ta sử dụng AuthenticationManagerBuilder để đặt ngữ cảnh xác thực của chúng ta.

Bây giờ, nếu chúng ta muốn tránh không dùng nữa, chúng ta có thể xác định một thành phần UserDetailsManager hoặc UserDetailsService :

@Bean
public UserDetailsService userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("user")
      .password(bCryptPasswordEncoder.encode("userPass"))
      .roles("USER")
      .build());
    manager.createUser(User.withUsername("admin")
      .password(bCryptPasswordEncoder.encode("adminPass"))
      .roles("USER", "ADMIN")
      .build());
    return manager;
}

Hoặc, với UserDetailService của chúng ta , chúng ta thậm chí có thể đặt AuthenticationManager :

@Bean
public AuthenticationManager authManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) 
  throws Exception {
    return http.getSharedObject(AuthenticationManagerBuilder.class)
      .userDetailsService(userDetailsService)
      .passwordEncoder(bCryptPasswordEncoder)
      .and()
      .build();
}

Tương tự, điều này sẽ hoạt động nếu chúng ta sử dụng xác thực JDBC hoặc LDAP.

2.2. Cấu hình bảo mật HTTP

Ví dụ: giả sử chúng ta muốn bảo mật các endpoint tùy thuộc vào các vai trò và chỉ để lại một điểm nhập ẩn danh để đăng nhập. Chúng ta cũng đang hạn chế mọi yêu cầu xóa đối với vai trò quản trị viên. Chúng ta sẽ sử dụng xác thực Cơ bản:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf()
      .disable()
      .authorizeRequests()
      .antMatchers(HttpMethod.DELETE)
      .hasRole("ADMIN")
      .antMatchers("/admin/**")
      .hasAnyRole("ADMIN")
      .antMatchers("/user/**")
      .hasAnyRole("USER", "ADMIN")
      .antMatchers("/login/**")
      .anonymous()
      .anyRequest()
      .authenticated()
      .and()
      .httpBasic()
      .and()
      .sessionManagement()
      .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    return http.build();
}

Bảo mật HTTP sẽ xây dựng một đối tượng DefaultSecurityFilterChain để tải các bộ lọc và đối sánh yêu cầu.

2.3. Cấu hình Security web

Ngoài ra, để bảo mật Web, bây giờ chúng ta có thể sử dụng giao diện gọi lại WebSecurityCustomizer.

Hãy thêm mức gỡ lỗi và bỏ qua một số đường dẫn, như hình ảnh hoặc tập lệnh:

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.debug(securityDebug)
      .ignoring()
      .antMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico");
}

3. Endpoints Controller

Hãy xác định một lớp điều khiển REST đơn giản cho ứng dụng của chúng ta:

@RestController
public class ResourceController {
    @GetMapping("/login")
    public String loginEndpoint() {
        return "Login!";
    }

    @GetMapping("/admin")
    public String adminEndpoint() {
        return "Admin!";
    }

    @GetMapping("/user")
    public String userEndpoint() {
        return "User!";
    }

    @GetMapping("/all")
    public String allRolesEndpoint() {
        return "All Roles!";
    }

    @DeleteMapping("/delete")
    public String deleteEndpoint(@RequestBody String s) {
        return "I am deleting " + s;
    }
}

Như chúng ta đã đề cập trước đó khi xác định bảo mật HTTP, chúng ta sẽ thêm một endpoint đăng nhập / chung mà bất kỳ ai cũng có thể truy cập, endpoint cụ thể cho quản trị viên và người dùng và một / tất cả endpoint không được bảo mật bằng vai trò nhưng vẫn yêu cầu xác thực.

4. Kiểm tra Endpoints

Hãy thêm cấu hình mới của chúng ta vào Spring Boot Test bằng cách sử dụng mô hình MVC để kiểm tra các endpoint của chúng ta.

4.1 Kiểm tra người dùng ẩn danh

Người dùng ẩn danh có thể truy cập / đăng nhập endpoint. Nếu họ cố gắng truy cập vào thứ gì đó khác, họ sẽ không được phép ( 401 ):

@Test
@WithAnonymousUser
public void whenAnonymousAccessLogin_thenOk() throws Exception {
    mvc.perform(get("/login"))
      .andExpect(status().isOk());
}

@Test
@WithAnonymousUser
public void whenAnonymousAccessRestrictedEndpoint_thenIsUnauthorized() throws Exception {
    mvc.perform(get("/all"))
      .andExpect(status().isUnauthorized());
}

Hơn nữa, đối với tất cả các endpoint ngoại trừ / đăng nhập , chúng ta luôn yêu cầu xác thực, như đối với / tất cả endpoint.

4.2. Kiểm tra vai trò của người dùng

Vai trò người dùng có thể truy cập các endpoint chung và tất cả các đường dẫn khác mà chúng ta đã cấp cho vai trò này:

@Test
@WithUserDetails()
public void whenUserAccessUserSecuredEndpoint_thenOk() throws Exception {
    mvc.perform(get("/user"))
      .andExpect(status().isOk());
}

@Test
@WithUserDetails()
public void whenUserAccessRestrictedEndpoint_thenOk() throws Exception {
    mvc.perform(get("/all"))
      .andExpect(status().isOk());
}

@Test
@WithUserDetails()
public void whenUserAccessAdminSecuredEndpoint_thenIsForbidden() throws Exception {
    mvc.perform(get("/admin"))
      .andExpect(status().isForbidden());
}

@Test
@WithUserDetails()
public void whenUserAccessDeleteSecuredEndpoint_thenIsForbidden() throws Exception {
    mvc.perform(delete("/delete"))
      .andExpect(status().isForbidden());
}

Cần lưu ý rằng nếu vai trò người dùng cố gắng truy cập vào endpoint được bảo mật bởi quản trị viên, người dùng sẽ gặp lỗi “forbidden” ( 403 ).

Thay vào đó, ai đó không có thông tin xác thực, chẳng hạn như người ẩn danh trong ví dụ trước, sẽ gặp lỗi “unauthorized” ( 401 ).

4.3. Kiểm tra vai trò quản trị viên

Như chúng ta có thể thấy, ai đó có vai trò quản trị viên có thể truy cập vào bất kỳ endpoint nào:

@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessUserEndpoint_thenOk() throws Exception {
    mvc.perform(get("/user"))
      .andExpect(status().isOk());
}

@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessAdminSecuredEndpoint_thenIsOk() throws Exception {
    mvc.perform(get("/admin"))
      .andExpect(status().isOk());
}

@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessDeleteSecuredEndpoint_thenIsOk() throws Exception {
    mvc.perform(delete("/delete").content("{}"))
      .andExpect(status().isOk());
}

5. Kết luận

Trong bài viết này, chúng ta đã biết cách tạo cấu hình Spring Security mà không cần sử dụng WebSecurityConfigureAdapter và thay thế nó trong khi tạo các thành phần để xác thực, bảo mật HTTP và bảo mật Web.