欢迎来到使用UserDetailsService的Spring安全示例。在上一篇文章中,我们学习了如何使用Web应用中的Spring安全性. 今天,我们将探讨如何将Spring Security集成到Spring Mvc项目中以实现身份验证。
目录
Spring Security示例
将Spring Security与Spring Mvc框架集成非常容易,因为我们已经有了Spring Beans配置文件。我们只需要创建与Spring Security身份验证相关的更改,使其正常工作。今天,我们将探讨如何使用内存在Spring MVC应用程序中实现身份验证,UserDetailsService
DAO implementation and JDBC based authentication.
首先在Spring工具套件中创建一个简单的Spring Mvc项目,它将为我们提供基本的Spring Mvc应用程序来构建Spring安全示例应用程序。一旦我们完成所有更改,我们的应用程序将如下图所示。
让我们看看Spring Security示例项目的每个组件。
Spring Security Maven依赖项
我们的决赛pom.xml文件文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<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/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
and spring-security-web
dependencies for Spring Security. Apart from that we have spring-jdbc
dependency because we will be using Spring JDBC authentication too.
其余的依赖关系与Spring Mvc、日志、AOP等相关。
Spring安全示例部署描述符
我们的web.xml文件文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="https://java.sun.com/xml/ns/javaee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://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
is the context parameter where we provide the spring security beans configuration file name. It is used by ContextLoaderListener
to configure authentication in our application.
我们还补充了HttpSessionEventPublisher
listener to publish session created/destroyed events to the Spring Root WebApplicationContext.
我也准备好了session-timeout
to 15 minutes, this is used for auto timeout when user is inactive for 15 minutes.
DelegatingFilterProxy
is the application filter defined, it is used for intercepting the HTTP requests and performing authentication related tasks.
DispatcherServlet
servlet is the front controller for the Spring MVC application.
用户详细信息服务
如果我们想使用任何DAO类进行身份验证,我们需要实现UserDetailsService
interface. Once the DAO is configured, it’s loadUserByUsername()
is used to validate the user.
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
instance by using anonymous inner class implementation. Ideally, we should have an implementation class for UserDetails
that can have other user data also, such as emailID, user name, address etc.
请注意,只有用户名为“;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 Security示例Bean配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://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>
<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="https://www.springframework.org/schema/security"
xmlns:beans="https://www.springframework.org/schema/beans" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
https://www.springframework.org/schema/beans https://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"
>
<beans:property name="rolePrefix" value=""></beans:property>
</beans:bean>
<beans:bean id="accessDecisionManager"
>
<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"
/>
<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"
>
<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"
>
<beans:property name="jndiName" value="java:comp/env/jdbc/MyLocalDB" />
</beans:bean>
</beans:beans>
accessDecisionManager
bean is defined so that we can have our custom roles, by default all the roles should start with ROLE_ and we are overriding this setting in the roleVoter
bean property rolePrefix
.
我们可以在spring安全配置中定义多个身份验证管理器。我已经定义了in-memory-auth
for in-memory authentication, dao-auth
for UserDetailsService DAO implementation and jdbc-auth
for JDBC authentication. For JDBC authentication, I have provided configuration for DataSource defined in the application as well as if we want to use JNDI resource defined in the servlet container.
http协议authentication-manager-ref
is used to define the authentication manager that will be used for authenticating the user. Currently it’s configured to use the JDBC based authentication.
http协议access-decision-manager-ref
is used to specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests.
intercept-url
is used to define the URL pattern and authorities of the user who can access this page. For example, we have defined that URI “/emp/**” can be accessible only by users having “Admin” access.
form-login
defines the login form configuration and we can provide the username and password parameter names. authentication-failure-url
is used to define the URL for the authentication failure page. If no login failure URL is specified, Spring Security will automatically create a failure login URL at /spring_security_login?login_error and a corresponding filter to render that login failure URL when requested.
default-target-url
is used to define the default URL that will be redirected to after successful authentication, if the user’s previous action could not be resumed. This generally happens if the user visits a login page without having first requested a secured operation that triggers authentication. If unspecified, it defaults to the root of the application.
logout
is used to define the logout processing filter. Here we are invalidating the session and sending the user to login page after successful logout. logout-url
is used to define the URL to be used for logout action.
access-denied-handler
defines the global error page if the user is denied the access, because he is not authorized to perform the specified action.
session-management
will add a SessionManagementFilter filter to the filter stack for Session Management.
我们还有一些最重要的配置。
Spring Security示例视图页面
在部署和测试应用程序之前,让我们快速浏览一下我们的视图页面。
home.jsp
<%@ taglib uri="https://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>
主页.jsp为“;/home”;URI返回,它不需要任何身份验证。
employee.jsp
<%@ taglib uri="https://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="https://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是“;/Spring安全检查“;。这是默认的登录处理URL,就像注销URL一样。
另一个重要的点是用户名和密码的表单参数名称。它们应该与Spring Security配置中配置的相同。
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>
注销.jsp以及jsp.jsp被拒绝页面很简单,但是我们可以根据用户的详细信息在这里包含一些信息。
我们的Spring Security示例应用程序已经准备好进行测试了,请注意,对于JDBC身份验证,我使用的设置与之前相同Spring Security示例. 所以如果你直接降落在这里,你应该检查一下。
Spring Security MVC示例测试
只需将应用程序部署到您最喜欢的servlet容器中,我的容器是apachetomcat7。下面的图片向我们展示了不同url的不同输出。
Spring安全示例–;当请求启用身份验证的页面时登录页面(/emp/get/{20})
Spring Security示例–身份验证成功时的响应页面
Spring安全示例–;身份验证失败时拒绝访问页面