Spring Security 提供了在 Web 应用程序中执行身份验证和授权的方法。我们可以在任何基于 Servlet 的 Web 应用程序中使用 Spring Security。
Spring Security
- 经过验证的技术,最好使用它而不是重新发明轮子。安全性是我们需要格外注意的事项,否则我们的应用程序将容易受到攻击。
- 防止一些常见攻击,如 CSRF、会话固定攻击。
- 易于集成到任何 Web 应用程序中。我们不需要修改 Web 应用程序配置,Spring 会自动将安全过滤器注入到 Web 应用程序中。
- 提供通过不同方式进行身份验证的支持 – 内存、DAO、JDBC、LDAP 等等。
- 提供忽略特定 URL 模式的选项,适用于提供静态 HTML、图像文件。
- 支持组和角色。
Spring Security 示例
我們將創建一個網絡應用程序並將其與Spring Security集成。在Eclipse中使用“動態Web項目”選項創建一個網絡應用程序,以便我們的骨架網絡應用程序準備就緒。請確保將其轉換為maven項目,因為我們使用Maven進行構建和部署。如果您對這些步驟不熟悉,請參考Java Web Application教程。一旦我們的應用程序被安全保護,最終的項目結構將如下圖所示。我們將研究三種Spring Security身份驗證方法。
- 內存中
- DAO
- JDBC
對於JDBC,我正在使用MySQL數據庫並執行以下腳本來創建用戶詳細信息表。
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` (
`username` varchar(20) NOT NULL DEFAULT '',
`role` varchar(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;
我們還需要在我們的servlet容器中將JDBC數據源配置為JNDI,要了解更多信息,請閱讀Tomcat JNDI數據源示例。
Spring Security Maven依賴
以下是我们的最终 pom.xml 文件。
<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>
我们有以下与 Spring 框架相关的依赖项。
- spring-jdbc: 该依赖用于通过 JDBC 认证方法进行 JDBC 操作。它需要设置 JNDI 作为 DataSource。有关其用法的完整示例,请参阅 Spring DataSource JNDI Example
- spring-security-taglibs: Spring 安全性标签库,我在 JSP 页面中使用它来显示用户角色。大多数情况下,您可能不需要它。
- spring-security-config: 用于配置身份验证提供程序,是否使用 JDBC、DAO、LDAP 等。
- spring-security-web: 该组件将 Spring 安全性集成到 Servlet API 中。我们需要它来在 Web 应用程序中插入我们的安全性配置。
还请注意,我们将使用 Servlet API 3.0 功能通过编程方式添加监听器和过滤器,因此依赖项中的 Servlet API 版本应为 3.0 或更高。
Spring Security Example View Pages
我们的应用程序中有 JSP 和 HTML 页面。我们希望在除 HTML 页面之外的所有页面上应用身份验证。health.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>
I have included index.jsp
as welcome-file
in the application deployment descriptor. Spring Security takes care of CSRF attack, so when we are submitting form for logout, we are sending the CSRF token back to server to delete it. The CSRF object set by Spring Security component is _csrf and we are using it’s property name and token value to pass along in the logout request. Let’s look at the Spring Security configurations now.
Spring Security範例UserDetailsService DAO實現
由於我們將使用基於DAO的身份驗證,因此需要實現UserDetailsService
接口並提供loadUserByUsername()
方法的實現。理想情況下,我們應該使用一些資源來驗證用戶,但為了簡單起見,我只是做了基本的驗證。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");
}
//創建虛擬用戶詳細信息,應該執行JDBC操作
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 auths = new java.util.ArrayList();
auths.add(new SimpleGrantedAuthority("admin"));
return auths;
}
};
}
}
請注意,我正在創建UserDetails
的匿名內部類並返回它。您可以為其創建一個實現類,然後實例化並返回它。通常在實際應用中,這是一種常用的方法。
Spring Security範例WebSecurityConfigurer實現
我們可以實現WebSecurityConfigurer
接口,也可以擴展基礎實現類WebSecurityConfigurerAdapter
並覆蓋方法。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 {
// 在內存身份驗證
// auth.inMemoryAuthentication().withUser("pankaj").password("pankaj123").roles("USER");
// 使用自定義UserDetailsService DAO
// auth.userDetailsService(new AppUserDetailsServiceDAO());
// 使用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 應該完全忽略以 .html 結尾的URL
.antMatchers("/*.html");
}
}
請注意,我們通過覆蓋 configure(WebSecurity web)
方法來忽略所有 HTML 文件。代碼顯示了如何插入JDBC身份驗證。我們需要通過提供 DataSource 來配置它。由於我們使用的是自定義表,我們還需要提供用於獲取用戶詳細信息及其角色的查詢。配置內存和DAO基礎身份驗證很容易,它們在上面的代碼中被註釋了。您可以取消註釋它們以使用它們,但請確保一次只有一個配置。需要 @Configuration
和 @EnableWebSecurity
注解,以便 Spring 框架知道該類將用於 Spring 安全配置。Spring 安全配置使用 建造者模式,並且基於 authenticate 方法,有些方法在後續將不可用。例如,auth.userDetailsService()
返回 UserDetailsService
的實例,然後我們就不能有其他選項了,例如我們不能在其後設置 DataSource。
將 Spring Security Web 與 Servlet API 整合
最後一部分是將我們的 Spring Security 配置類整合到 Servlet API 中。這可以通過擴展 AbstractSecurityWebApplicationInitializer
類並在超類構造函數中傳遞 Security 配置類來輕鬆完成。 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);
}
}
當我們的上下文啟動時,它使用 ServletContext 添加 ContextLoaderListener 監聽器 並將我們的配置類註冊為 Servlet 過濾器。請注意,這僅在 Servlet-3 兼容的 Servlet 容器中有效。因此,如果您使用 Apache Tomcat,請確保其版本為 7.0 或更高。我們的項目已準備就緒,只需將其部署在您喜歡的 Servlet 容器中即可。我正在使用 Apache Tomcat-7 運行此應用程序。下面的圖像顯示了各種情況下的響應。
訪問 HTML 頁面時不使用安全性
錯誤的憑證驗證失敗
帶有 Spring Security JDBC 驗證的首頁
首頁與 Spring Security UserDetailsService DAO 認證
首頁與 Spring Security 內存認證
登出頁面
如果您想使用不支持 Servlet Specs 3 的 Servlet 容器,則需要通過部署描述符註冊
DispatcherServlet
。詳情請參閱 WebApplicationInitializer
的 JavaDoc。這就是有關 Spring Security 示例教程及其在基於 Servlet 的 Web 應用程序中的集成。請從下面的鏈接下載示例項目並進行更多學習。
Source:
https://www.digitalocean.com/community/tutorials/spring-security-example-tutorial