- Sản phẩm & Dịch vụSản phẩm & Dịch vụ
- Giải phápGiải pháp
- Bảng giáBảng giá
- Công tyCông ty
- Tài liệuTài liệu
vi
vi
Chuyên mục với các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.
Technical Writer
I have over 5 years of experience writing technical documentation for tech products, making them accessible and user-friendly. My focus is always on providing clear and precise information. @#@ Tôi đã có hơn 5 năm kinh nghiệm viết tài liệu kỹ thuật cho các sản phẩm công nghệ, giúp người dùng dễ dàng tiếp cận và sử dụng. Tôi luôn tập trung vào việc cung cấp thông tin chính xác và dễ hiểu.
Thảo luận (0)
Đăng nhập để thảo luận
Spring Security cung cấp các cách để thực hiện xác thực (authentication) và phân quyền (authorization) trong ứng dụng web. Chúng ta có thể sử dụng Spring Security trong bất kỳ ứng dụng web nào dựa trên servlet.

Một số lợi ích của việc sử dụng Spring Security:
Chúng ta sẽ tạo một ứng dụng web và tích hợp nó với Spring Security. Hãy tạo ứng dụng web bằng tùy chọn “Dynamic Web Project” trong Eclipse để tạo sẵn cấu trúc ứng dụng cơ bản. Đảm bảo chuyển dự án này thành dự án Maven, vì chúng ta sẽ sử dụng Maven để build và triển khai.
Sau khi thêm bảo mật, cấu trúc cuối cùng của dự án sẽ giống như hình minh họa trong hướng dẫn.

Chúng ta sẽ tìm hiểu ba phương pháp xác thực với Spring Security:
Với JDBC, mình sẽ sử dụng cơ sở dữ liệu MySQL và chạy đoạn script SQL sau để tạo bảng lưu thông tin người dùng.
CREATE TABLE `Employees` ( `username` varchar(20) NOT NULL DEFAULT '', `password` varchar(20) NOT NULL DEFAULT '', `enabled` tinyint(1) NOT NULL DEFAULT '1', PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE
Roles(usernamevarchar(20) NOT NULL DEFAULT '',rolevarchar(20) NOT NULL DEFAULT '', PRIMARY KEY (username,role) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO
Employees(username,password,enabled) VALUES ('pankaj', 'pankaj123', 1);INSERT INTO
Roles(username,role) VALUES ('pankaj', 'Admin'), ('pankaj', 'CEO');commit;
Chúng ta cũng cần cấu hình JDBC DataSource dưới dạng JNDI trong servlet container.
Dưới đây là file pom.xml cuối cùng của chúng ta.
<project xmlns="<https://maven.apache.org/POM/4.0.0>" xmlns:xsi="<https://www.w3.org/2001/XMLSchema-instance>" xsi:schemaLocation="<https://maven.apache.org/POM/4.0.0> <https://maven.apache.org/xsd/maven-4.0.0.xsd>"> <modelVersion>4.0.0</modelVersion> <groupId>WebappSpringSecurity</groupId> <artifactId>WebappSpringSecurity</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!-- Spring Security Artifacts - START --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>3.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>3.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>3.0.5.RELEASE</version> </dependency> <!-- Spring Security Artifacts - END --><dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.0.2.RELEASE</version> </dependency> </dependencies> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <warSourceDirectory>WebContent</warSourceDirectory> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build></project>
Chúng ta có các phụ thuộc sau liên quan đến Spring Framework:
Lưu ý thêm rằng chúng ta sẽ sử dụng tính năng của Servlet API 3.0 để thêm listener và filter bằng cách lập trình (programmatically), do đó phiên bản servlet-api trong phần dependencies nên là 3.0 hoặc cao hơn.
Ứng dụng của chúng ta sẽ có các trang JSP và HTML. Mục tiêu là áp dụng xác thực đối với tất cả các trang, trừ các trang HTML.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Health Check</title>
</head>
<body>
<h3>Service is up and running!!</h3>
</body>
</html>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="<https://java.sun.com/jsp/jstl/core>" prefix="c" %>
<%@ taglib uri="<https://www.springframework.org/security/tags>" prefix="sec" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "<https://www.w3.org/TR/html4/loose.dtd>">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Home Page</title>
</head>
<body>
<h3>Home Page</h3>
<p>
Hello <b><c:out value="${pageContext.request.remoteUser}"/></b><br>
Roles: <b><sec:authentication property="principal.authorities" /></b>
</p>
<form action="logout" method="post">
<input type="submit" value="Logout" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
</body>
</html>
Tôi đã thêm index.jsp làm welcome-file trong tệp deployment descriptor của ứng dụng. Spring Security sẽ xử lý các cuộc tấn công CSRF, vì vậy khi chúng ta gửi form để đăng xuất, chúng ta cần gửi CSRF token trở lại máy chủ để xóa nó. Đối tượng CSRF được Spring Security thiết lập là _csrf, và chúng ta sử dụng thuộc tính name và token của nó để gửi kèm trong yêu cầu đăng xuất. Giờ chúng ta hãy xem phần cấu hình Spring Security.
Vì chúng ta sẽ sử dụng xác thực theo kiểu DAO, nên cần phải triển khai giao diện UserDetailsService và cung cấp phần cài đặt cho phương thức loadUserByUsername(). Tốt nhất chúng ta nên sử dụng một nguồn dữ liệu để xác thực người dùng, nhưng để đơn giản hóa, tôi chỉ làm một kiểm tra cơ bản. File này có tên là AppUserDetailsServiceDAO.java.
package com.journaldev.webapp.spring.dao;import java.util.Collection; import java.util.List;
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class AppUserDetailsServiceDAO implements UserDetailsService {
protected final Log logger = LogFactory.getLog(getClass()); @Override public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { logger.info("loadUserByUsername username="+username); if(!username.equals("pankaj")){ throw new UsernameNotFoundException(username + " not found"); } //creating dummy user details, should do JDBC operations return new UserDetails() { private static final long serialVersionUID = 2059202961588104658L; @Override public boolean isEnabled() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isAccountNonExpired() { return true; } @Override public String getUsername() { return username; } @Override public String getPassword() { return "pankaj123"; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>(); auths.add(new SimpleGrantedAuthority("admin")); return auths; } }; }}
Lưu ý rằng tôi đang tạo một lớp ẩn danh (anonymous inner class) của UserDetails và trả về nó. Bạn cũng có thể tạo một lớp cài đặt riêng cho UserDetails, sau đó khởi tạo và trả về đối tượng đó. Thông thường, đây là cách tiếp cận phổ biến trong các ứng dụng thực tế.
Chúng ta có thể cài đặt giao diện WebSecurityConfigurer, hoặc mở rộng lớp cơ sở WebSecurityConfigurerAdapter và ghi đè các phương thức cần thiết.
File cấu hình bảo mật sẽ là: SecurityConfig.java.
package com.journaldev.webapp.spring.security;import javax.naming.Context; import javax.naming.InitialContext; import javax.sql.DataSource;
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import com.journaldev.webapp.spring.dao.AppUserDetailsServiceDAO;
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // in-memory authentication // auth.inMemoryAuthentication().withUser("pankaj").password("pankaj123").roles("USER"); // using custom UserDetailsService DAO // auth.userDetailsService(new AppUserDetailsServiceDAO()); // using JDBC Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx .lookup("java:/comp/env/jdbc/MyLocalDB"); final String findUserQuery = "select username,password,enabled " + "from Employees " + "where username = ?"; final String findRoles = "select username,role " + "from Roles " + "where username = ?"; auth.jdbcAuthentication().dataSource(ds) .usersByUsernameQuery(findUserQuery) .authoritiesByUsernameQuery(findRoles); } @Override public void configure(WebSecurity web) throws Exception { web .ignoring() // Spring Security should completely ignore URLs ending with .html .antMatchers("/*.html"); }}
Lưu ý rằng chúng ta đang bỏ qua tất cả các tệp HTML bằng cách ghi đè phương thức configure(WebSecurity web). Đoạn mã trên cũng cho thấy cách cấu hình xác thực JDBC. Chúng ta cần cấu hình bằng cách cung cấp DataSource. Vì đang sử dụng các bảng tùy chỉnh, nên ta cũng cần cung cấp câu lệnh SELECT để lấy thông tin người dùng và vai trò của họ.
Việc cấu hình xác thực in-memory và DAO-based thì đơn giản hơn, chúng đã được ghi chú (comment) trong đoạn mã phía trên. Bạn có thể bỏ ghi chú (uncomment) để sử dụng, nhưng nhớ rằng chỉ nên dùng một phương thức xác thực tại một thời điểm.
Hai annotation @Configuration và @EnableWebSecurity là bắt buộc, để Spring Framework biết rằng lớp này sẽ được dùng cho cấu hình bảo mật Spring Security.
Cấu hình Spring Security sử dụng Builder Pattern, và tùy theo phương thức xác thực được chọn, một số phương thức khác sẽ không còn khả dụng sau đó. Ví dụ: auth.userDetailsService() trả về một thể hiện của UserDetailsService, và sau đó chúng ta không thể cấu hình DataSource được nữa.
Phần cuối cùng là tích hợp lớp cấu hình Spring Security của chúng ta vào Servlet API. Việc này khá đơn giản: chỉ cần mở rộng lớp AbstractSecurityWebApplicationInitializer và truyền lớp cấu hình bảo mật vào constructor của lớp cha (Super class).
File: SecurityWebApplicationInitializer.java
package com.journaldev.webapp.spring.security;import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() { super(SecurityConfig.class); }}
Khi ứng dụng khởi động, nó sẽ sử dụng ServletContext để thêm ContextLoaderListener và đăng ký lớp cấu hình của chúng ta như một Servlet Filter. Lưu ý rằng việc này chỉ hoạt động trên các Servlet Container tương thích với Servlet 3.0 trở lên. Vì vậy, nếu bạn đang sử dụng Apache Tomcat, hãy đảm bảo rằng phiên bản là 7.0 hoặc cao hơn. Dự án của chúng ta đến đây là đã sẵn sàng, chỉ cần triển khai (deploy) nó vào Servlet Container yêu thích của bạn. Ở đây, tôi đang sử dụng Apache Tomcat 7 để chạy ứng dụng này.
Các hình ảnh bên dưới sẽ minh họa phản hồi của ứng dụng trong nhiều tình huống khác nhau.






Nếu bạn muốn sử dụng Servlet Container không hỗ trợ Servlet Specs 3.0, thì bạn sẽ cần phải đăng ký DispatcherServlet thông qua file cấu hình triển khai. Xem phần JavaDoc của WebApplicationInitializer để biết thêm chi tiết.