作者:jiangzz
电话:15652034180
微信:jiangzz_wx
微信公众账号:jiangzz_wy
Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求。
快速入门
- 在pom.xml文件中额外添加如下依赖
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
- 在applicationContext.xml引入如下配置
<!--引入security配置文件-->
<import resource="securityContext.xml"/>
- securityContext.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:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<!--配置SecurityFilterChain 最简单配置-->
<security:http>
<security:intercept-url pattern="/**" access="isAuthenticated()"/>
<security:form-login/>
<security:logout/>
</security:http>
<!--创建 ProviderManager 负责认证-->
<security:authentication-manager>
<!--DaoAuthenticationProvider负责查询用户-->
<security:authentication-provider>
<!--创建InMemoryDaoImpl-->
<security:user-service>
<!--表示一个UserDetail,其中{noop}表示没有使用密码编码器-->
<security:user name="zhangsan" password="{noop}123456" authorities="ROLE_USER"/>
<security:user name="admin" password="{noop}123456" authorities="ROLE_USER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
- 在web.xml如下Filter
<!--配置SpringSecurity安全框架-->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>encode</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意这里的filtername必须配置成
springSecurityFilterChain
不允许用户胡乱修改。也可以这么配置如下:
<filter>
<filter-name>securityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSecurityFilterChain</param-value>
</init-param>
</filter>
- 编辑登录首页
<%@page pageEncoding="UTF-8" contentType="text/html;charset=utf-8" isELIgnored="false" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<html>
<body>
<security:authentication property="principal.username" var="name"/>
<h2>欢迎${name}登录成功!<a href="logout">退出</a></h2>
</body>
</html>
启动web服务访问首页
为什么Filter的name必须是springSecurityFilterChain
Spring Security在做权限限定的时候,通过一个前端过滤器将DelegatingFilterProxy拦截用户的web请求,底层将拦截到的请求委派给FilterChainProxy
。其中FilterChainProxy
中持有一个List<SecurityFilterChain>
这些SecurityFilterChain
就对应者securityContext.xml中的http
标签。Spring容器在启动的时候会解析securityContext.xml文件中的一些标签,目的就是为例创建FilterChainProxy
并且该类的bean的id就叫做springSecurityFilterChain.该bean的注册在FilterChainBeanDefinitionParser
中注册。
注册FilterChainProxy
自定义登录页
自定义登录页
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<head>
<title>用户登录</title>
</head>
<body>
<form action="userlogin" method="post">
<input type="text" name="username" placeholder="用户名"/>
<input type="password" name="password" placeholder="密码"/>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<button type="submit" class="btn">Log in</button>
</form>
</body>
</html>
配置<http>
标签
<security:http use-expressions="false">
<security:intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<security:form-login login-page="/login.jsp"
login-processing-url="/userlogin"
username-parameter="username"
password-parameter="password"
default-target-url="/"
/>
<security:logout logout-url="/logout"
logout-success-url="/login.jsp"
invalidate-session="true" />
</security:http>
该技术称为夸站点请求伪造,Spring Security为了防止夸站点请求伪造,默认在所有的Post请求中会对所有的表单提交做校验。默认的实现是通过每次用户登录的时候随机生成一个token存储在用户当前会话的session中,用户在做post提交表单的时候必须携带当前token。这样如果用户异步小心点击了其他网站,但是由于Http的同源策略,在其他恶意的网站上由于获取不到系统的Token数据,因此任何恶意的操作就可以有效的避免。
自定义退出
配置<http>
标签
<security:http use-expressions="false">
<security:intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<security:form-login login-page="/login.jsp"
login-processing-url="/userlogin"
username-parameter="username"
password-parameter="password"
default-target-url="/"
/>
<security:logout logout-url="/logout" logout-success-url="/login.jsp" invalidate-session="true" />
</security:http>
定制退出表单
<%@page pageEncoding="UTF-8" contentType="text/html;charset=utf-8" isELIgnored="false" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<html>
<body>
<security:authentication property="principal.username" var="name"/>
<h2>欢迎${name}登录成功!</h2>
<form action="logout" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<button type="submit">退出</button>
</form>
</body>
</html>
用户数据库登录
配置authentication-manager标签
<!--配置SecurityFilterChain 最简单配置-->
<security:http use-expressions="false">
<security:intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<security:form-login login-page="/login.jsp"
login-processing-url="/userlogin"
username-parameter="username"
password-parameter="password"
default-target-url="/"
/>
<security:logout logout-url="/logout" logout-success-url="/login.jsp" invalidate-session="true" />
</security:http>
<!--创建 ProviderManager 负责认证-->
<security:authentication-manager>
<!--DaoAuthenticationProvider负责查询用户-->
<security:authentication-provider >
<!--创建InMemoryDaoImpl-->
<security:user-service>
<!--表示一个UserDetail,其中{noop}表示没有使用密码编码器-->
<security:user name="zhangsan" password="{noop}123456" authorities="ROLE_USER"/>
<security:user name="admin" password="{noop}123456" authorities="ROLE_USER,ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
<security:authentication-provider user-service-ref="webUserDetailService">
<security:password-encoder ref="webPasswordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--配置用户服务-->
<bean id="webUserDetailService" class="com.jiangzz.security.WebUserDetailService"/>
<bean id="webPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
WebUserDetailService
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> authorities=new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12,
new SecureRandom(username.getBytes()));
return new User(username,encoder.encode("123456"),authorities);
}
记住我
配置记住我
<security:http use-expressions="false" >
<security:intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<!--记住我-->
<security:remember-me user-service-ref="webUserDetailService"
remember-me-cookie="rem"
remember-me-parameter="remember"
token-validity-seconds="3600"
key="key123"/>
<security:form-login login-page="/login.jsp"
login-processing-url="/userlogin"
username-parameter="username"
password-parameter="password"
default-target-url="/"
/>
<security:logout logout-url="/logout"
logout-success-url="/login.jsp"
invalidate-session="true" />
</security:http>
Session Management
超时检测
<security:http pattern="/session_timeout.jsp" security="none"/>
<security:http use-expressions="false" >
<!--安全拦截-->
<security:intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<!--记住我-->
<security:remember-me user-service-ref="webUserDetailService"
remember-me-cookie="rem"
remember-me-parameter="remember"
token-validity-seconds="3600"
key="key123"/>
<!--用户登录 -->
<security:form-login login-page="/login.jsp"
login-processing-url="/userlogin"
username-parameter="username"
password-parameter="password"
default-target-url="/"
/>
<!--测试退出-->
<security:logout logout-url="/logout" logout-success-url="/login.jsp" invalidate-session="true" />
<!--session 管理-->
<security:session-management invalid-session-url="/session_timeout.jsp" />
</security:http>
为了能看到效果,用户需在web.xml配置会话超时时间
<session-config>
<!--配置session超时,单位是分钟-->
<session-timeout>1</session-timeout>
</session-config>
并发会话控制
在web.xml添加如下监听器
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
修改session-management标签
<security:session-management invalid-session-url="/session_timeout.jsp" >
<security:concurrency-control max-sessions="1" />
</security:session-management>
这种配置意味着,如果同一个账户登录同时在线登录次数操过1次,则第一个登录的账户将会收到如下提示
This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).
通常在设计的时候,当用户登录成功后,如果有别的账户登录则需要阻止第二次登录,而不是将第一次登录失效
<!--用户登录 -->
<security:form-login login-page="/login.jsp"
login-processing-url="/userlogin"
username-parameter="username"
password-parameter="password"
default-target-url="/"
authentication-failure-url="/login.jsp"
/>
<security:session-management invalid-session-url="/session_timeout.jsp" >
<security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</security:session-management>
此时如果第二次登录系统会自动的跳转到authentication-failure-url
指定的页面。如果用户开启的记住我功能,一不小心将页面丢失了,即使用户在尝试去访问,也访问不了,因为系统认为用户的session会话没有失效。用户可以通过设置session-authentication-error-url
设置错误页面,防止看到HTTP Status 401 - Unauthorized错误
<security:session-management invalid-session-url="/session_timeout.jsp"
session-authentication-error-url="login.jsp" >
<security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</security:session-management>