Học viên : Nguyễn Thu Hằng (Java 08)
Email : 14thuhang@gmail.com
Bài viết gốc : https://www.baeldung.com/spring-security-expressions

1. Giới thiệu

Trong bài viết này chúng ta cùng tìm hiểu về Spring Security Expressions, và các ví dụ thực tế bằng cách sử dụng các biểu thức này.

Trước khi xem xét các triển khai phức tạp hơn, chẳng hạn như ACL. Điều quan trọng là phải nắm chắc security expressions, vì chúng khá linh hoạt và mạnh mẽ nếu được sử dụng đúng cách.

2. Maven Dependencies

Để sử dụng Spring Sercurity ta cần thêm dependency spring security web vào file pom.xml trong dự án của mình

<dependencies>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>5.6.0</version>
   </dependency>
</dependencies>

Version mới nhất có thể được tìm kiếm tại đây.

Xin lưu ý rằng dependency này chỉ bao gồm Spring Security; chúng ta sẽ cần thêm spring-corespring-context trong một ứng dụng web đầy đủ.

3. Cấu hình

Đầu tiên hãy xem cấu hình java

Chúng tôi sẽ extends WebSecurityConfigurerAdapter để chúng tôi có tùy chọn kết nối vào bất kỳ method nào mà lớp cơ sở cung cấp:

@Configuration
@EnableAutoConfiguration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
    ...
}

Tất nhiên, chúng tôi cũng có thể thực hiện bằng cách sử dụng cấu hình XML:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans ...>
    <global-method-security pre-post-annotations="enabled"/>
</beans:beans>

4. Web Security Expressions

Bây giờ hãy tìm hiể các security expressions trong Spring Sercurity :

  • hasRole, hasAnyRole
  • hasAuthority, hasAnyAuthority
  • permitAll, denyAll
  • isAnonymous, isRememberMe, isAuthenticated, isFullyAuthenticated
  • principal, authentication
  • hasPermission

4.1. hasRole, hasAnyRole

Các biểu thức này có trách nhiệm xác định kiểm soát truy cập hoặc ủy quyền cho các URL và method cụ thể trong ứng dụng của chúng tôi:

 @Override
 protected void configure(final HttpSecurity http) throws Exception {
   ...
   .antMatchers("/auth/admin/*").hasRole("ADMIN")
   .antMatchers("/auth/*").hasAnyRole("ADMIN","USER")
   ...
 }

Trong ví dụ trên, chúng tôi đã chỉ định quyền truy cập vào tất cả các link bắt đầu bằng /auth/ với những người dùng đăng nhập với role USER hoặc role ADMIN. Hơn nữa, để truy cập các link bắt đầu bằng /auth/admin/, chúng ta cần có role ADMIN trong hệ thống.

Chúng ta có thể cấu hình tương tự trong file XML bằng cách viết:

<http>
    <intercept-url pattern="/auth/admin/*" access="hasRole('ADMIN')"/>
    <intercept-url pattern="/auth/*" access="hasAnyRole('ADMIN','USER')"/>
</http>

4.2. hasAuthority, hasAnyAuthority

Roles và authorities là tương tự nhau trong Spring.

Sự khác biệt chính là roles có ngữ nghĩa đặc biệt. Bắt đầu với Spring Security 4, tiền tố ‘ROLE_‘ được thêm tự động (nếu chưa có) bằng bất kỳ method nào liên quan đến role.

Vì vậy, hasAuthority(‘ROLE_ADMIN’) tương tự như hasRole(‘ADMIN’) vì tiền tố ‘ROLE_’ được tự động thêm vào.

Lợi ích của việc sử dụng authorities là chúng ta không phải sử dụng tiền tố ROLE_.

Dưới đây là một ví dụ nhanh về việc định nghĩa user với authorities cụ thể:

 @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   auth.inMemoryAuthentication()
     .withUser("user1").password(encoder().encode("user1Pass"))
     .authorities("USER")
     .and().withUser("admin").password(encoder().encode("adminPass"))
     .authorities("ADMIN");
}

Sau đó, chúng ta có thể sử dụng các authorities expressions sau:

@Override
protected void configure(final HttpSecurity http) throws Exception {
  ...
  .antMatchers("/auth/admin/*").hasAuthority("ADMIN")
  .antMatchers("/auth/*").hasAnyAuthority("ADMIN", "USER")
  ...
}

Như đã thấy thấy, chúng tôi không đề cập đến các role ở đây.

Ngoài ra, bắt đầu với Spring 5, chúng ta cần một bean PasswordEncoder:

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

Cuối cùng, chúng tôi cũng có thể thực hiện tương tự bằng cách sử dụng cấu hình XML:

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="user1" password="user1Pass" authorities="ROLE_USER"/>
            <user name="admin" password="adminPass" authorities="ROLE_ADMIN"/>
        </user-service>
    </authentication-provider>
</authentication-manager>
<bean name="passwordEncoder" 
  class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<http>
    <intercept-url pattern="/auth/admin/*" access="hasAuthority('ADMIN')"/>
    <intercept-url pattern="/auth/*" access="hasAnyAuthority('ADMIN','USER')"/>
</http>

4.3. permitAll, denyAll

Hai chú thích này cũng khá đơn giản. Chúng tôi có thể cho phép hoặc từ chối quyền truy cập vào một số URL trong ứng dụng của chúng tôi.

Hãy xem ví dụ:

...
.antMatchers("/*").permitAll()
...

Với cấu hình này, chúng tôi sẽ cho phép tất cả người dùng (cả ẩn danh và đã đăng nhập) truy cập vào trang bắt đầu bằng '/ ’ (ví dụ: trang chủ của chúng tôi).

Chúng tôi cũng có thể từ chối quyền truy cập vào toàn bộ URL của mình:

...
.antMatchers("/*").denyAll()
...

Và một lần nữa, chúng ta cũng có thể thực hiện cấu hình tương tự với XML:

<http auto-config="true" use-expressions="true">
    <intercept-url access="permitAll" pattern="/*" /> <!-- Choose only one -->
    <intercept-url access="denyAll" pattern="/*" /> <!-- Choose only one -->
</http>

4.4. isAnonymous, isRememberMe, isAuthenticated, isFullyAuthenticated

Trong mục này, chúng tôi sẽ tập trung vào các biểu thức liên quan đến trạng thái đăng nhập của người dùng. Hãy bắt đầu với một người dùng không đăng nhập vào trang của chúng tôi. Bằng cách chỉ định cấu hình sau, chúng tôi sẽ cho phép tất cả người dùng truy cập trang chính của chúng tôi:

...
.antMatchers("/*").anonymous()
...

Đây là điều tương tự trong cấu hình XML:

<http>
    <intercept-url pattern="/*" access="isAnonymous()"/>
</http>

Nếu chúng tôi muốn bảo mật trang web để mọi người sử dụng nó cần đăng nhập, chúng tôi sẽ cần sử dụng method isAuthenticated():

...
.antMatchers("/*").authenticated()
...

Hoặc chúng ta có thể sử dụng cấu hình XML:

<http>
    <intercept-url pattern="/*" access="isAuthenticated()"/>
</http>

Chúng tôi cũng có hai biểu thức bổ sung, isRememberMe()isFullyAuthenticated(). Thông qua việc sử dụng cookie, Spring cho phép remember-me, vì vậy không cần phải đăng nhập vào hệ thống mỗi lần. Chúng ta có thể đọc thêm về Remember Me tại đây.

Để cấp quyền truy cập cho người dùng đã đăng nhập bằng remember me, chúng tôi có thể sử dụng:

...
.antMatchers("/*").rememberMe()
...

Hoặc chúng ta có thể sử dụng cấu hình XML:

<http>
    <intercept-url pattern="*" access="isRememberMe()"/>
</http>

Cuối cùng, một số phần trong ứng dụng của chúng tôi yêu cầu xác thực lại người dùng, ngay cả khi người dùng đã đăng nhập. Ví dụ: giả sử một người dùng muốn thay đổi cài đặt hoặc thông tin thanh toán; cách tốt nhất là yêu cầu xác thực thủ công trong các chức năng nhạy cảm của hệ thống.

...
.antMatchers("/*").fullyAuthenticated()
...

Hoặc chúng ta có thể sử dụng cấu hình XML:

<http>
    <intercept-url pattern="*" access="isFullyAuthenticated()"/>
</http>

4.5. principal, authentication

Các biểu thức này cho phép chúng tôi truy cập principal object đại diện cho người dùng được ủy quyền (hoặc ẩn danh) hiện tại và đối tượng Authentication hiện tại từ SecurityContext.

Ví dụ: chúng tôi có thể sử dụng principal để load email, hình đại diện của user hoặc bất kỳ dữ liệu nào khác có thể truy cập được từ người dùng đã đăng nhập.

authentication cung cấp thông tin đầy đủ về đối tượng Authentication, cùng với các quyền được cấp của nó.

Cả hai biểu thức này đều được mô tả chi tiết hơn trong bài viết Lấy thông tin người dùng trong Spring Security.

4.6. hasPermission APIs

Biểu thức này nhằm mục đích trở thành cầu nối giữa hệ thống biểu thức (expression system) và hệ thống ACL của Spring Security, cho phép chúng tôi chỉ định các ràng buộc expression system trên các domain object riêng lẻ dựa trên các abstract permissions.

Hãy xem một ví dụ. Hãy tưởng tượng rằng chúng tôi có một chức năng cho phép hợp tác viết bài, với một tác giả chính, người quyết định bài viết nào được đề xuất bởi các tác giả nên được xuất bản.

Để cho phép sử dụng một chức năng như vậy, chúng tôi có thể tạo các method sau bằng method kiểm soát truy cập:

@PreAuthorize("hasPermission(#articleId, 'isEditor')")
public void acceptArticle(Article article) {
…
}

Chỉ người dùng được ủy quyền mới có thể gọi method này và họ cần có quyền isEditor trong ứng dụng.

Chúng ta cũng cần cấu hình rõ ràng PermissionEvaluator trong ứng dụng của chúng ta, trong đó customInterfaceImplementation sẽ là class triển khai PermissionEvaluator:

<global-method-security pre-post-annotations="enabled">
   <expression-handler ref="expressionHandler"/>
</global-method-security>

<bean id="expressionHandler"
   class="org.springframework.security.access.expression
     .method.DefaultMethodSecurityExpressionHandler">
   <property name="permissionEvaluator" ref="customInterfaceImplementation"/>
</bean>

Tất nhiên, chúng ta cũng có thể làm điều này với cấu hình Java:

@Override
protected MethodSecurityExpressionHandler expressionHandler() {
    DefaultMethodSecurityExpressionHandler expressionHandler = 
      new DefaultMethodSecurityExpressionHandler();
    expressionHandler.setPermissionEvaluator(new CustomInterfaceImplementation());
    return expressionHandler;
}

5. Tổng kết

Bài viết này là một giới thiệu toàn diện và hướng dẫn về Spring Security Expressions…

Tất cả các ví dụ được thảo luận ở đây đều có sẵn trong GitHub project.