Spring Security (章节1)

作者: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>

更多精彩内容关注

微信公众账号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值