初识
- Spring Security提供了基于Java EE的企业应用软件全面的安全服务,“认证”和“授权”这两个主要区域是Spring Security的两个目标
- 在身份验证层,Spring Security的支持多种认证模式:LDAP ,OpenID 等技术集成的身份验证以及自定义认证。
- Spring Security提供一套的授权功能。这里有三个主要的热点区域,授权web请求、授权方法是否可以被调用和授权访问单个域对象的实例。
Spring Security认证过程
- 用户使用用户名和密码进行登录。
- Spring Security将获取到的用户名和密码封装成一个实现了Authentication接口的UsernamePasswordAuthenticationToken。
- 将上述产生的token对象传递给AuthenticationManager进行登录认证。
- AuthenticationManager认证成功后将会返回一个封装了用户权限等信息的Authentication对象。
- 通过调用SecurityContextHolder.getContext().setAuthentication(...)将AuthenticationManager返回的Authentication对象赋予给当前的SecurityContext。
- 在认证成功后,用户可以继续操作去访问其它受保护的资源,但是在访问的时候将会使用保存在SecurityContext中的Authentication对象进行相关的权限鉴定。
Web应用的认证过程
如果用户直接访问登录页面,那么认证过程跟上节描述的基本一致,只是在认证完成后将跳转到指定的成功页面,默认是应用的根路径。如果用户直接访问一个受保护的资源,那么认证过程将如下:
- 引导用户进行登录,通常是重定向到一个基于form表单进行登录的页面,具体视配置而定。
- 用户输入用户名和密码后请求认证,后台获取用户名和密码封装成一个UsernamePasswordAuthenticationToken对象,然后把它传递给AuthenticationManager进行认证。
- 如果认证失败将继续执行步骤1,如果认证成功则会保存返回的Authentication到SecurityContext,然后默认会将用户重定向到之前访问的页面。
- 用户登录认证成功后再次访问之前受保护的资源时就会对用户进行权限鉴定,如不存在对应的访问权限,则会返回403错误码。
基于Maven搭建环境
项目结构
框架:SpringMvc
pom依赖
采用父级pom version:
- spring:4.1.7.RELEASE
- spring.security:4.0.1.RELEASE(尝试使用security4.1.1时启动失败)
<?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/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ideal-demos</artifactId>
<groupId>com.ideal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<artifactId>spring-security-demo</artifactId>
<dependencies>
<!-- Spring dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<!-- jstl for jsp page -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>
配置文件
1.Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/app-*.xml</param-value>
</context-param>
<!-- Spring 监听器, 启动Web容器时,自动装配ApplicationContext的配置信息 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--springMVC web控制 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--springSecurity -->
<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>
<!-- 编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
2.conf/spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 自动扫描controller包下的所有类,使其认为spring mvc的控制器 -->
<context:component-scan base-package="com.ideal.*.controller"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
3.conf/app-conf.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 自动扫描service包下的所有类 -->
<context:component-scan base-package="com.ideal"/>
<!--<context:property-placeholder location="classpath:*.properties"/>-->
<!-- 将多个配置文件读取到容器中 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<!-- <value>classpath*:/resources/jdbc.properties</value>
<value>classpath*:/resources/common.properties</value>
<value>classpath*:/resources/message_zh_CN.properties</value>
<value>classpath*:/resources/remote.properties</value>-->
</list>
</property>
</bean>
</beans>
4.conf/app-security.xml
官网:
http://docs.spring.io/spring-security/site/docs/4.1.1.RELEASE/reference/htmlsingle/
<?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/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<http pattern="/login.jsp" security="none"/>
<http pattern="/index.jsp" security="none"/>
<http pattern="/error.jsp" security="none"/>
<http auto-config="true">
<!--intercept-url有拦截顺序,如果下面两个被反转了./**会一直 被匹配,/security/admin**就永远也不会执行。-->
<!--满足该条件的请求需要有ADMIN角色-->
<intercept-url pattern="/security/admin**" access="hasRole('ROLE_ADMIN')"/>
<!--pattern="/**" 对所有路径进行角色认证-->
<intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
<!--自定义登录页面是通过login-page属性来指定的。提到login-page我们不得不提另外几个属性。
1、username-parameter:表示登录时用户名使用的是哪个参数,默认是“j_username”。
2、password-parameter:表示登录时密码使用的是哪个参数,默认是“j_password”。
3、login-processing-url:表示登录时提交的地址,默认是“/j-spring-security-check”。这个只是Spring Security用来标记登录页面使用的提交地址,真正关于登录这个请求是不需要用户自己处理的。
4、authentication-success-handler-ref:使用了authentication-success-handler-ref之后认证成功后的处理就由指定的AuthenticationSuccessHandler来处理
5、authentication-failure-url:通过authentication-failure-url指定登录失败后的页面
6、authentication-failure-handler-ref:对应一个用于处理认证失败的AuthenticationFailureHandler实现类。指定了该属性,Spring Security在认证失败后会调用指定AuthenticationFailureHandler的onAuthenticationFailure方法对认证失败进行处理,此时authentication-failure-url属性将不再发生作用。
-->
<form-login
login-page="/login.jsp"
login-processing-url="/login.do"
username-parameter="username"
password-parameter="password"
authentication-failure-url="/error.jsp"
/>
<!--要实现退出登录的功能我们需要在http元素下定义logout元素,这样Spring Security将自动为我们添加用于处理退出登录的过滤器LogoutFilter到FilterChain。
当我们指定了http元素的auto-config属性为true时logout定义是会自动配置的,此时我们默认退出登录的URL为“/logout”,
可以通过logout元素的logout-url属性来改变退出登录的默认地址。
1、logout-url:改变退出登录的默认地址,这里需要注意的一点是,spring security 3.x默认的注销拦截url为/j_spring_security_logout,而4.x则默认使用/logout
2、invalidate-session:表示是否要在退出登录后让当前session失效,默认为true。
3、delete-cookies:指定退出登录后需要删除的cookie名称,多个cookie之间以逗号分隔。
4、logout-success-url:指定成功退出登录后要重定向的URL。需要注意的是对应的URL应当是不需要登录就可以访问的。
5、success-handler-ref:指定用来处理成功退出登录的LogoutSuccessHandler的引用。
-->
<logout invalidate-session="true"
logout-success-url="/login.jsp"
/>
<!--Spring Security 4默认启用了CSRF保护功能(false),该功能在Spring Security 3时就已经存在默认是不启用,该功能防止跨站请求伪造攻击;
在提交请求时,该请求被CsrfFilter拦截,验证_csrf的token是否有效。
-->
<csrf disabled="true"/>
</http>
<authentication-manager>
<authentication-provider>
<!--具有角色的用户信息,ideal只有普通用户USER权限-->
<user-service>
<user name="ideal" password="ideal" authorities="ROLE_USER"/>
<user name="admin" password="admin" authorities="ROLE_USER,ROLE_ADMIN"/>
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
controller
package com.ideal.security.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
/**
* @author iDeaL
* @since 2016/8/12.
*/
@Controller
@RequestMapping("security")
public class SecurityController {
@RequestMapping(value = {"/home"}, method = RequestMethod.GET)
public ModelAndView homePage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "home Page");
model.addObject("message", "Welcome to home Page");
model.setViewName("request");
return model;
}
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public ModelAndView adminPage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "admin Page");
model.addObject("message", "Welcome to admin Page");
model.setViewName("request");
return model;
}
}
页面
- 自定义登录认证页面login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>自定义登录页面</title>
</head>
<body>
<h3>自定义登录页面</h3>
<form action="/login.do" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"/></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="登录"/>
<input type="reset" value="重置"/>
</td>
</tr>
</table>
</form>
</body>
</html>
- 请求相应页面 request.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<h1>Title : ${title}</h1>
<h1>Message : ${message}</h1>
<%--<form action="/logout" method="post">
<input type="submit" value="Logout"/>
</form>--%>
<h2> <a href="<c:url value="/logout" />" > Logout</a></h2>
</body>
</html>
测试
-
服务启动,请求"/security/home",自动跳转到自定义登录认证页面
-
认证ideal成功
-
不注销继续访问"/security/admin",ideal无admin权限
- 注销返回到指定的跳转页面login.jsp