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-login

1. Giới thiệu

Trong ví dụ này sẽ tập trung vào phần login trong spring security. Chúng ta sẽ xây dựng dựa trên ví dụ Spring MVC trước đó vì đó là một phần cần thiết trong việc xây dựng cơ chế đăng nhập của một ứng dụng web.

2. Maven Dependencies

Khi làm việc với Spring boot, spring-boot-starter-security sẽ tự động thêm vào các dependencies, chẳng hạn như spring-security-core, spring-security-web, và spring-security-config

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

Nếu chúng ta không sử dụng Spring Boot, vui lòng xem bài viết Spring Security with Maven, bài viết mô tả cách thêm tất cả các dependencies như thế nào. Cả spring-security-webspring-security-config sẽ được yêu cầu.

3. Spring Security Java Configuration

Hãy bắt đầu bằng cách tạo một lớp cấu hình Spring Security kế thừa WebSecurityConfigurerAdapter.

Thêm @EnableWebSecurity chúng ta sẽ nhận được hỗ trợ tích hợp Spring Security và MVC

@Configuration
@EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        // authentication manager (see below)
    }

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // http builder configurations for authorize requests and form login (see below)
    }
}

3.1 Authentication Manager

Authentication Provider được hỗ trợ đơn giản bằng cách sử dụng InMemoryUserDetailsManager, điều này khá hữu ích để chúng ta có thể tạo nhanh dữ liệu khi chưa có 1 cơ chế đầy đủ

protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("user1").password(passwordEncoder().encode("user1Pass")).roles("USER")
        .and()
        .withUser("user2").password(passwordEncoder().encode("user2Pass")).roles("USER")
        .and()
        .withUser("admin").password(passwordEncoder().encode("adminPass")).roles("ADMIN");
}

Ở đây chúng ta sẽ hard code 3 user với username, passwordroles

Bắt đầu với Spring 5, chúng ta cũng phải định nghĩa password encoder. Trong ví dụ này chúng ta sẽ sử dụng BCryptPasswordEncoder

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

Tiếp theo, sẽ là cấu hình HttpSecurity

3.2. Cấu hình phân quyền

Chúng tôi sẽ bắt đầu bằng cách thực hiện các cấu hình cần thiết để Authorize Requests.

Ở đây chúng ta cho phép người dùng ẩn danh (anonymous) truy cập /login
để người dùng có thể xác thực. Chúng tôi sẽ cho phép truy cập /admin đối với các vai trò QUẢN TRỊ và bảo mật mọi thứ khác:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
      .csrf().disable()
      .authorizeRequests()
      .antMatchers("/admin/**").hasRole("ADMIN")
      .antMatchers("/anonymous*").anonymous()
      .antMatchers("/login*").permitAll()
      .anyRequest().authenticated()
      .and()
      // ...
}

Lưu ý rằng thứ tự của các method antMatchers() là rất quan trọng; các quy tắc cụ thể cần phải xuất hiện trước, sau đó là các quy tắc chung hơn.

3.3. Cấu hình form đăng nhập

Tiếp theo, chúng ta sẽ mở rộng cấu hình trên để đăng nhập và đăng xuất theo form

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
      // ...
      .and()
      .formLogin()
      .loginPage("/login.html")
      .loginProcessingUrl("/perform_login")
      .defaultSuccessUrl("/homepage.html", true)
      .failureUrl("/login.html?error=true")
      .failureHandler(authenticationFailureHandler())
      .and()
      .logout()
      .logoutUrl("/perform_logout")
      .deleteCookies("JSESSIONID")
      .logoutSuccessHandler(logoutSuccessHandler());
}
  • loginPage() : trang đăng nhập tùy chỉnh
  • loginProcessingUrl () - URL để gửi tên người dùng và mật khẩu đến
  • defaultSuccessUrl () - trang chuyển hướng sau khi đăng nhập thành công
  • failUrl () - trang chuyển hướng sau khi đăng nhập không thành công
  • logoutUrl () - đăng xuất tùy chỉnh

4. Thêm Spring Security vào Web

Để sử dụng cấu hình Spring Security được xác định ở trên, chúng ta cần gắn nó vào ứng dụng web.

Chúng ta sẽ sử dụng WebApplicationInitializer, vì vậy chúng ta không cần viết bất kỳ file web.xml nào

public class AppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext sc) {

        AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
        root.register(SecSecurityConfig.class);

        sc.addListener(new ContextLoaderListener(root));

        sc.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain"))
          .addMappingForUrlPatterns(null, false, "/*");
    }
}

Lưu ý rằng trình khởi tạo này không cần thiết nếu chúng ta đang sử dụng Spring Boot. Để biết thêm chi tiết về cách cấu hình security trong Spring Boot, hãy tham khảo bài viết này

5. Cấu hình XML

Chúng ta cũng có thể xem cấu hình XML tương ứng.

Dự án đang sử dụng cấu hình Java, vì vậy chúng ta cần nhập file cấu hình XML qua class Java @Configuration:

@Configuration
@ImportResource({ "classpath:webSecurityConfig.xml" })
public class SecSecurityConfig {
   public SecSecurityConfig() {
      super();
   }
}

Và cấu hình Spring Security XML trong file webSecurityConfig.xml

<http use-expressions="true">
    <intercept-url pattern="/login*" access="isAnonymous()" />
    <intercept-url pattern="/**" access="isAuthenticated()"/>

    <form-login login-page='/login.html' 
      default-target-url="/homepage.html" 
      authentication-failure-url="/login.html?error=true" />
    <logout logout-success-url="/login.html" />
</http>

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="user1" password="user1Pass" authorities="ROLE_USER" />
        </user-service>
        <password-encoder ref="encoder" />
    </authentication-provider>
</authentication-manager>

<beans:bean id="encoder" 
  class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
</beans:bean>

6. web.xml

Trước khi giới thiệu Spring 4, chúng tôi đã sử dụng cấu hình Spring Security trong file web.xml; chỉ bổ sung một filter vào web.xml Spring MVC tiêu chuẩn:

<display-name>Spring Secured Application</display-name>

<!-- Spring MVC -->
<!-- ... -->

<!-- Spring Security -->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  • Filter DelegateFilterProxy - chỉ cần ủy quyền cho một bean do Spring quản lý
  • FilterChainProxy - mà bản thân nó có thể hưởng lợi từ việc quản lý toàn bộ vòng đời của Spring bean

7. Login Form

Trang form login sẽ được với Spring MVC bằng cách sử dụng cơ chế đơn giản để ánh xạ views names to URLs.

registry.addViewController("/login.html");

Tất nhiên, điều này tương ứng với login.jsp

<html>
<head></head>
<body>
   <h1>Login</h1>
   <form name='f' action="login" method='POST'>
      <table>
         <tr>
            <td>User:</td>
            <td><input type='text' name='username' value=''></td>
         </tr>
         <tr>
            <td>Password:</td>
            <td><input type='password' name='password' /></td>
         </tr>
         <tr>
            <td><input name="submit" type="submit" value="submit" /></td>
         </tr>
      </table>
  </form>
</body>
</html>

Các thành phần trong Spring Login form :

  • login - URL nơi form được POST để tiến hành xử lý xác thực
  • username – tên đăng nhập
  • password – mật khẩu

8. Một số cấu hình Spring Login khác

Chúng tôi đã thảo luận sơ lược về một số cấu hình của cơ chế đăng nhập khi chúng tôi giới thiệu Spring Security ở trên. Bây giờ chúng ta hãy đi vào một số cấu hình chi tiết hơn.

Một lý do để overide hầu hết các cấu hình mặc định trong Spring Security là ẩn ứng dụng được bảo mật bằng Spring Security. Chúng tôi cũng muốn giảm thiểu thông tin mà kẻ tấn công biết về ứng dụng.

Phần login được cấu hình đầy đủ trông như sau

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
      .loginPage("/login.html")
      .loginProcessingUrl("/perform_login")
      .defaultSuccessUrl("/homepage.html",true)
      .failureUrl("/login.html?error=true")
}

Hoặc cấu hình XML tương ứng:

<form-login 
  login-page='/login.html' 
  login-processing-url="/perform_login" 
  default-target-url="/homepage.html"
  authentication-failure-url="/login.html?error=true" 
  always-use-default-target="true"/>

8.1. Trang đăng nhập

Tiếp theo, chúng tôi sẽ cấu hình trang đăng nhập bằng phương thức loginPage():

http.formLogin()
  .loginPage("/login.html")

Tương tự, chúng ta có thể sử dụng cấu hình XML

login-page='/login.html'

Nếu chúng ta không chỉ định điều này, Spring Security sẽ tạo một form đăng nhập có sẵn tại URL /login.

8.2. POST URL cho Login

URL mặc định nơi mà Spring Login xử lý quá trình đăng nhập là /login, hoặc là /j_spring_security_check trước Spring Security 4.

http.formLogin()
  .loginProcessingUrl("/perform_login")

Chúng ta cũng có thể sử dụng cấu hình XML:

login-processing-url="/perform_login"

Bằng cách ghi đè URL mặc định này, giúp chương trình được bảo mật hơn, tránh bị rò rỉ, đánh cắp thông tin.

8.3. Chuyển hướng sau khi đăng nhập thành công

Sau khi đăng nhập thành công, chúng ta sẽ được chuyển đến một trang, mặc định là đường dẫn gốc (root path) của ứng dụng web.

Chúng ta có thể ghi đè điều này thông qua phương thức defaultSuccessUrl():

http.formLogin()
  .defaultSuccessUrl("/homepage.html")

Hoặc với cấu hình XML:

default-target-url="/homepage.html"

Nếu thuộc tính always-use-default-target được đặt thành true, thì người dùng luôn được chuyển hướng đến trang này. Nếu thuộc tính đó được đặt thành false, thì người dùng sẽ được chuyển hướng đến trang mà họ muốn truy cập trước khi được nhắc xác thực.

8.4 Chuyển hướng đăng nhập không thành công

Tương tự như Login Page, Login Failure Page mặc định được Spring Security tự động tạo tại lỗi /login?error.

Để ghi đè điều này, chúng ta có thể sử dụng phương thức failureUrl():

http.formLogin()
  .failureUrl("/login.html?error=true")

Hoặc với XML:

authentication-failure-url="/login.html?error=true"

9. Kết luận

Trong Ví dụ Spring Login Example, chúng ta đã cấu hình một quy trình xác thực đơn giản. Chúng tôi cũng đã thảo luận về Spring Security Login Form, Security Configuration cũng như một số cấu hình nâng cao

Việc triển khai ứng dụng trong bài viết này có thể được tìm thấy trong GitHub project - đây là một dự án dựa trên Eclipse, vì vậy nó sẽ dễ dàng import và run ứng dụng

Khi chạy ứng dụng, Có thể được truy cập tại:

http://localhost:8080/spring-security-mvc-login/login.html