Spring MVC 安全示例
欢迎使用UserDetailsService的Spring安全性示例。在上一篇文章中,我们学习了如何在Web应用程序中使用Spring Security。今天我们将研究如何在Spring MVC项目中集成Spring Security以进行身份验证。
目录[ 隐藏 ]
Spring安全示例
将Spring Security与Spring MVC Framework集成非常简单,因为我们已经有了Spring Beans配置文件。我们所需要的只是创建与弹簧安全认证相关的更改以使其正常工作。今天我们将研究如何使用内存,UserDetailsService
DAO实现和基于JDBC的身份验证在Spring MVC应用程序中实现身份验证。
首先在Spring Tool Suite中创建一个简单的Spring MVC项目,它将为我们提供基础Spring MVC应用程序来构建我们的Spring安全性示例应用程序。一旦我们完成所有更改,我们的应用程序将如下图所示。
让我们看一下Spring安全性示例项目的每个组件。
Spring Security Maven依赖项
我们的最终pom.xml文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>SpringMVCSecurity</artifactId>
<name>SpringMVCSecurity</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.4</org.aspectj-version>
<org.slf4j-version>1.7.5</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- Spring Security -->
<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-web</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</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>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
这里已包括spring-security-config
和spring-security-web
为Spring Security依赖。除此之外我们还有spring-jdbc
依赖,因为我们也将使用Spring JDBC身份验证。
其余依赖项与Spring MVC,日志记录,AOP等相关。
Spring安全性示例部署描述符
我们的web.xml文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Spring Security Configuration File -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/spring-security.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlet and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<!-- Spring Security Filter -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring MVC - START -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/appServlet/servlet-context.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Spring MVC - END -->
</web-app>
contextConfigLocation
是我们提供spring安全bean配置文件名的context参数。它用于ContextLoaderListener
在我们的应用程序中配置身份验证。
我们还添加了HttpSessionEventPublisher
侦听器,以将会话创建/销毁事件发布到Spring Root WebApplicationContext。
我也设置session-timeout
为15分钟,当用户处于非活动状态15分钟时,用于自动超时。
DelegatingFilterProxy
是定义的应用程序过滤器,它用于拦截HTTP请求和执行与身份验证相关的任务。
DispatcherServlet
servlet是Spring MVC应用程序的前端控制器。
的UserDetailsService
如果我们想使用任何DAO类进行身份验证,我们需要实现UserDetailsService
接口。配置DAO后,它将loadUserByUsername()
用于验证用户。
package com.journaldev.spring.security.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
通过使用匿名内部类实现返回实例。理想情况下,我们应该有一个实现类,UserDetails
也可以有其他用户数据,如emailID,用户名,地址等。
请注意,唯一可行的组合是用户名为“pankaj”且密码为“pankaj123”。
Spring安全性示例控制器类
这是我们的控制器类,它定义了我们可以访问的两个URI。
package com.journaldev.spring.controller;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
@RequestMapping(value = "/emp/get/{id}", method = RequestMethod.GET)
public String getEmployee(Locale locale, Model model,@PathVariable("id") int id) {
logger.info("Welcome user! Requested Emp ID is: "+id);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
model.addAttribute("id", id);
model.addAttribute("name", "Pankaj");
return "employee";
}
@RequestMapping(value="/login")
public String login(HttpServletRequest request, Model model){
return "login";
}
@RequestMapping(value="/logout")
public String logout(){
return "logout";
}
@RequestMapping(value="/denied")
public String denied(){
return "denied";
}
}
在我们的示例中,我们仅将身份验证应用于URI“/ emp / get / {id}”。无需任何身份验证即可访问所有其他URI。登录,注销和拒绝URI用于在请求安全URL时发送相应的响应页面。
Spring安全示例Bean配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="com.journaldev.spring.controller" />
</beans:beans>
我们的spring bean配置文件很简单,它只有与Spring MVC应用程序相关的配置。
Spring MVC安全配置
这是我们教程中最重要的部分,让我们来看看我们的文件。我们将逐一了解每个部分。
spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Configuring RoleVoter bean to use custom access roles, by default roles
should be in the form ROLE_{XXX} -->
<beans:bean id="roleVoter"
class="org.springframework.security.access.vote.RoleVoter">
<beans:property name="rolePrefix" value=""></beans:property>
</beans:bean>
<beans:bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<beans:constructor-arg name="decisionVoters"
ref="roleVoter" />
</beans:bean>
<http authentication-manager-ref="jdbc-auth"
access-decision-manager-ref="accessDecisionManager">
<intercept-url pattern="/emp/**" access="Admin" />
<form-login login-page="/login" authentication-failure-url="/denied"
username-parameter="username" password-parameter="password"
default-target-url="/home" />
<logout invalidate-session="true" logout-success-url="/login"
logout-url="/j_spring_security_logout" />
<access-denied-handler error-page="/denied"/>
<session-management invalid-session-url="/login">
<concurrency-control max-sessions="1"
expired-url="/login" />
</session-management>
</http>
<authentication-manager id="in-memory-auth">
<authentication-provider>
<user-service>
<user name="pankaj" password="pankaj123" authorities="Admin" />
</user-service>
</authentication-provider>
</authentication-manager>
<authentication-manager id="dao-auth">
<authentication-provider user-service-ref="userDetailsService">
</authentication-provider>
</authentication-manager>
<beans:bean id="userDetailsService"
class="com.journaldev.spring.security.dao.AppUserDetailsServiceDAO" />
<authentication-manager id="jdbc-auth">
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,enabled from Employees where username = ?"
authorities-by-username-query="select username,role from Roles where username = ?" />
</authentication-provider>
</authentication-manager>
<!-- MySQL DB DataSource -->
<beans:bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
<beans:property name="url"
value="jdbc:mysql://localhost:3306/TestDB" />
<beans:property name="username" value="pankaj" />
<beans:property name="password" value="pankaj123" />
</beans:bean>
<!-- If DataSource is configured in Tomcat Servlet Container -->
<beans:bean id="dbDataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<beans:property name="jndiName" value="java:comp/env/jdbc/MyLocalDB" />
</beans:bean>
</beans:beans>
accessDecisionManager
bean被定义为我们可以拥有自定义角色,默认情况下所有角色都应该以ROLE_开头,并且我们在roleVoter
bean属性中覆盖了这个设置rolePrefix
。
我们可以在spring安全配置中定义多个身份验证管理器。我已经in-memory-auth
为内存中的身份验证,dao-auth
UserDetailsService DAO实现和jdbc-auth
JDBC身份验证定义了。对于JDBC身份验证,我已经为应用程序中定义的DataSource提供了配置,以及我们是否要使用servlet容器中定义的JNDI资源。
http authentication-manager-ref
用于定义将用于验证用户的身份验证管理器。目前,它已配置为使用基于JDBC的身份验证。
http access-decision-manager-ref
用于指定应该用于授权HTTP请求的AccessDecisionManager实现的ID。
intercept-url
用于定义可以访问此页面的用户的URL模式和权限。例如,我们已经定义了URI“/ emp / **”只能由具有“Admin”访问权限的用户访问。
form-login
定义登录表单配置,我们可以提供用户名和密码参数名称。authentication-failure-url
用于定义身份验证失败页面的URL。如果未指定登录失败URL,Spring Security将自动在/ spring_security_login?login_error创建失败登录URL,并在请求时自动创建登录失败URL。
default-target-url
用于定义在成功验证后将重定向到的默认URL,如果用户的先前操作无法恢复。如果用户在没有首先请求触发认证的安全操作的情况下访问登录页面,则通常会发生这种情况。如果未指定,则默认为应用程序的根目录。
logout
用于定义注销处理过滤器。这里我们使会话无效并在成功注销后将用户发送到登录页面。logout-url
用于定义用于注销操作的URL。
access-denied-handler
如果拒绝用户访问,则定义全局错误页面,因为他无权执行指定的操作。
session-management
将SessionManagementFilter过滤器添加到会话管理的过滤器堆栈。
还有一些其他配置,但我已经包含了我们使用的大多数重要配置。
Spring安全示例查看页面
在部署和测试我们的应用程序之前,让我们快速浏览一下我们的视图页面。
home.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page session="false"%>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Hello world!</h1>
<P>The time on the server is ${serverTime}.</P>
</body>
</html>
home.jsp返回“/ home”URI,不应该要求任何身份验证。
employee.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page session="false"%>
<html>
<head>
<title>Get Employee Page</title>
</head>
<body>
<h1>Employee Information</h1>
<p>
Employee ID:${id}<br> Employee Name:${name}<br>
</p>
<c:if test="${pageContext.request.userPrincipal.name != null}">
Hi ${pageContext.request.userPrincipal.name}<br>
<c:url var="logoutAction" value="/j_spring_security_logout"></c:url>
<form action="${logoutAction}" method="post">
<input type="submit" value="Logout" />
</form>
</c:if>
</body>
</html>
当我们访问需要身份验证的URI时,将返回此页面。这里我提供了注销选项,以便用户可以注销并终止会话。注销成功后,应按照配置将用户发送回登录页面。
login.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Login Page</title>
</head>
<body>
<h3>Login with Username and Password</h3>
<c:url var="loginUrl" value="/j_spring_security_check"></c:url>
<form action="${loginUrl}" method="POST">
<table>
<tr>
<td>User ID:</td>
<td><input type='text' name='username' /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password' /></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit"
value="Login" /></td>
</tr>
</table>
</form>
</body>
</html>
这里有几点需要注意。第一个是登录URL是“ / j_spring_security_check ”。这是默认的登录处理URL,就像logout-url一样。
另一个重点是用户名和密码的表单参数名称。它们应与弹簧安全配置中配置的相同。
logout.jsp
<html>
<head>
<title>Logout Page</title>
</head>
<body>
<h2>
Logout Successful!
</h2>
</body>
</html>
denied.jsp
<html>
<head>
<title>Access Denied</title>
</head>
<body>
<h1>
Access Denied!
</h1>
</body>
</html>
logout.jsp和denied.jsp页面很简单,但我们可以根据用户的详细信息在这里包含一些信息。
我们的spring安全性示例应用程序已准备好进行测试,请注意,对于JDBC身份验证,我使用的是与之前的Spring安全性示例相同的设置。所以如果你直接降落在这里,你应该检查出来。
Spring Security MVC示例测试
只需将应用程序部署在您最喜欢的servlet容器中,我的是Apache Tomcat 7.下图显示了不同URL的不同输出。
这就是使用UserDetailsService的Spring安全示例,请从下面的链接下载示例项目并浏览它以了解更多信息。
转载来源:https://www.journaldev.com/2736/spring-security-example-userdetailsservice