Spring安全示例教程
Spring Security提供了在Web应用程序中执行身份验证和授权的方法。
我们可以在任何基于servlet的Web应用程序中使用spring security。
Spring安全
使用Spring Security的一些好处是:
经过验证的技术,最好使用此技术,而不是重新发明轮子。
安全是我们需要格外注意的事情,否则我们的应用程序将容易受到攻击者的攻击。防止某些常见的攻击,例如CSRF,会话固定攻击。
易于集成到任何Web应用程序中。
我们不需要修改Web应用程序配置,Spring会自动将安全过滤器注入Web应用程序。通过不同的方式提供对身份验证的支持-内存,DAO,JDBC,LDAP等。
提供忽略特定URL模式的选项,非常适合提供静态HTML,图像文件。
支持组和角色。
Spring安全示例
我们将创建一个Web应用程序并将其与Spring Security集成。
使用Eclipse中的"动态Web项目"选项创建一个Web应用程序,以便我们的框架Web应用程序已准备就绪。
确保将其转换为Maven项目,因为我们使用Maven进行构建和部署。
如果您不熟悉这些步骤,请参考Java Web Application Tutorial。
一旦我们确保了应用程序的安全,最终的项目结构将如下图所示。
我们将研究三种Spring安全认证方法。
- 在记忆中
- 道
- 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 ('hyman', 'hyman123', 1); INSERT INTO `Roles` (`username`, `role`) VALUES ('hyman', 'Admin'), ('hyman', '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 Framework相关的依赖项。
spring-jdbc:通过JDBC认证方法用于JDBC操作。
它要求将数据源设置为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 <c:out value="${pageContext.request.remoteUser}"<br> Roles: <sec:authentication property="principal.authorities" </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.theitroad.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("hyman")){ 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 "hyman123"; } @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.theitroad.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.theitroad.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("hyman").password("hyman123").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模式,并且基于authenticate方法,某些方法稍后将不可用。
例如," auth.userDetailsService()"返回" UserDetailsService"的实例,那么我们就没有其他选择,例如,我们不能在其后设置DataSource。
将Spring Security Web与Servlet API集成
最后一部分是将我们的Spring Security配置类集成到Servlet API。
这可以通过扩展AbstractSecurityWebApplicationInitializer类并在超类构造函数中传递Security配置类来轻松完成。
SecurityWebApplicationInitializer.java
package com.theitroad.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来运行此应用程序。