Spring安全示例教程

Spring Security 提供了在 Web 应用程序中执行身份验证和授权的方式。我们可以在任何基于 Servlet 的 Web 应用程序中使用 Spring Security。

Spring Security

使用 Spring Security 的一些好处包括:

  1. 经过验证的技术,使用它比重新发明轮子更好。安全性是我们需要格外小心的事情,否则我们的应用程序将容易受到攻击。
  2. 预防一些常见的攻击,如 CSRF、会话固定攻击。
  3. 易于集成到任何 Web 应用程序中。我们不需要修改 Web 应用程序的配置,Spring 会自动向 Web 应用程序注入安全过滤器。
  4. 提供对不同方式进行身份验证的支持 – 内存、DAO、JDBC、LDAP 等等。
  5. 提供忽略特定 URL 模式的选项,非常适合提供静态 HTML、图像文件。
  6. 支持组和角色。

Spring Security 示例

我们将创建一个网络应用程序,并将其与Spring Security集成。在Eclipse中使用“动态Web项目”选项创建一个网络应用程序,这样我们的骨架网络应用程序就准备好了。确保将其转换为Maven项目,因为我们要使用Maven进行构建和部署。如果您对这些步骤不熟悉,请参阅Java Web应用程序教程。一旦我们的应用程序被保护,最终项目结构将如下图所示。我们将研究三种Spring Security身份验证方法。

  1. 内存中的
  2. DAO
  3. 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 框架相关的依赖项。

  1. spring-jdbc:此依赖项用于通过 JDBC 认证方法进行 JDBC 操作。它需要将 DataSource 设置为 JNDI。有关其用法的完整示例,请参阅Spring DataSource JNDI 示例
  2. spring-security-taglibs:Spring 安全性标签库,我已将其用于在 JSP 页面中显示用户角色。大多数情况下,您可能不需要它。
  3. spring-security-config:用于配置身份验证提供程序,例如是否使用 JDBC、DAO、LDAP 等。
  4. spring-security-web:此组件将 Spring 安全性集成到 Servlet API 中。我们需要它来插入我们的安全配置到 Web 应用程序中。

还请注意,我们将使用 Servlet API 3.0 功能通过编程方式添加监听器和过滤器,因此依赖项中的 Servlet API 版本应为 3.0 或更高。

Spring 安全性示例视图页面

我们的应用程序中有 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 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类,并将安全配置类传递给超类构造函数,可以轻松实现此操作。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规范3的Servlet容器,则需要通过部署描述符注册DispatcherServlet。有关更多详细信息,请参阅WebApplicationInitializer的JavaDoc。这就是关于Spring Security示例教程及其在基于Servlet的Web应用程序中的集成的所有内容。请从下面的链接下载示例项目,并进行更多实践以了解更多内容。

下载Spring Servlet安全项目

Source:
https://www.digitalocean.com/community/tutorials/spring-security-example-tutorial