+13

[Spring security] - Authentication and Authorization In Depth

Tiếp tục về chủ đề bảo mật nằm trong loạt bài viết về Spring Security JWT.

Mọi người có thể tìm đọc các bài viết liên quan tại:

Trước khi trở lên master spring security, chúng ta nên hiểu 3 khái niệm quan trọng:

  • Authentication
  • Authorization
  • Servlet Filter

2 khái niệm Authentication (xác thực) & Authorization (phân quyền) mình sẽ không nhắc tới ở đây nữa mà sẽ đi vào chi tiết luôn.

Lưu ý: Bài viết khá là dài, các bạn có thể theo dõi theo 2 mục lớn là Authentication và Authorization. Sau khi đọc xong mục lớn thứ nhất có thể nghỉ ngơi, thư giãn hôm sau đọc tiếp mục lớn thứ hai.


Authentication

1. Servlet Filter

Bản chất của Servlet (ví dụ như DispatcherServlet) là giúp điều hướng các HTTP Request (từ Browser, Native App, ...) đến @Controllers hoặc @RestControllers ứng dụng của chúng ta.

Vấn đề là: Phần bảo mật, mã hóa sẽ không được xử lý ở DispatcherServlet và chúng ta cũng rất có thể không muốn xử lý chúng ở trong @Controllers hay @RestControllers 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 đến tầng Controllers.

May mắn thay, trong Spring chúng ta có thể đặt các Filter (bộ lọc) lên trước các Servlet để lọc, validate mọi request HTTP trước khi nó truy cập vào servlet.

Một SecurityFilter đơn giản có thể như sau:

Ví dụ một SecurityFilter có 4 tác vụ với implement đơn giản:

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. Filter sẽ extract username/password từ request (thông qua Basic Authen Http Header, hoặc các field trong form, hoặc cookie, ...)

  2. Filter xác thực bằng cách đối chiếu username/password vừa extract với một thứ gì đó, chẳng hạn như thông tin trong database.

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

  4. Nếu request vẫn hợp lệ sau tất cả các lần kiểm tra này, thì Filter có thể cho phép request chuyển đến DispatcherServlet (hay tầng Controllers)

FilterChains

Trong thực tế thì sẽ có rất nhiều Filter, sau đó nối chuỗi chúng lại với nhau gọi là chuỗi bộ lọc (FilterChain). Ví dụ một request sẽ phải đi qua nhiều filter:

  1. Đầu tiên, đi qua LoginMethodFilter…
  2. Sau đó, đi qua AuthenticationFilter…
  3. Sau đó, chuyển qua AuthorizationFilter…
  4. Cuối cùng, cho phép request tới tầng Servlet

Với một chuỗi bộ lọc (FilterChain) như vậy thì về cơ bản, chúng ta có thể xử lý mọi vấn đề authentication hay authorization trong ứng dụng tại các class Filter này mà không cần thay đổi việc triển khai ứng dụng thực tế cũng như các nghiệp vụ từ tầng @RestControllers / @Controllers trở xuống tầng Service, DAO, ....

FilterChain và Security Configuration

Spring's DefaultSecurityFilterChain

Khi thiết lập Spring security và khởi động ứng dụng ta sẽ thấy dòng log như sau:

2021-09-10 10:34:27.392  INFO 16760 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request,.....

Đây là FilterChain mặc định của Spring security. Chúng ta có cài đặt mở rộng FilterChain này bao gồm 15 Filter khác nhau. Khi đó một Request đến sẽ đi qua tất cả 15 Filter này trước khi nó đến được tầng Controller. Dưới đây là follow của 15 Filter theo thứ tự:

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

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

  • DefaultLoginPageGeneratingFilter : Tạo trang login mặc định (nếu chúng ta không disable tính năng đó). Đây là lý do tại sao chúng ta nhận được trang đăng nhập mặc định khi bật Spring Security.

  • DefaultLogoutPageGeneratingFilter : Tạo trang logout mặc định, (nếu bạn không disable tính năng đó).

  • FilterSecurityInterceptor : Thực hiện authorization.

  • .... và rất nhiều các Filter khác bao gồm cả những Filter do chúng ta custom sẽ tham gia vào quá trình authen và authorize.

Cấu hình Spring Security như thế nào?

Để cấu hình Spring security ta sẽ tạo một class:

  1. Được chú thích (gán annotation) @EnableWebSecurity.
  2. Kế thừa class WebSecurityConfigurerAdapter, cung cấp một số phương thức để chúng ta chỉ định những URI nào trong ứng dụng cần được bảo vệ hay một số tính năng bổ sung như CsrfFilter,....

Ví dụ WebSecurityConfigurerAdapter cơ bản:

@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. Chú thích @Configuration đánh dấu đây là 1 class config, @EnableWebSecurity đánh dấu enable Spring security, kế thừa từ class WebSecurityConfigurerAdapter.
  2. Bằng cách override phương thức cấu hình (HttpSecurity) của adapter, chúng ta có thể cấu hình FilterChain của theo cách của mình.
  3. Tất cả các request đến endpoind //home được cấp phép - người dùng không phải xác thực. Chúng ta đang sử dụng antMatcher , có nghĩa là có thể sử dụng ký tự đại diện (*, \ * \ *,?) kiểu regex.
  4. Mọi request khác đều cần người dùng xác thực (authenticated) trước , tức là user cần login.
  5. Chúng ta đ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 được tạo tự động của Spring Security). Bất kỳ ai cũng có thể truy cập trang login mà không cần phải đăng nhập trước (permitAll).
  6. Tương tự như (5) nhưng là với trang logout
  7. Cho phép Basic Auth, nghĩa là gửi một HTTP Basic Auth Header để authenticate.

Lưu ý : Chúng ta sẽ không cần phải override ngay lập tức phương thức configure của adapter, vì nó có một implementation mặc định như dưới đây:

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)
        }
}
  • anyRequest(): Để truy cập bất kỳ URI nào trên ứng dụng cần xác thực (authenticated()).
  • formLogin(): Form login mặc định được bật.
  • httpBasic()) : cho phép Basic Authen bằng username/password

Cấu hình mặc định này là lý do tại sao ứng dụng của sẽ yêu cầu login ngay sau khi thêm Spring Security.

Authentication trong Spring hoạt động như thế nào?

Ở tầng Filter, sau khi extract được username/password từ request. Vậy các class Filter này authenicate thông tin này như thế nào. Trước hết hãy nói về các kịch bản xác thực của Spring:

  1. Mặc định: Chúng ta có thể truy cập (hashed) password của user, bởi vì thông tin chi tiết của user (username, password) được lưu trong một bảng database của chúng ta quản lý chẳng hạn
  2. Ít phổ biến: Chúng ta không thể truy cập password (hashed) của user. Đây là trường hợp nếu user và password của người dùng được lưu trữ ở một nơi khác (của một bên thứ 3 cung cấp Rest api phục vụ việc xác thực).
  3. Phổ biến: Sử dụng OAuth2 hoặc đăng nhập bằng Google/Twitter,.... (OpenID), kết hợp với JWT.

Lưu ý: Tùy thuộc vào từng kịch bản, chúng ta sẽ chỉ định các @Beans khác nhau để Spring Security hoạt động.

Hãy cùng xem xét 2 kịch bản đầu tiên.

1. Có quyền truy cập vào password của user

Giả sử chúng ta có một bảng trong database bạn lưu trữ thông tin người dùng. Bảng này có một số column, nhưng quan trọng nhất là column username và password (lưu trữ hashed password).

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

Spring security cần xác định hai bean để thiết lập và chạy authentication là UserDetailsServicePasswordEncoder.

Bean UserDetailsService (Bean thứ nhất)

@Bean
public UserDetailsService userDetailsService() {
    return new MyDatabaseUserDetailsService(); // (1)
}
  1. MyDatabaseUserDetailsService implements interface UserDetailsService - là một interface chỉ có 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. Load UserDetails qua username của người dùng.
  2. Interface UserDetails có phương thức để lấy (hashed) password và lấy username (và một số phương thức khác).

Chúng ta có thể tự implement các interface này để thêm các thông tin và xử lý theo mong muốn, 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.
Ví dụ, tạo một implements của interface UserDetails để thêm thông tin email:

public class UserDetailsImpl implements UserDetails {
    private static final long serialVersionUID = 1L;

    private Long id;
    private String username;
    private String email;
    @JsonIgnore
    private String password;
    private Collection<? extends GrantedAuthority> authorities;
    ...
}    

Một số Implementation sẵn có Spring cung cấp

Chúng ta luôn có thể tự mình implements các interface UserDetailsServiceUserDetails.

Tuy nhiên, chúng ta cũng có thể thay thế bằng các implementations có sẵn của Spring Security để có thể sử dụng configure/extend/override như.

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

  • InMemoryUserDetailsManager : lưu thông tin chi tiết của user trong memory (thường dùng để test)

  • org.springframework.security.core.userdetail.User: là một implementation mặc định của UserDetails chúng ta có thể sử dụng. Chúng ta có thể ánh xạ/sao chép giữa các entity/ bảng trong database và class User này. Hoặc chúng ta có thể chỉ cần làm cho các entity của mình implement interface UserDetails tương tự ví dụ bên trên.

Quy trình xử lý của UserDetailsService

Giả sử ứng dụng được bảo mật bới Spring security và Basic Authen/ Khi chúng ta chỉ định một UserDetailsService và cố gắng login thì quá trình xử lý sẽ như sau:

  1. Extract username/password từ HTTP Basic Authentication header trong Filter. Chúng ta không phải làm bất cứ điều gì cả, tất cả tự động xử lý.
  2. Sử dụng MyDatabaseUserDetailsService để lấy thông tin user tương ứng từ database, được wrap dưới dạng đối tượng UserDetails (có thể là thông qua các các implementation của UserDetails), từ đó có thể lấy được 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 vừa lấy được. Nếu cả hai khớp, user sẽ được authenticate thành công.

Đó là tất cả các bước cần để authenticate. Nhưng 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?

Bean PasswordEncoder (Bean thứ 2)

Spring Security không thể biết chúng ta sử dụng thuật toán băm password nào. Vì thế chúng ta cần chỉ định một @Bean khác là PasswordEncoder. Giả sử nếu 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 chúng ta sẽ chỉ định @Bean này trong SecurityConfig.

// khai báo Bean
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

// sử dụng Bean
    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

Nếu chúng ta có nhiều thuật toán băm password cùng được sử dụng. Ví dụ có một số user cũ có password được lưu trữ bằng MD5 (không nên dùng thuật toán này) và những user mới hơn dùng Bcrypt hoặc 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 sẽ xem xét hashed password của UserDetail (ví dụ là lưu trong DB), mỗi hashed password có một Prefix (tiền tố), đó là phương pháp hash của thuật toán! Thông tin được lưu trong database sẽ trông như thế này:

Username Password
[email protected] {bcrypt}$2y$12$6t86Rpr3llMANhCUt26oUen2WhvXr/A89Xo9zJion8W7gWgZ/zA0C
[email protected] {sha256}5ffa39f5757a0dad5dfada519d02c6b71b61ab1df51b4ed1f3beed6abe0ff5f6

Spring Security sẽ:

  • Đọc các password và loại bỏ tiền tố ({bcrypt} hoặc {sha256}).
  • Tùy thuộc vào giá trị tiền tố để lựa chọn sử dụng PasswordEncoder chính xác (tức là BCryptEncoder hoặc SHA256Encoder)
  • Băm password lấy từ request với PasswordEncoder được lựa chọn và so sánh với password đã lưu trữ trong Database.

Tóm tắt:
Nếu chúng ta sử dụng Spring Security và có thể truy cập vào password của user, thì cần:

  • Chỉ định một UserDetailsService (có thể implementation do chúng ta custom hoặc một implementation do Spring Security cung cấp).
  • Chỉ định một PasswordEncoder.

2. Không có quyền truy cập vào password của user

Giả sử rằng ta sử dụng một bên thứ 3 để cung cấp thông tin người dùng (khi này không lưu trong database của chúng ta nữa). Điều này có nghĩa rằng:

  • Chúng ta không có password user trong ứng dụng của mình nữa, vì không thể yêu cầu bên thứ 3 cung cấp thông tin đó được.
  • Tuy nhiên, chúng ta có một API REST mà ta có thể đăng nhập bằng username và password của mình. (Một POST request đến điểm cuối /rest/usermanagement/1/authentication của REST).

Bây giờ ta không thể sử dụng UserDetailsService thay vào đó ta cần implements và cung cấp một @BeanAuthenticationProvider.

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

Một implementation của AuthenticationProvider sẽ bao gồm một phương thức như sau:

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 UserDetailsService khi sử dụng phương thức loadUserByUsername(String username) ta chỉ có quyền truy cập vào username, giờ đây ta đã có quyền truy cập để authenticate hoàn chỉnh (thường chứa username và password).
  2. Chúng ta có thể làm bất cứ điều gì để authenticate user, ví dụ như gọi một REST-service chẳng hạn.
  3. Nếu authenticate không thành công, chúng ta cần phải throw ra một exception.
  4. Nếu authenticate thành công, chúng ta 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 (constructor được sử dụng ở trên sẽ tự làm việc này). Chúng ta sẽ đề cập đến các authorities trong chương tiếp theo.

Quy trình xử lý của AuthenticationProvide

  1. Extract thông tin username / password từ HTTP Basic Authentication Header trong Filter. Chúng không phải làm bất cứ điều gì để thực hiện việc đó cả, quá trính sẽ diễn ra tự động.
  2. Sử dụng AuthenticationProvider (trong ví dụ trên là: AtlassianCrowdAuthenticationProvider) và cung cấp thông tin Authentication bằng username và password đó để bạn tự thực hiện authenticate (ví dụ: gọi REST).

Trường hợ này không có quá trình hash password hoặc bước xử lý gì đó khác tương tự xảy ra, vì về cơ bản chúng ta đang ủy quyền cho bên thứ ba thực hiện kiểm tra username / password. Đó là tất cả về AuthenticationProvider.

Tóm tắt:

Nếu chúng ta đ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

Authentication là quá trình xác thực, kiểm tra thông tin người dùng username/password khi đăng nhập vào hệ thống. Tuy nhiên trong một số bài toán lớn thì điều này là chưa đủ, khi vào được hệ thống rồi thì tùy thuộc vào từng tài khoản với vai trò quyền hạn khác nhau sẽ được truy cập vào các phần khác nhau. Ví dụ:

  • Tài khoản khách hàng sẽ không thể truy cập vào đại lý trung tâm hoặc khu vực quản trị. Khách hàng chỉ được phép mua sắm trong trang web.
  • Tài khoản đại lý trung tâm sẽ không thể truy cập vào khu vực quản trị.
  • Trong khi đó, tài khoản 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ị.

Khái niệm Authorities?

Theo mình hiểu Role hay Authority ở đây đều có thể hiểu là quyền hạn. Tạm hiểu nó là một chuỗi String như: "user", "ADMIN", "ROLE_USER", "ROLE_ADMIN", .... Trong một hệ thống một người dùng có thể có nhiều vai trò (role), một vai trò có thể có nhiều quyền hạn (permission). Xong suy cho cùng thì phân quyền đều có thể dựa trên vai trò (role) hoặc quyền hạn (permission) nên gọi chung là authorities.

GrantedAuthority là gì? SimpleGrantedAuthority là gì?

Trong Java có một class đại diện cho String authority là SimpleGrantedAuthority

public final class SimpleGrantedAuthority implements GrantedAuthority {

 private final String role;

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

Ta sẽ lấy được thông tin authorities này thông qua UserDetailsServiceUserDetails.

1. Sử dụng UserDetailsService (khi lưu trữ và get authorities ở trong ứng dụng của chúng ta)

Giả sử trong DB của chúng ta có bảng User lưu thông tin của người dùng. Giờ chúng ta thêm một column nữa có tên "authorities" vào đó. Đó có thể là một column lưu dữ liệu String đơn gian, nó có thể chứa nhiều giá trị được phân tách bằng dấu phẩy. Ngoài ra, chúng ta cũng có thể có một bảng AUTHORITIES hoàn toàn tách biệt, nhưng đối với phạm vi của bài viết này thế này là đủ.

Username Password authorities
[email protected] {bcrypt}$2y$12$6t86Rpr3llMANhCUt26oUen2WhvXr/A89Xo9zJion8W7gWgZ/zA0C ROLE_ADMIN
[email protected] {sha256}5ffa39f5757a0dad5dfada519d02c6b71b61ab1df51b4ed1f3beed6abe0ff5f6 ROLE_CALLCENTER

Trong implementation của interface UserDetailsService điều duy nhất phải làm là thay đổi cách lấy thông tin User trong Database để có thể lấy được thông tin của authorities nữa (nếu là tạo ra bảng AUTHORITIES thì có thể dùng join để lấy).

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)
  }
}
  • Chúng ta 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.
  • Chúng ta đang sử dụng implementation UserDetails cơ bản của Spring Security ở đây. Chúng ta cũng có thể sử dụng class của riêng mình implements UserDetails và thậm chí có thể không cần phải ánh xạ sau đó.

2. Sử dụng AuthenticationManager (khi thông tin user đến từ ứng dụng thứ 3)

Khi user đến từ một ứng dụng của bên thứ ba, chúng ta sẽ cần tìm hiểu xem họ đang sử dụng khái niệm gì để hỗ trợ các authorities.

Vì vậy, tùy thuộc vào sản phẩm thực tế chúng ta đang sử dụng, chúng ta cần ánh xạ đến Spring Security authority, trong AuthenticationProvider.

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
}
  • Giả sử chúng ta 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.
  • Từ thông tin User này ta sẽ xử lý để lấy được thông tin "SimpleGrantedAuthority".

Cấu hình WebSecurityConfigurerAdapter với authorities

Bảo mật các URL với authority khác nhau

@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();
  }
}
  • Để truy cập khu vực /admin, người dùng cần được xác thực (authenticate) và có quyền (authority) là ROLE_ADMIN.
  • Để truy cập khu vực /callcenter, người dùng cần phải được xác thực authenticate và có quyền (authority) là ROLE_ADMIN HOẶC ROLE_CALLCENTER.
  • Đối với bất kỳ yêu cầu truy cập nào khác không cần vai trò cụ thể nhưng vẫn cần được xác thực (authenticate).

Đoạn code trên tương đương:

http.authorizeRequests()
    .antMatchers("/admin").hasRole("ADMIN") // (1)
    .antMatchers("/callcenter").hasAnyRole("ADMIN", "CALLCENTER") // (2)
  1. Thay vì gọi "hasAuthority", bây giờ sẽ 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ờ sẽ 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 và SpEL

Cách mạnh nhất để cấu hình authorization là sử dụng 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. Chúng ta đ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.

Chi tiết tìm hiểu thêm tại đây.

Tổng kết

Trên đây là hướng dẫn để mọi người hiểu hơn về authentication và authorization trong Spring security. Hy vọng bạn sẽ hiểu được ý tưởng tổng thể của bài viết và áp dụng nó vào dự án của bạn một cách thoải mái.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.