Spring Security 示例教程
Spring Security 提供了在 Web 应用程序中执行身份验证和授权的方法。我们可以在任何基于 servlet 的 Web 应用程序中使用 Spring Security。
Spring Security
使用 Spring Security 的一些好处包括:
- 经过验证的技术,使用它比重新发明轮子更好。安全性是我们需要格外小心的事情,否则我们的应用程序将容易受到攻击者的攻击。
- 防止一些常见的攻击,例如CSRF,会话固定攻击。
- 易于集成到任何 Web 应用程序中。我们不需要修改 Web 应用程序配置,Spring 会自动将安全过滤器注入到 Web 应用程序中。
- 通过不同的方式提供对身份验证的支持 - 内存、DAO、JDBC、LDAP 等等。
- 提供忽略特定 URL 模式的选项,适合提供静态 HTML、图像文件。
- 支持群组和角色。
Spring Security 示例
我们将创建一个 Web 应用程序并将其与 Spring Security 集成。使用 Eclipse 中的“动态 Web 项目”选项创建一个 Web 应用程序,这样我们的骨架 Web 应用程序就准备就绪了。确保将其转换为 Maven 项目,因为我们使用 Maven 进行构建和部署。如果您不熟悉这些步骤,请参阅Java Web 应用程序教程。一旦我们的应用程序得到保护,最终的项目结构将如下图所示。我们将研究三种 Spring Security 身份验证方法。
- 内存中
- 去中心化自治组织
- 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 DataSource 配置为 JNDI,要了解这一点,请阅读Tomcat JNDI DataSource 示例。
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 Framework 相关的依赖项。
- spring-jdbc:用于通过 JDBC 身份验证方法进行 JDBC 操作。它需要将 DataSource 设置为 JNDI。有关其用法的完整示例,请参阅Spring DataSource JNDI 示例
- spring-security-taglibs:Spring Security 标签库,我使用它在 JSP 页面中显示用户角色。不过大多数情况下,你不需要它。
- spring-security-config:用于配置身份验证提供程序,是否使用 JDBC、DAO、LDAP 等。
- spring-security-web:此组件将 Spring Security 集成到 Servlet API。我们需要它在 Web 应用程序中插入我们的安全配置。
还要注意,我们将使用 Servlet API 3.0 功能通过编程添加监听器和过滤器,这就是依赖项中的 servlet api 版本应该是 3.0 或更高版本的原因。
Spring Security 示例视图页面
我们的应用程序中有 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>
我已将其包含在应用程序部署描述index.jsp
符welcome-file
中。Spring Security 负责处理 CSRF 攻击,因此当我们提交注销表单时,我们会将 CSRF 令牌发送回服务器以将其删除。Spring Security 组件设置的 CSRF 对象是_csrf,我们使用其属性名称和令牌值在注销请求中传递。现在让我们看看 Spring Security 配置。
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");
}
//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;
}
};
}
}
注意,我创建了匿名内部类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 {
// 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");
}
}
请注意,我们通过覆盖configure(WebSecurity web)
方法忽略了所有 HTML 文件。代码显示如何插入 JDBC 身份验证。我们需要通过提供 DataSource 来配置它。由于我们使用自定义表,我们还需要提供选择查询来获取用户详细信息及其角色。配置内存和基于 DAO 的身份验证很容易,它们已在上面代码中注释掉。您可以取消注释以使用它们,确保一次只有一个配置。@Configuration
并且@EnableWebSecurity
需要注释,以便 spring 框架知道此类将用于 spring 安全配置。Spring Security Configuration 使用Builder Pattern并基于 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 Filter。请注意,这只适用于兼容 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 应用程序中的集成。请从以下链接下载示例项目并试用它以了解更多信息。
下载 Spring Servlet 安全项目