🌸Spring Security: Authentication and Authorization In-Depth

Bạn có thể sử dụng hướng dẫn này để hiểu về Spring Security là gì và làm thế nào các tính năng cốt lõi như authentication, authorization hoặc common exploit protection hoạt động.

Introduction

Sớm muộn, mọi người đều cần thêm tính bảo mật vào dự án của mình và trong hệ sinh thái Spring, bạn có thể làm điều đó với sự trợ giúp của thư viện Spring Security.

Vì vậy, bạn tiếp tục, thêm Spring Security vào dự án Spring Boot (hoặc Spring thuần) của bạn và đột nhiên...

... bạn có các trang đăng nhập được tự động tạo.

... bạn không thể thực hiện các yêu cầu POST nữa.

... toàn bộ ứng dụng của bạn bị khóa và yêu cầu bạn nhập tên người dùng và mật khẩu.

Sau khi vượt qua giai đoạn suy sụp tâm lý sau đó, bạn có thể quan tâm đến cách mọi thứ hoạt động.

What is Spring Security and how does it work?

The short answer:

Ở cốt lõi, Spring Security thực chất chỉ là một tập hợp các bộ lọc servlet giúp bạn thêm chức năng xác thực (authentication) và phân quyền (authorization) vào ứng dụng web của bạn.

Nó cũng tích hợp tốt với các framework như Spring Web MVC (hoặc Spring Boot), cũng như các tiêu chuẩn như OAuth2 hoặc SAML. Ngoài ra, nó tự động tạo trang đăng nhập/đăng xuất và bảo vệ khỏi các lỗ hổng thông thường như CSRF.

Tuy nhiên, tôi hiểu rằng thông tin trên có thể chưa đủ giúp ích.

Luckily, there’s also a long answer:

The remainder of this article.

Web Application Security: 101

Trước khi trở thành bậc thầy Spring Security, bạn cần hiểu ba khái niệm quan trọng:

  1. Authentication

  2. Authorization

  3. Bộ lọc Servlet

Đừng bỏ qua phần này, vì nó là nền tảng cho mọi thứ mà Spring Security thực hiện. Ngoài ra, tôi sẽ làm cho thú vị nhất có thể.

1. Authentication (xác thực)

Trước hết, nếu bạn đang chạy một ứng dụng (web) điển hình, bạn cần user của mình authenticate. Điều đó có nghĩa là ứng dụng của bạn cần xác minh xem user có phải là người mà anh ta thừa nhận đúng là anh ta hay không, thường được thực hiện bằng kiểm tra username và password.

User : “Tôi là tổng thống Hoa Kỳ. username của tôi là: potus!”

Ứng dụng web của bạn : “Chắc chắn rồi, thế còn password thì sao, thưa ngài Tổng thống?”

User : “Pass của tôi là: th3don4ld”.

Ứng dụng web của bạn : “Đúng. Chào mừng, thưa ngài!”

2. Authorization (Phân quyền)

Trong các ứng dụng đơn giản hơn, chỉ cần authenticate là đủ: Ngay sau khi người dùng authenticate, họ có thể truy cập từng phần của ứng dụng.

Nhưng hầu hết các ứng dụng đều có khái niệm về quyền (hoặc vai trò). Hãy tưởng tượng: khách hàng có quyền truy cập vào giao diện công khai của webshop của bạn và quản trị viên có quyền truy cập vào khu vực quản trị riêng biệt.

Cả hai loại user đều cần đăng nhập, nhưng thực tế authentication đơn thuần không nói lên gì về việc họ được phép làm gì trong hệ thống của bạn. Do đó, bạn cũng cần phải kiểm tra các quyền của user đã được authenticate, tức là bạn cần cấp quyền cho người dùng đó.

User : “Hãy để tôi chơi với quả bóng hạt nhân đó….”

Ứng dụng web của bạn : "Đợi một giây, tôi cần kiểm tra lại permission của ngài trước… Dạ thưa Chủ tịch, ngài có đủ điều kiện. Hãy cứ tận hưởng”

User : “Lại cái nút đỏ đó là gì vậy… ??”

3. Bộ lọc Servlet

Cuối cùng nhưng không kém phần quan trọng, chúng ta hãy xem xét Bộ lọc Servlet. Chúng liên quan gì đến authentication và authorization?

Tại sao sử dụng Bộ lọc Servlet?

Hãy nhớ lại bài viết khác của tôi , nơi chúng tôi phát hiện ra rằng về cơ bản bất kỳ ứng dụng web Spring nào cũng chỉ là một servlet: DispatcherServlet cũ của Spring, giúp chuyển hướng các yêu cầu HTTP đến (ví dụ từ trình duyệt) đến @Controllers hoặc @RestControllers của bạn.

Vấn đề là: Không có mật mã bảo mật nào được mã hóa trong DispatcherServlet đó và bạn cũng rất có thể không muốn mò mẫm với header HTTP Basic Auth thô sơ trong @Controllers của mình. Để cho tối ưu, việc authentication và authorization nên được thực hiện trước khi một request truy cập vào @Controllers của bạn.

May mắn thay, có một cách để thực hiện chính xác điều này trong thế giới web Java: bạn có thể đặt bộ lọc lên trước các servlet, tức là bạn có thể viết SecurityFilter và cấu hình nó trong Tomcat (servlet container/ application server) của bạn để lọc mọi request HTTP trước khi nó truy cập vào servlet của bạn.

Một SecurityFilter đơn giản

Một SecurityFilter có khoảng 4 tác vụ và một cách triển khai đơn giản quá mức có thể trông như thế này:

import javax.servlet.*;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SecurityServletFilter extends HttpFilter {

    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        UsernamePasswordToken token = extractUsernameAndPasswordFrom(request);  // (1)

        if (notAuthenticated(token)) {  // (2)
            // either no or wrong username/password
            // unfortunately the HTTP status code is called "unauthorized", instead of "unauthenticated"
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // HTTP 401.
            return;
        }

        if (notAuthorized(token, request)) { // (3)
            // you are logged in, but don't have the proper rights
            response.setStatus(HttpServletResponse.SC_FORBIDDEN); // HTTP 403
            return;
        }

        // allow the HttpRequest to go to Spring's DispatcherServlet
        // and @RestControllers/@Controllers.
        chain.doFilter(request, response); // (4)
    }

    private UsernamePasswordToken extractUsernameAndPasswordFrom(HttpServletRequest request) {
        // Either try and read in a Basic Auth HTTP Header, which comes in the form of user:password
        // Or try and find form login request parameters or POST bodies, i.e. "username=me" & "password="myPass"
        return checkVariousLoginOptions(request);
    }


    private boolean notAuthenticated(UsernamePasswordToken token) {
        // compare the token with what you have in your database...or in-memory...or in LDAP...
        return false;
    }

    private boolean notAuthorized(UsernamePasswordToken token, HttpServletRequest request) {
       // check if currently authenticated user has the permission/role to access this request's /URI
       // e.g. /admin needs a ROLE_ADMIN , /callcenter needs ROLE_CALLCENTER, etc.
       return false;
    }
}
  1. Đầu tiên, bộ lọc cần extract username/password từ request. Nó có thể thông qua Basic Auth Http Header, hoặc các field trong form, hoặc cookie, v.v.

  2. Sau đó, bộ lọc cần xác thực bằng cách đối chiếu tổ hợp username/password đó với một thứ gì đó , chẳng hạn như database.

  3. Sau khi authenticate thành công, bộ lọc cần kiểm tra user có được phép truy cập requested URI hay không.

  4. Nếu request vẫn tồn tại sau tất cả các lần kiểm tra này, thì bộ lọc có thể cho phép request chuyển đến DispatcherServlet của bạn, tức là @Controllers của bạn.

FilterChains

Kiểm chứng thực tế: Trong khi đoạn mã trên có thể hoạt động và biên dịch, tuy nhiên sớm hay muộn nó sẽ dẫn đến một bộ lọc khổng lồ với rất nhiều mã cho các cơ chế xác thực và phân quyền khác nhau.

Trong thực tế, bạn sẽ tách bộ lọc này thành nhiều bộ lọc riêng biệt, sau đó ghép chúng thành một chuỗi (FilterChain).

Ví dụ, một yêu cầu HTTP đến…

Đầu tiên, đi qua một bộ lọc LoginMethodFilter…

Tiếp theo, đi qua một bộ lọc AuthenticationFilter…

Sau đó, đi qua một bộ lọc AuthorizationFilter…

Cuối cùng, đến servlet của bạn.

Khái niệm này được gọi là FilterChain, và cuộc gọi phương thức cuối cùng trong bộ lọc trên thực tế đang giao phó cho chuỗi đó.

chain.doFilter(request, response);

Với một bộ lọc (chuỗi) như vậy, bạn có thể xử lý mọi vấn đề xác thực hoặc phân quyền trong ứng dụng của bạn mà không cần thay đổi cài đặt thực tế của ứng dụng (nghĩa là các @RestControllers / @Controllers của bạn).

Với kiến thức đó, hãy tìm hiểu cách Spring Security sử dụng "phép màu" của bộ lọc này.

FilterChain & Security Configuration DSL

Chúng ta sẽ bắt đầu tìm hiểu về Spring Security một cách không truyền thống, bằng cách đi theo hướng ngược lại so với chương trước, bắt đầu với FilterChain của Spring Security.

Spring’s DefaultSecurityFilterChain

Giả sử bạn đã thiết lập Spring Security đúng cách và khởi động ứng dụng web của bạn. Bạn sẽ thấy thông báo ghi log sau đây:

2020-02-25 10:24:27.875  INFO 11116 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@46320c9a, org.springframework.security.web.context.SecurityContextPersistenceFilter@4d98e41b, org.springframework.security.web.header.HeaderWriterFilter@52bd9a27, org.springframework.security.web.csrf.CsrfFilter@51c65a43, org.springframework.security.web.authentication.logout.LogoutFilter@124d26ba, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@61e86192, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@10980560, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@32256e68, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@52d0f583, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5696c927, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5f025000, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5e7abaf7, org.springframework.security.web.session.SessionManagementFilter@681c0ae6, org.springframework.security.web.access.ExceptionTranslationFilter@15639d09, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4f7be6c8]|

Nếu bạn mở rộng một dòng đó thành một list, nó sẽ giống như Spring Security không chỉ cài đặt một bộ lọc, thay vào đó, nó cài đặt toàn bộ một filterchain bao gồm 15 (!) bộ lọc khác nhau.

Vì vậy, khi một HTTPRequest đến, nó sẽ đi qua tất cả 15 bộ lọc này, trước khi request của bạn cuối cùng truy cập vào @RestControllers của bạn. Thứ tự cũng quan trọng, bắt đầu từ trên cùng list đó và đi xuống đáy.

Analyzing Spring’s FilterChain

Sẽ mất rất lâu để có một cái nhìn chi tiết về mọi bộ lọc của chain này, nhưng đây là lời giải thích cho một số bộ lọc đó. Vui lòng xem mã nguồn của Spring Security để hiểu các bộ lọc khác.

  • BasicAuthenticationFilter : Cố gắng tìm Basic Auth HTTP Header theo request và nếu tìm thấy, cố gắng xác thực người dùng bằng username và password của header.

  • UsernamePasswordAuthenticationFilter : Cố gắng tìm tham số request username/password hay POST body và nếu được tìm thấy, cố gắng authenticate user bằng các giá trị đó.

  • DefaultLoginPageGeneratingFilter : Tạo trang login cho bạn, nếu bạn không disable tính năng đó. Bộ lọc NÀY là lý do tại sao bạn nhận được trang đăng nhập mặc định khi bật Spring Security.

  • DefaultLogoutPageGeneratingFilter : Tạo trang logout cho bạn, nếu bạn không disable tính năng đó.

  • FilterSecurityInterceptor : Thực hiện authorization của bạn.

Vì vậy, với một số bộ lọc này, Spring Security cung cấp cho bạn trang login / logout, cũng như khả năng login bằng Basic Auth hoặc Form Logins, cũng như một số tính năng bổ sung như CsrfFilter, mà chúng ta sẽ tìm hiểu sau.

Những bộ lọc này, phần lớn, là Spring Security. Không hơn, không kém. Chúng thực hiện toàn bộ công việc. Còn lại cho bạn là cấu hình cách chúng thực hiện công việc, tức là bảo vệ các URL nào, bỏ qua các URL nào và sử dụng bảng cơ sở dữ liệu nào để xác thực. Do đó, chúng ta cần xem cách cấu hình Spring Security tiếp theo.

How to configure Spring Security: WebSecurityConfigurerAdapter

Với phiên bản mới nhất của Spring Security và/hoặc Spring Boot, cách cấu hình Spring Security là thông qua một lớp có các đặc điểm sau:

  • Được chú thích bằng @EnableWebSecurity.

  • Mở rộng từ lớp WebSecurityConfigurer, đóng vai trò cung cấp một DSL/phương thức cấu hình. Với những phương thức đó, bạn có thể chỉ định các URI trong ứng dụng của bạn cần bảo vệ hoặc bật/tắt các bảo vệ chống khai thác.

Dưới đây là ví dụ về một lớp WebSecurityConfigurerAdapter điển hình:

@Configuration
@EnableWebSecurity // (1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // (1)

  @Override
  protected void configure(HttpSecurity http) throws Exception {  // (2)
      http
        .authorizeRequests()
          .antMatchers("/", "/home").permitAll() // (3)
          .anyRequest().authenticated() // (4)
          .and()
       .formLogin() // (5)
         .loginPage("/login") // (5)
         .permitAll()
         .and()
      .logout() // (6)
        .permitAll()
        .and()
      .httpBasic(); // (7)
  }
}
  1. Một Spring @Configuration bình thường với chú thích @EnableWebSecurity, extends từ WebSecurityConfigurerAdapter.

  2. Bằng cách override phương thức cấu hình (HttpSecurity) của adapter, bạn sẽ có được một DSL nhỏ xinh để bạn có thể cấu hình FilterChain của mình.

  3. Tất cả các yêu cầu đi đến / /home đều được phép - người dùng không cần xác thực. Bạn đang sử dụng antMatcher, điều này có nghĩa là bạn cũng có thể sử dụng ký tự đại diện (wildcards) (*, **, ?) trong chuỗi.

  4. Mọi request khác đều cần người dùng authenticate trước , tức là user cần login.

  5. Bạn đang để form login (username/password dưới dạng form), với loginPage tùy chỉnh (/login, tức là không phải trang mặc định do Spring Security tự động tạo ra). Bất kỳ ai cũng có thể truy cập vào trang đăng nhập mà không cần phải đăng nhập trước (permitAll; nếu không, chúng ta sẽ gặp một tình huống Catch-22!).

  6. Cũng tương tự như (5) nhưng là với trang logout.

  7. Trên hết, bạn cũng đang cho phép Basic Auth, nghĩa là gửi một HTTP Basic Auth Header để authenticate.

How to use Spring Security’s configure DSL

Việc sử dụng DSL này có thể mất một thời gian để làm quen, nhưng bạn sẽ tìm thấy nhiều ví dụ hơn trong phần FAQ: AntMatchers: Các Ví dụ Thông dụng.

Điều quan trọng lúc này là phương thức configure(HttpSecurity) chính là nơi bạn chỉ định:

  • Những URL cần bảo vệ (authenticated()) và những URL được phép truy cập (permitAll()).

  • Authentication method nào được cho phép (formLogin(), httpBasic()) và cách chúng được cấu hình.

Nói một cách ngắn gọn: đó là cấu hình bảo mật đầy đủ cho ứng dụng của bạn.

Lưu ý: Bạn không cần phải override configure mặc định của adapter ngay lập tức, vì nó đã được cài đặt khá hợp lý. Đây là cách nó trông như sau:

public abstract class WebSecurityConfigurerAdapter implements
		WebSecurityConfigurer<WebSecurity> {

    protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .anyRequest().authenticated()  // (1)
                    .and()
                .formLogin().and()   // (2)
                .httpBasic();  // (3)
        }
}
  1. Để truy cập bất kỳ URI (anyRequest()) nào trong ứng dụng của bạn, bạn cần xác thực (authenticated()).

  2. Đăng nhập qua Form (formLogin()) với các thiết lập mặc định được bật.

  3. Cũng như xác thực HTTP Basic (httpBasic()).

Cấu hình mặc định này là lý do tại sao ứng dụng của bạn bị khóa ngay khi bạn thêm Spring Security vào. Đơn giản, phải không?

Summary: WebSecurityConfigurerAdapter’s DSL configuration

Chúng ta đã tìm hiểu rằng Spring Security bao gồm một số bộ lọc mà bạn cấu hình bằng một lớp WebSecurityConfigurerAdapter @Configuration.

Nhưng có một phần quan trọng bị thiếu. Hãy lấy ví dụ về BasicAuthFilter của Spring. Nó có thể trích xuất tên người dùng/mật khẩu từ tiêu đề HTTP Basic Auth, nhưng nó xác thực thông tin đăng nhập này với điều gì?

Điều này dẫn đến câu hỏi về cách xác thực hoạt động với Spring Security.

Authentication with Spring Security

Khi nói về xác thực và Spring Security, bạn có khoảng ba tình huống chính:

  1. Mặc định : Bạn có thể truy cập (hashed) password của user, bởi vì bạn có thông tin chi tiết của mình (username, password) được lưu chẳng hạn trong một bảng database.

  2. Ít phổ biến hơn : Bạn không thể truy cập password (hashed) của user. Đây là trường hợp nếu user và password của bạn được lưu trữ ở một nơi khác, chẳng hạn như trong một sản phẩm quản lý danh tính của bên thứ ba cung cấp dịch vụ REST cho authentication. Hãy thử tìm hiểu: Atlassian Crowd.

  3. Cũng phổ biến : Bạn muốn sử dụng OAuth2 hoặc “Đăng nhập bằng Google / Twitter / v.v.” (OpenID), khả năng kết hợp với JWT. Trong trường hợp này, không áp dụng bất kỳ điều gì sau đây và bạn nên đi thẳng vào chương OAuth2.

Lưu ý: Tùy thuộc vào tình huống của bạn, bạn cần chỉ định các @Bean khác nhau để làm việc với Spring Security, nếu không, bạn sẽ gặp các exception khá khó hiểu (như NullPointerException nếu bạn quên chỉ định PasswordEncoder). Hãy nhớ điều đó.

Bây giờ chúng ta sẽ xem xét hai tình huống đầu.

UserDetailsService: Having access to the user’s password

Hãy tưởng tượng bạn có một bảng database nơi bạn lưu trữ user của mình. Nó có một vài cột, nhưng quan trọng nhất là nó có cột username và password, nơi bạn lưu trữ hashed password của user.

create table users (id int auto_increment primary key, username varchar(255), password varchar(255));

Trong trường hợp này, Spring Security cần bạn xác định hai bean để thiết lập và chạy authentication

  1. Một UserDetailsService.

  2. Một PasswordEncoder

Chỉ định một UserDetailsService đơn giản như sau:

@Bean
public UserDetailsService userDetailsService() {
    return new MyDatabaseUserDetailsService(); // (1)
}
  1. MyDatabaseUserDetailsService implements UserDetailsService - một interface rất đơn giản bao gồm một method trả về một object UserDetails:

public class MyDatabaseUserDetailsService implements UserDetailsService {

	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // (1)
         // 1. Load the user from the users table by username. If not found, throw UsernameNotFoundException.
         // 2. Convert/wrap the user to a UserDetails object and return it.
        return someUserDetails;
    }
}

public interface UserDetails extends Serializable { // (2)

    String getUsername();

    String getPassword();

    // <3> more methods:
    // isAccountNonExpired,isAccountNonLocked,
    // isCredentialsNonExpired,isEnabled
}
  1. Một UserDetailsService tải (load) UserDetails qua username của user. Lưu ý rằng method chỉ nhận một tham số: username (không phải password).

  2. Interface UserDetails có các method để lấy (hashed) password và một method để lấy username.

  3. UserDetails thậm chí còn có nhiều method hơn, chẳng hạn như account đang hoạt động hay bị chặn, thông tin đăng nhập đã hết hạn hay user được cấp phép gì - nhưng chúng ta sẽ không đề cập đến ở đây.

Vì vậy, bạn có thể tự implement các interface này, giống như chúng ta đã làm ở trên, hoặc là sử dụng các interface hiện có mà Spring Security cung cấp.

Các Implementation sẵn có

Bạn luôn có thể tự mình triển khai các interface UserDetailsService và UserDetails.

Tuy nhiên, bạn cũng có thể thay thế bằng các implementations có sẵn của Spring Security mà bạn có thể sử dụng/configure/extend/override.

  1. JdbcUserDetailsManager, là một UserDetailsService dựa trên JDBC (database). Bạn có thể cấu hình nó để khớp với cấu trúc bảng/cột user của mình .

  2. InMemoryUserDetailsManager , giữ tất cả các chi tiết user in-memory và rất tốt cho việc test.

  3. org.springframework.security.core.userdetail.User, là một implementation UserDetails mặc định, hợp lý mà bạn có thể sử dụng. Điều đó có nghĩa là có khả năng ánh xạ/sao chép giữa các entity/ bảng database của bạn và class User này. Ngoài ra, bạn có thể chỉ cần làm cho các entity của mình implement interface UserDetails.

Full UserDetails Workflow: HTTP Basic Authentication

Bây giờ, hãy quay trở lại HTTP Basic Authentication của bạn, bạn đang bảo mật ứng dụng của mình bằng Spring Security và Basic Auth. Đây là những gì sẽ diễn ra khi bạn chỉ định một UserDetailsService và cố gắng login:

  1. Extract tổ hợp username/password từ HTTP Basic Authentication header trong một bộ lọc. Bạn không phải làm bất cứ điều gì cả, nó sẽ tự diễn ra đằng sau tấm màn che.

  2. Gọi MyDatabaseUserDetailsService của bạn để tải user tương ứng từ database, được bao bọc dưới dạng đối tượng UserDetails, làm lộ hashed password của user.

  3. Lấy password được extract từ HTTP Basic Auth header, tự động băm nó và so sánh nó với hashed password từ object UserDetails của bạn. Nếu cả hai khớp, user sẽ được authenticate thành công.

Đó là tất cả gì cần để authenticate. Nhưng đợi đã, làm cách nào Spring Security băm được password từ phía client (bước 3)? Và với thuật toán nào?

PasswordEncoders

Spring Security không thể đoán một cách kỳ diệu thuật toán băm password ưa thích của bạn. Đó là lý do tại sao bạn cần chỉ định một @Bean khác, một PasswordEncoder. Giả sử nếu bạn muốn sử dụng chức năng hashed password kiểu BCrypt (mặc định của Spring Security) cho tất cả các password của mình , bạn sẽ chỉ định @Bean này trong SecurityConfig.

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
}

Điều gì sẽ xảy ra nếu bạn có nhiều thuật toán băm password, vì bạn có một số user cũ có password được lưu trữ bằng MD5 (đừng làm điều này) và những user mới hơn với Bcrypt hoặc thậm chí là thuật toán thứ ba như SHA-256? Vậy thì bạn sẽ sử dụng bộ mã hóa sau:

@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

Bộ mã hóa này hoạt động như thế nào? Nó sẽ xem xét hashed password của UserDetail (đến từ bảng database của bạn chẳng hạn), bây giờ phải bắt đầu với một {prefix}. Prefix (tiền tố) đó, chính là phương pháp hash của bạn! Bảng database của bạn sau đó sẽ trông như thế này:

Table 1. Users Table

username

password

{bcrypt}$2y$12$6t86Rpr3llMANhCUt26oUen2WhvXr/A89Xo9zJion8W7gWgZ/zA0C

{sha256}5ffa39f5757a0dad5dfada519d02c6b71b61ab1df51b4ed1f3beed6abe0ff5f6

Spring Security sẽ:

  1. Đọc các password đó và loại bỏ tiền tố ({bcrypt} hoặc {sha256}).

  2. Tùy thuộc vào giá trị tiền tố, hãy sử dụng PasswordEncoder cho chính xác (tức là BCryptEncoder hoặc SHA256Encoder)

  3. Băm password thô với PasswordEncoder đó và so sánh với password đã lưu trữ.

Đó là tất cả những gì có được với PasswordEncoders.

Summary: Having access to the user’s password

Điểm nhấn cho phần này là: nếu bạn đang sử dụng Spring Security và có thể truy cập vào password của user, thì:

  1. Chỉ định một UserDetailsService. Có thể implementation tùy chọn hay sử dụng và tự cấu hình một implementation do Spring Security cung cấp.

  2. Chỉ định một PasswordEncoder.

Đó là vắn tắt về authentication trong Spring Security.

AuthenticationProvider: Not having access to the user’s password

Ồ, giả sử rằng bạn đang sử dụng Atlassian Crowd cho việc quản lý danh tính tập trung. Điều đó có nghĩa là tất cả người dùng và mật khẩu cho tất cả ứng dụng của bạn được lưu trữ trong Atlassian Crowd và không còn trong bảng cơ sở dữ liệu của bạn nữa.

Điều này có hai tác động:

  1. Bạn không còn có mật khẩu người dùng trong ứng dụng của mình, vì bạn không thể yêu cầu Crowd cung cấp cho bạn các mật khẩu đó.

  2. Tuy nhiên, bạn có một API REST mà bạn có thể đăng nhập bằng username và password của mình (một POST request đến /rest/usermanagement/1/authentication REST endpoint).

Nếu đó là trường hợp, bạn không thể sử dụng UserDetailsService nữa, thay vào đó bạn cần triển khai và cung cấp một AuthenticationProvider @Bean.

 @Bean
    public AuthenticationProvider authenticationProvider() {
        return new AtlassianCrowdAuthenticationProvider();
    }

Một AuthenticationProvider chủ yếu bao gồm một method và một implementation ngắn gọn có thể trông như thế này:

public class AtlassianCrowdAuthenticationProvider implements AuthenticationProvider {

        Authentication authenticate(Authentication authentication)  // (1)
                throws AuthenticationException {
            String username = authentication.getPrincipal().toString(); // (1)
            String password = authentication.getCredentials().toString(); // (1)

            User user = callAtlassianCrowdRestService(username, password); // (2)
            if (user == null) {                                     // (3)
                throw new AuthenticationException("could not login");
            }
            return new UserNamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities()); // (4)
        }
	    // other method ignored
}
  1. So với method UserDetails load (), nơi bạn chỉ có quyền truy cập vào username, giờ đây bạn đã có quyền truy cập để authenticate hoàn chỉnh, thường chứa username và password.

  2. Bạn có thể làm bất cứ điều gì bạn muốn để authenticate user, như gọi một REST-service chẳng hạn.

  3. Nếu authenticate không thành công, bạn cần phải throw ra một exception.

  4. Nếu authenticate thành công, bạn cần return một UsernamePasswordAuthenticationToken được khởi tạo đầy đủ. Nó là một implementation của interface Authentication và cần phải đặt trường được authenticate thành true (mà constructor được sử dụng ở trên sẽ tự động đặt). Chúng ta sẽ đề cập đến các authorities trong chương tiếp theo.

Full AuthenticationProvider Workflow: HTTP Basic Authentication

Bây giờ, hãy quay lại HTTP Basic Authentication của bạn, có nghĩa là bạn đang bảo mật ứng dụng của mình bằng Spring Security và Basic Auth. Đây là những gì sẽ xảy ra khi bạn chỉ định AuthenticationProvider và cố gắng login:

  1. Extract tổ hợp username / password từ HTTP Basic Authentication Header trong một filter. Bạn không phải làm bất cứ điều gì để thực hiện việc đó cả, nó sẽ tự diễn ra đằng sau tấm màn che.

  2. Gọi AuthenticationProvider của bạn (ví dụ: AtlassianCrowdAuthenticationProvider) bằng username và password đó để bạn tự thực hiện authentication (ví dụ: gọi REST).

Không có quá trình hash password hoặc gì đó tương tự đang diễn ra, vì về cơ bản bạn đang ủy quyền cho bên thứ ba thực hiện kiểm tra username / password thực tế. Tóm lại đó là tất cả về AuthenticationProvider!

Summary: AuthenticationProvider

Bài học kinh nghiệm cho phần này là: nếu bạn đang sử dụng Spring Security và không có quyền truy cập vào password của user, thì hãy implement và cung cấp một @Bean AuthenticationProvider .

Authorization with Spring Security

Cho tới giờ chúng ta mới chỉ nói về authentication, ví dụ: kiểm tra username và password.

Bây giờ chúng ta hãy xem xét quyền, hay đúng hơn là vai tròquyền hạn trong Spring Security.

What is Authorization?

Hãy xem trang web thương mại điện tử điển hình của bạn. Nó có thể bao gồm các phần sau:

  • Chính trang web bán hàng. Giả sử URL của nó là www.youramazinshop.com.

  • Có thể là một khu vực dành cho các đại lý bán hàng, nơi họ có thể đăng nhập và xem một khách hàng gần đây đã mua những gì hoặc bưu kiện của họ ở đâu. URL của nó có thể là www.youramazinshop.com/callcenter.

  • Một khu vực quản trị riêng biệt, nơi quản trị viên có thể đăng nhập và quản lý các đại lý bán hàng hoặc các khía cạnh kỹ thuật khác (như chủ đề, hiệu suất, v.v.) của web bán hàng. URL của nó có thể là www.youramazinshop.com/admin.

Điều này có những ý nghĩa sau đây, vì chỉ authenticate thôi thì không còn đủ nữa:

  • Một khách hàng rõ ràng sẽ không thể truy cập vào đại lý trung tâm hoặc khu vực quản trị. Anh/cô ta chỉ được phép mua sắm trong trang web.

  • Một đại lý trung tâm sẽ không thể truy cập vào khu vực quản trị.

  • Trong khi đó, quản trị viên có thể truy cập web bán hàng, khu vực các đại lý trung tâm và khu vực quản trị.

Nói một cách đơn giản, bạn muốn cho phép các quyền truy cập khác nhau cho những user khác nhau, tùy thuộc vào quyền hạn hoặc vai trò của họ .

What are Authorities? What are Roles?

Đơn giản thôi:

  • Một authority (ở dạng đơn giản nhất) chỉ là một chuỗi, nó có thể là bất kỳ thứ gì như: user, ADMIN, ROLE_ADMIN hoặc 53cr37_r0l3.

  • Một vai trò là một authority có tiền tố ROLE_ . Vì vậy, một vai trò được gọi ADMIN cũng giống như một authority được gọi ROLE_ADMIN.

Sự phân biệt giữa vai trò và quyền hạn chỉ đơn giản là khái niệm và vì thế thường khiến những người mới tham gia Spring Security bối rối.

Why is there a distinction between roles and authorities?

Thành thật mà nói, tôi đã đọc tài liệu Spring Security cũng như một vài bài StackOverflow có liên quan đến câu hỏi này và tôi vẫn không thể cung cấp cho bạn một câu trả lời dứt khoát.

What are GrantedAuthorities? What are SimpleGrantedAuthorities?

Đương nhiên, Spring Security không cho phép bạn chỉ sử dụng chuỗi đơn thuần. Có một lớp Java đại diện cho authority string, một trong số đó là SimpleGrantedAuthority.

public final class SimpleGrantedAuthority implements GrantedAuthority {

	private final String role;

    @Override
	public String getAuthority() {
		return role;
	}
}

Chú ý: Có các lớp quyền authority khác nhau cho phép bạn lưu trữ các đối tượng bổ sung (e.g. the principal) cùng với chuỗi, tuy nhiên tôi sẽ không đề cập đến chúng ở đây. Hiện tại, chúng ta sẽ sử dụng SimpleGrantedAuthority duy nhất.

1. UserDetailsService: Where to store and get authorities?

Giả sử bạn đang lưu trữ người dùng trong ứng dụng của riêng bạn (giống như UserDetailsService), bạn sẽ có một bảng Users.

Bây giờ, bạn chỉ cần thêm một cột có tên là "authorities" vào bảng này. Trong bài viết này, tôi đã chọn một cột chuỗi đơn giản ở đây, tuy nhiên nó cũng có thể chứa nhiều giá trị phân tách bằng dấu phẩy. Theo cách khác, bạn cũng có thể có một bảng hoàn toàn riêng biệt là AUTHORITIES, nhưng với phạm vi của bài viết này, việc này cũng đủ.

Liên quan lại đến "Authorities là gì? Roles là gì?": Bạn lưu trữ các authorities, tức là chuỗi, vào cơ sở dữ liệu. Trong trường hợp này, các authorities này bắt đầu bằng tiền tố ROLE_, vì vậy, trong thuật ngữ của Spring Security, các authorities này cũng là các roles.

Table 2. Users Table With Permissions

username

password

authorities

{bcrypt}…​

ROLE_ADMIN

{sha256}…​

ROLE_CALLCENTER

Điều duy nhất còn lại phải làm là điều chỉnh UserDetailsService của bạn để read trong cột authorities đó.

public class MyDatabaseUserDetailsService implements UserDetailsService {

  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
     User user = userDao.findByUsername(username);
     List<SimpleGrantedAuthority> grantedAuthorities = user.getAuthorities().map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList()); // (1)
     return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); // (2)
  }

}
  1. Bạn chỉ cần ánh xạ bất cứ thứ gì bên trong cột database của mình vào danh sách SimpleGrantedAuthority là xong.

  2. Lần nữa, chúng ta đang sử dụng implementation UserDetails cơ bản của Spring Security ở đây. Bạn cũng có thể sử dụng class của riêng mình triển khai UserDetails và thậm chí có thể không cần phải ánh xạ sau đó.

2. AuthenticationManager: Where to store and get authorities?

Khi người dùng đến từ một ứng dụng bên thứ ba, như Atlassian Cloud, bạn sẽ cần tìm hiểu khái niệm mà họ sử dụng để hỗ trợ authorities. Atlassian Crowd đã có khái niệm "vai trò" (roles), nhưng đã đánh dấu nó là lỗi thời và thay thế bằng "nhóm" (groups).

Vì vậy, tùy thuộc vào sản phẩm cụ thể bạn đang sử dụng, bạn cần ánh xạ điều này thành một Spring Security authority trong AuthenticationProvider của bạn.

public class AtlassianCrowdAuthenticationProvider implements AuthenticationProvider {

    Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        String username = authentication.getPrincipal().toString();
        String password = authentication.getCredentials().toString();

        atlassian.crowd.User user = callAtlassianCrowdRestService(username, password); // (1)
        if (user == null) {
            throw new AuthenticationException("could not login");
        }
        return new UserNamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), mapToAuthorities(user.getGroups())); // (2)
    }
	    // other method ignored
}
  1. Lưu ý: Đây không phải là code Atlassian Crowd thực tế , nhưng vẫn phục vụ mục đích của nó. Bạn authenticate với một dịch vụ REST và lấy lại object User JSON, object này sau đó được chuyển đổi thành object atlassian.crowd.User.

  2. User đó có thể là thành viên của một hoặc nhiều nhóm, ở đây chỉ là những strings. Bạn sau đó có thể đơn giản chỉ cần ánh xạ các nhóm này với “SimpleGrantedAuthority” của Spring.

Revisiting WebSecurityConfigurerAdapter for Authorities

Cho đến nay, chúng ta đã nói nhiều về việc lưu trữ và truy xuất các authorities cho User đã authenticate trong Spring Security. Nhưng làm thế nào để bạn bảo vệ các URL với các authority khác nhau với DSL của Spring Security? Đơn giản thôi:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
            .antMatchers("/admin").hasAuthority("ROLE_ADMIN") // (1)
            .antMatchers("/callcenter").hasAnyAuthority("ROLE_ADMIN", "ROLE_CALLCENTER") // (2)
            .anyRequest().authenticated() // (3)
            .and()
         .formLogin()
           .and()
         .httpBasic();
	}
}
  1. Để truy cập khu vực /admin, bạn (tức là user) cần được authenticate có authority (một string đơn giản) ROLE_ADMIN.

  2. Để truy cập khu vực /callcenter, bạn cần phải được authenticate có authority ROLE_ADMIN HOẶC ROLE_CALLCENTER.

  3. Đối với bất kỳ yêu cầu nào khác, bạn không cần vai trò cụ thể nhưng vẫn cần được authenticate.

Lưu ý rằng đoạn code (1,2) trên tương đương với code sau:

http
    .authorizeRequests()
      .antMatchers("/admin").hasRole("ADMIN") // (1)
      .antMatchers("/callcenter").hasAnyRole("ADMIN", "CALLCENTER") // (2)
  1. Thay vì gọi “hasAuthority”, bây giờ bạn gọi “hasRole”. Ghi chú : Spring Security sẽ tìm kiếm một authority được gọi ROLE_ADMIN trên user được authenticate.

  2. Thay vì gọi “hasAnyAuthority”, bây giờ bạn gọi “hasAnyRole”. Ghi chú : Spring Security sẽ tìm kiếm một authority được gọi ROLE_ADMIN hoặc ROLE_CALLCENTER trên user đã authenticate.

hasAccess and SpEL

Cuối cùng cũng không kém phần quan trọng, cách mạnh nhất để cấu hình authorization là với method access . Nó cho phép bạn chỉ định khá nhiều biểu thức SpEL.

 http
    .authorizeRequests()
      .antMatchers("/admin").access("hasRole('admin') and hasIpAddress('192.168.1.0/24') and @myCustomBean.checkAccess(authentication,request)") // (1)
  1. Bạn đang kiểm tra xem liệu user có ROLE_ADMIN, với IP address cụ thể cũng như một bean thông thường.

Để có cái nhìn tổng quan đầy đủ về những gì có thể làm được với Spring’s Expression-Based Access Control, hãy xem tài liệu chính thức .

Common Exploit Protections

Spring Security cung cấp nhiều biện pháp bảo vệ chống lại các cuộc tấn công phổ biến. Nó bắt đầu bằng việc ngăn chặn cuộc tấn công theo thời gian (ví dụ: Spring Security luôn mã hóa mật khẩu được cung cấp trong quá trình đăng nhập, ngay cả khi người dùng không tồn tại) và kết thúc bằng việc bảo vệ chống lại các cuộc tấn công kiểm soát bộ nhớ cache, kiểm soát nội dung, clickjacking, tấn công cross-site scripting và nhiều hơn nữa.

Trong phạm vi của hướng dẫn này, không thể đi vào chi tiết từng cuộc tấn công này. Do đó, chúng ta chỉ tập trung vào một biện pháp bảo vệ cụ thể gây khó khăn cho những người mới làm quen với Spring Security nhất: tấn công Cross-Site Request Forgery (CSRF - tấn công giả mạo yêu cầu trang web).

Cross-Site-Request-Forgery: CSRF

Nếu bạn hoàn toàn mới với CSRF, bạn có thể muốn xem video này để nắm vững kiến thức về nó. Tuy nhiên, điểm quan trọng là, mặc định Spring Security bảo vệ bất kỳ yêu cầu POST (hoặc PUT/DELETE/PATCH) nào đến với một token CSRF hợp lệ.

Điều đó có ý nghĩa gì?

CSRF & Server-Side Rendered HTML

Hãy tưởng tượng một form chuyển khoản ngân hàng hay bất kỳ form nào (như form đăng nhập) cho vấn đề đó, được @Controllers của bạn hiển thị với sự trợ giúp của công nghệ tạo template như Thymeleaf hoặc Freemarker.

<form action="/transfer" method="post">  <!-- 1 -->
  <input type="text" name="amount"/>
  <input type="text" name="routingNumber"/>
  <input type="text" name="account"/>
  <input type="submit" value="Transfer"/>
</form>

Với Spring Security được bật, bạn sẽ không thể submit form đó nữa . Bởi vì CSRFFilter của Spring Security đang tìm kiếm một parameter ẩn bổ sung trên bất kỳ request POST (PUT / DELETE) nào: cái gọi là CSRF token.

Theo mặc định, nó cứ tạo ra một token như vậy cho mỗi một HTTP session và lưu trữ ở đó. Và bạn cần đảm bảo inject nó vào bất kỳ form HTML nào của mình.

CSRF Tokens & Thymeleaf

Vì Thymeleaf tích hợp tốt với Spring Security (khi được sử dụng cùng với Spring Boot), bạn chỉ cần thêm đoạn code sau vào bất kỳ form nào và bạn sẽ nhận được token tự động được inject, từ session, vào form của bạn. Thậm chí tốt hơn, nếu bạn đang sử dụng “th: action” cho form của mình, Thymeleaf sẽ tự động inject field ẩn đó cho bạn mà không cần phải thực hiện theo cách thủ công.

<form action="/transfer" method="post">  <!-- 1 -->
  <input type="text" name="amount"/>
  <input type="text" name="routingNumber"/>
  <input type="text" name="account"/>
  <input type="submit" value="Transfer"/>
  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>

<!-- OR -->

<form th:action="/transfer" method="post">  <!-- 2 -->
  <input type="text" name="amount"/>
  <input type="text" name="routingNumber"/>
  <input type="text" name="account"/>
  <input type="submit" value="Transfer"/>
</form>
  1. Ở đây, chúng ta đang add parameter CSRF theo cách thủ công.

  2. Ở đây, chúng ta đang sử dụng hỗ trợ form của Thymeleaf.

Lưu ý: Để biết thêm thông tin về hỗ trợ CSRF của Thymeleaf, hãy xem tài liệu chính thức .

CSRF & Other Templating Libraries

Tôi không thể trình bày tất cả các thư viện template trong phần này, nhưng phương sách cuối cùng là bạn luôn có thể đưa CSRFToken vào bất kỳ method @Controller nào của mình và chỉ cần add nó vào model để render nó thành một view hoặc truy cập trực tiếp dưới dạng request attribute HttpServletRequest .

@Controller
public class MyController {
    @GetMaping("/login")
    public String login(Model model, CsrfToken token) {
        // the token will be injected automatically
        return "/templates/login";
    }
}

CSRF & React or Angular

Mọi thứ có một chút khác biệt đối với ứng dụng Javascript, như ứng dụng single page React hoặc Angular. Đây là những gì bạn cần làm:

  1. Cấu hình cho Spring Security để sử dụng CookieCsrfTokenRepository, hệ thống này sẽ đưa CSRFToken vào một cookie “XSRF-TOKEN” (và gửi cookie đó đến trình duyệt).

  2. Làm cho ứng dụng Javascript của bạn nhận giá trị cookie đó và gửi nó dưới dạng “X-XSRF-TOKEN” header với mọi request POST (/ PUT / PATCH / DELETE).

Để có ví dụ đầy đủ về React copy-and-paste, hãy xem bài đăng tuyệt vời trên blog này: https://developer.okta.com/blog/2018/07/19/simple-crud-react-and-spring-boot .

Disabling CSRF

Nếu bạn chỉ cung cấp một REST API không có trạng thái (stateless) mà bảo vệ CSRF không có ý nghĩa, bạn có thể tắt hoàn toàn bảo vệ CSRF trong Spring Security. Đây là cách bạn có thể thực hiện:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends
   WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .csrf().disable();
  }
}

OAuth2

Tích hợp OAuth2 vào Spring Security là một chủ đề phức tạp và cần một giải thích chi tiết, vượt ra khỏi phạm vi của bài viết này.

Cập nhật vào ngày 21 tháng 8 năm 2020: Tôi vừa xuất bản Spring Security & OAuth2 Guide. Hãy kiểm tra nó!

Spring Integrations

Spring Security & Spring Framework

Trong hầu hết phần của bài viết này, bạn chỉ định các cấu hình bảo mật trên tầng web của ứng dụng của bạn. Bạn bảo vệ một số URL bằng cách sử dụng antMatcher hoặc regexMatchers với DSL của WebSecurityConfigurerAdapter. Đây là một phương pháp bảo mật hoàn toàn hợp lệ và tiêu chuẩn.

Ngoài việc bảo vệ tầng web, còn có khái niệm "defense in depth" (bảo vệ theo lớp). Điều đó có nghĩa là ngoài việc bảo vệ các URL, bạn cũng có thể muốn bảo vệ logic kinh doanh chính của mình. Hãy nghĩ đến: các @Controllers, @Components, @Services hoặc ngay cả @Repositories của bạn. Nói một cách ngắn gọn, đó là các Spring bean của bạn.

Method Security

Phương pháp đó được gọi là phương pháp bảo mật phương thức (method security) và hoạt động thông qua các annotation mà bạn có thể sử dụng trên bất kỳ phương thức công khai nào của các Spring bean của bạn. Bạn cũng cần bật phương pháp bảo mật phương thức một cách rõ ràng bằng cách đặt annotation @EnableGlobalMethodSecurity trên ApplicationContextConfiguration của bạn.

@Configuration
@EnableGlobalMethodSecurity(
  prePostEnabled = true, // (1)
  securedEnabled = true, // (2)
  jsr250Enabled = true) // (3)
public class YourSecurityConfig extends WebSecurityConfigurerAdapter{
}
  1. Thuộc tính prePostEnabled cho phép hỗ trợ các annotation @PreAuthorize@PostAuthorize của Spring. Hỗ trợ có nghĩa là Spring sẽ bỏ qua chú thích này nếu bạn không đặt nó thành true.

  2. Thuộc tính secureEnabled cho phép hỗ trợ annotion @Secured. Hỗ trợ ở đây có nghĩa là Spring sẽ bỏ qua chú thích này nếu bạn không đặt nó thành true.

  3. Thuộc tính jsr250Enabled cho phép hỗ trợ annotation @RolesAllowed. Hỗ trợ ở đây có nghĩa là Spring sẽ bỏ qua chú thích này nếu bạn không đặt thành true.

What is the difference between @PreAuthorize, @Secured and @RolesAllowed?

@Secured và @RolesAllowed là hai annotation tương tự nhau, tuy nhiên @Secured là một annotation cụ thể của Spring đi kèm với phụ thuộc spring-security-core, trong khi @RolesAllowed là một annotation được chuẩn hóa, thuộc javax.annotation-api. Cả hai annotation đều nhận một chuỗi quyền/ vai trò làm giá trị đầu vào.

@PreAuthorize và @PostAuthorize cũng là các annotation (mới hơn) cụ thể của Spring và mạnh mẽ hơn các annotation trên, vì chúng có thể chứa không chỉ các quyền/ vai trò mà còn bất kỳ biểu thức SpEL hợp lệ nào.

Cuối cùng, tất cả các annotation này sẽ ném ra một ngoại lệ AccessDeniedException nếu bạn cố gắng truy cập vào một phương thức được bảo vệ mà không có đủ quyền/ vai trò.

Vì vậy, cuối cùng, hãy xem các annotation này hoạt động như thế nào.

@Service
public class SomeService {

    @Secured("ROLE_CALLCENTER") // (1)
    // == @RolesAllowed("ADMIN")
    public BankAccountInfo get(...) {

    }

    @PreAuthorize("isAnonymous()") // (2)
    // @PreAuthorize("#contact.name == principal.name")
    // @PreAuthorize("ROLE_ADMIN")
    public void trackVisit(Long id);

    }
}
  1. Như đã đề cập, @Secured có authority/role làm tham số. @RolesAllowed cũng tương tự như vậy. Lưu ý : Hãy nhớ rằng @RolesAllowed("ADMIN") sẽ check một authority được cấp ROLE_ADMIN.

  2. Như đã đề cập, @PreAuthorize ngoài chấp nhận các authorities còn có bất kỳ biểu thức SpEL valid nào. Đối với danh sách các biểu thức security tích hợp sẵn phổ biến như isAnonymous(), thay vì viết các biểu thức SpEL của riêng bạn, hãy xem tài liệu chính thức .

Which annotation should I use?

Ở mức cơ bản, việc sử dụng @Secured hay @RolesAllowed là một vấn đề về đồng nhất, không phải là ràng buộc quá nhiều vào các API cụ thể của Spring (đây là một lập luận thường được đưa ra).

Nếu bạn đang sử dụng @Secured, hãy tuân thủ nó và không sử dụng @RolesAllowed trong 28% các bean khác nhau của bạn một cách không đồng bộ chỉ với mục tiêu tiêu chuẩn hóa, nhưng không thực sự triển khai hoàn toàn.

Để bắt đầu, bạn luôn có thể sử dụng @Secured và chuyển sang @PreAuthorize ngay khi cần thiết. Điều này cho phép bạn dễ dàng thay đổi và mở rộng quyền truy cập của các phương thức của mình khi cần thiết, mà không cần phải thay đổi quá nhiều mã nguồn hiện có.

Spring Security & Spring Web MVC

Đối với việc tích hợp với Spring WebMVC, Spring Security cho phép bạn thực hiện một số việc sau:

  1. Ngoài antMatchers và regexMatchers, bạn cũng có thể sử dụng mvcMatchers. Sự khác biệt là, trong khi antMatchers và regexMatchers về cơ bản để match các chuỗi URI với các ký tự đại diện, mvcMatchers hoạt động giống hệt như @RequestMappings.

  2. Inject principal hiện đã được authenticate của bạn vào method @Controller / @RestController.

  3. Inject CSRFToken session hiện tại của bạn vào một method @Controller / @RestController.

  4. Handle chính xác bảo mật để xử lý request không đồng bộ .

@Controller
public class MyController {

    @RequestMapping("/messages/inbox")
    public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser, CsrfToken token) {  // (1) (2)

    // .. find messages for this user and return them ...
    }
}
  1. @AuthenticationPrincipal sẽ inject một principal nếu user được authenticate hoặc null nếu không có user nào được authenticate. Principal này là object đến từ UserDetailsService / AuthenticationManager của bạn!

  2. Hoặc bạn có thể inject CSRFToken session hiện tại vào từng method.

Nếu bạn không sử dụng annotation @AuthenticationPrincipal, bạn sẽ phải tự mình tìm principal, thông qua SecurityContextHolder. Một kỹ thuật thường thấy trong di sản của các ứng dụng Spring Security.

@Controller
public class MyController {

    @RequestMapping("/messages/inbox")
    public ModelAndView findMessagesForUser(CsrfToken token) {
         SecurityContext context = SecurityContextHolder.getContext();
         Authentication authentication = context.getAuthentication();

         if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
             CustomUser customUser = (CustomUser) authentication.getPrincipal();
             // .. find messages for this user and return them ...
         }

         // todo
    }
}

Spring Security & Spring Boot

Spring Boot thực sự chỉ tiền cấu hình sẵn Spring Security cho bạn khi bạn thêm phụ thuộc spring-boot-starter-security vào dự án Spring Boot của mình.

Ngoài ra, tất cả cấu hình bảo mật được thực hiện bằng các khái niệm cơ bản của Spring Security (hãy nghĩ đến: WebSecurityConfigurerAdapter, quy tắc xác thực và phân quyền), không liên quan trực tiếp đến Spring Boot.

Vì vậy, tất cả những gì bạn đọc trong hướng dẫn này đều áp dụng trực tiếp khi sử dụng Spring Security với Spring Boot. Và nếu bạn không hiểu rõ về Security cơ bản, đừng mong đợi hiểu đúng cách cách hai công nghệ này hoạt động cùng nhau.

Spring Security & Thymeleaf

Spring Security tích hợp tốt với Thymeleaf. Nó cung cấp một Thymeleaf dialect đặc biệt cho Spring Security, cho phép bạn đặt các biểu thức bảo mật trực tiếp vào các mẫu HTML Thymeleaf của bạn.

<div sec:authorize="isAuthenticated()">
  This content is only shown to authenticated users.
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')">
  This content is only shown to administrators.
</div>
<div sec:authorize="hasRole('ROLE_USER')">
  This content is only shown to users.
</div>

Để có cái nhìn tổng quan đầy đủ và chi tiết hơn về cách cả hai công nghệ hoạt động cùng nhau, hãy xem tài liệu chính thức .

Via: Marco Behle

Last updated