写点什么

Spring Security 系列教程 04-- 实现 Form 表单认证

作者:一一哥
  • 2022 年 8 月 30 日
    上海
  • 本文字数:6469 字

    阅读完需:约 21 分钟

Spring Security系列教程04--实现Form表单认证

前言

在上一章节中,一一哥 带大家认识了 Spring Security 中的第一种认证方式,但是这种基本认证的方式,UI 效果不漂亮,安全性也很差,好像一无是处的样子,那么有没有更好一点的认证方式呢?有的!接下来我给大家介绍一个新的认证方式,即 Form 表单认证。

一. Form 表单认证

1. 认证方式

我们从前文中得知,Spring Security 中的认证方式可以分为 HTTP 层面和表单层面,常见的认证方式如下:

  • ①. HTTP 基本认证;

  • ②. Form 表单认证;

  • ③. HTTP 摘要认证;

2. 表单认证简介

对于表单认证,其实在 SpringBoot 开发环境中,只要我们添加了 Spring Security 的依赖包,就会自动实现表单认证。是这样的吗? 我们来看看源码吧,在 WebSecurityConfigurerAdapter 类的 config(HttpSecurity http)方法中,可以看到如下默认实现。



所以在 SpringBoot 环境中,默认支持的就是表单认证方式。

3. 表单认证效果

各位回想一下,我们在之前的章节中,创建的第一个 Spring Security 项目中实现的效果,其实就是表单认证。每次我们在访问某个 Web 接口之前,都会重定向到一个 Security 自带的 login 登录页面上,这个登录页面,就是表单认证的效果。


4. 表单认证中的预置 url 和页面

这时候有的小伙伴可能就会很好奇,为什么表单认证会有以上效果?这是因为在默认的 formLogin 配置中,自动配置了一些 url 和页面:

  • /login(get): get 请求时会跳转到这个页面,只要我们访问任意一个需要认证的请求时,都会跳转到这个登录界面。

  • /login(post): post 请求时会触发这个接口,在登录页面点击登录时,默认的登录页面表单中的 action 就是关联这个 login 接口。

  • /login?error: 当用户名或密码错误时,会跳转到该页面。

  • /: 登录成功后,默认跳转到该页面,如果配置了 index.html 页面,则 ”/“ 会重定向到 index.html 页面,当然这个页面要由我们自己实现。

  • /logout: 注销页面。

  • /login?logout: 注销成功后跳转到的页面。


由此可见,SpringSecurity 默认有两个 login,即登录页面和登录接口的地址都是 /login:


如果是 GET 请求,表示你想访问登录页面;如果是 POST 请求,表示你想提交登录数据。


对于这几个 URL 接口,我们简单了解即可。

二. 自定义表单认证配置

在第一节中,我们只是了解了 Spring Security 内部是如何实现默认的表单认证的,那么我们如何自定义表单认证配置呢?


接下来 一一哥 带各位创建一个新的 model,讲解如何实现自定义表单认证,项目的具体创建过程略,请参考《Spring Security 系列教程 03--创建 SpringSecurity 项目》。

1. 创建 SecurityConfig 配置类

我们先编写一个类,继承自 WebSecurityConfigurerAdapter 父类,该类的作用如下:

  • 验证所有请求;

  • 允许用户使用表达登录进行身份验证;

  • 允许用户使用 Http 基本认证。


代码如下:

package com.yyg.security.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/** * @Author: 一一哥 * @Blame: yyg * @Since: Created in 2021/4/14 */@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http);
//配置表单认证方式 http.authorizeRequests() .anyRequest() .authenticated() .and() //开启表单认证 .formLogin(); }
}
复制代码


我们在 SecurityConfig 类上添加 @EnableWebSecurity 注解后,会自动被 Spring 发现并注册。在 configure()方法中,我执行了 formLogin()方法,该方法的功能就是开启表单认证。

2. 代码结构

我们看看现在的代码结构,跟上一篇文章的代码结构其实是一样的,小伙伴们可以直接在你上一个案例基础之上直接修改,我这里为了方便大家查看,所以有新建了一个新的 model。

3. 启动项目

接下来启动项目,访问我们定义的/hello 接口时,首先会重定向到/login 页面。



我们只有输入自己配置的用户名和密码后,才可以正常访问/hello 接口。



当认证成功后,内部再次发生了 302 重定向:



可见从/login 接口重定向到了/hello 接口。这样我们通过几行代码,就实现了自定义的表单认证,是不是也很简单?简单就对了,其实我们开发时用到的各种框架,使用起来都并不难,主要是掌握其用法和内部原理即可。

三. 自定义表单认证的登录界面

但是有的小伙伴可能会说,这个默认提供的表单登录页面看起来有点丑,或者与我们项目中其他页面的 UI 风格并不一致,能不能自定义这个登录页面呢?


一一哥 在第一章节中给大家介绍 Spring Security 时就说过,它的一个特点就在于可以高度自定义,灵活配置,所以我们当然可以自定义一个登录页面。

1. 服务端代码定义

如果想实现自定义登录页面,我们可以在上面定义的 SecurityConfig 类中,复写 configure(WebSecurity web) 和 configure(HttpSecurity http) 方法,通过 loginPage()方法来配置自定义登录页面,代码如下:

package com.yyg.security.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/** * @Author: 一一哥 * @Blame: yyg * @Since: Created in 2021/4/14 */@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
/** * 用来定义哪些请求需要忽略安全控制,哪些请求必须接受安全控制;还可以在合适的时候清除SecurityContext以避免内存泄漏, * 同时也可以用来定义请求防火墙和请求拒绝处理器,另外我们开启Spring Security Debug模式也是这里配置的 */ @Override public void configure(WebSecurity web) throws Exception { //super.configure(web); web.ignoring() .antMatchers("/js/**", "/css/**", "/images/**"); }
@Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http);
//2.配置自定义的登录页面 http.authorizeRequests() .anyRequest() .authenticated() .and() .formLogin() //加载自定义的登录页面地址 .loginPage("/myLogin.html") .permitAll() .and() //注意:需禁用crsf防护功能,否则登录不成功 .csrf() .disable(); }
}
复制代码


以上这段代码,就是实现自定义登录页面的核心代码了。


你会发现实现自定义表单认证的代码很简单,但是 Spring Security 内部是如何加载我们自定义的登录页面的呢?有哪些类或方法来负责完成呢?如果想要弄明白这个原理,我们需要先了解 2 个核心参数:WebSecurity 和 HttpSecurity,接下来我带各位分别了解一下这 2 个核心参数。

1.1 WebSecurity 执行流程

我们先看下图:



这是我们请求时,WebSecurity 的执行流程图。


在 configure(WebSecurity web)方法中,有个核心参数:WebSecurity 类!在这个类里定义了一个 securityFilterChainBuilders 集合,可以同时管理多个 SecurityFilterChain 过滤器链,各位可以回顾我们学习 Web 基础时,关于过滤器的知识点,这些过滤器的执行是不是比 Servlet 更早?


当 WebSecurity 在执行时,会构建出一个名为 ”springSecurityFilterChain“Spring BeanFilterChainProxy 代理类,它的作用是来 定义哪些请求可以忽略安全控制,哪些请求必须接受安全控制;以及在合适的时候 清除 SecurityContext 以避免内存泄漏,同时也可以用来 定义请求防火墙和请求拒绝处理器,也可以在这里 开启 Spring Security 的 Debug 模式


因为有上面这一系列的 Filter 过滤器,我们就可以利用 web.ignoring() 方法来配置想要忽略的静态资源 URL 地址,这样这些静态资源就可以不被拦截,从而可以被识别访问了。

1.2 HttpSecurity 作用

了解完了 WebSecurity 的作用之后,一一哥 再带各位学习一下 HttpSecurity 的作用。



HttpSecurity 用来构建包含一系列的过滤器链 SecurityFilterChain,平常我们的配置就是围绕着这个 SecurityFilterChain 进行。

2. 页面定义

了解完内部执行原理,接下来我们进行代码实现,首先自定义一个登陆页面,主要是编写 html 代码和 css 样式,各位小伙伴可以发挥你们的前端能力,编写自己满意的登录页面吧,其核心代码如下:


<body>    <div class="login">        <h2>Access Form</h2>        <div class="login-top">            <h1>登录验证</h1>            <form action="/myLogin.html" method="post">                <input type="text" name="username" placeholder="username" />                <input type="password" name="password" placeholder="password" />                <div class="forgot">                    <a href="#">忘记密码</a>                    <input type="submit" value="登录" >                </div>            </form>        </div>        <div class="login-bottom">            <h3>新用户&nbsp;<a href="#">注&nbsp;册</a></h3>        </div>    </div></body>
复制代码

注意:

form 表单中 action 的值,请暂时先写成”/myLogin.html“。

3. 代码结构

此时我们项目的包结构如下图所示:


4. 启动项目

启动项目后,我们访问接口时,就会自动跳转到自己定义的/myLogin.html 页面上,输入用户名和密码后,就可以成功访问自己的接口。



这时候小伙伴会发现,我们的登录页面比之前默认的登录页面漂亮多了,赞一个呗!

四. 细化表单认证配置

在上面的章节中,我们实现了自定义的表单认证登录页面,但是有些小伙伴可能觉得以上的案例,并不够灵活,尤其是一些关于表单的参数,能不能自定义呢?一一哥 可以负责的告诉你,完全没问题!所以我们进一步细化表单认证的配置,以满足我们更多的需求。


比如我们现在想修改表单认证页面中请求参数的名称,定义认证失败时的错误处理页面,处理退出登录时的操作等,这些都可以自定义配置,实现代码如下。

1. 定义 SecurityConfig 类

package com.yyg.security.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/** * @Author: 一一哥 * @Blame: yyg * @Since: Created in 2021/4/14 */@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
/** * 用来定义哪些请求需要忽略安全控制,哪些请求必须接受安全控制;还可以在合适的时候清除SecurityContext以避免内存泄漏, * 同时也可以用来定义请求防火墙和请求拒绝处理器,另外我们开启Spring Security Debug模式也是这里配置的 */ @Override public void configure(WebSecurity web) throws Exception { //super.configure(web); web.ignoring() .antMatchers("/js/**", "/css/**", "/images/**"); }
@Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http);
//3.进一步配置自定义的登录页面 //拦截请求,创建FilterSecurityInterceptor http.authorizeRequests() .anyRequest() .authenticated() //用and来表示配置过滤器结束,以便进行下一个过滤器的创建和配置 .and() //设置表单登录,创建UsernamePasswordAuthenticationFilter .formLogin() .loginPage("/myLogin.html") .permitAll() //指登录成功后,是否始终跳转到登录成功url。它默认为false .defaultSuccessUrl("/index.html",true) //post登录接口,登录验证由系统实现 .loginProcessingUrl("/login") //用户密码错误跳转接口 .failureUrl("/error.html") //要认证的用户参数名,默认username .usernameParameter("username") //要认证的密码参数名,默认password .passwordParameter("password") .and() //配置注销 .logout() //注销接口 .logoutUrl("/logout") //注销成功后跳转到的接口 .logoutSuccessUrl("/myLogin.html") .permitAll() //删除自定义的cookie .deleteCookies("myCookie") .and() //注意:需禁用crsf防护功能,否则登录不成功 .csrf() .disable(); }
}
复制代码

2. 自定义登录页面

这时候我们把登录页面也跟着修改一下,主要是把 form 表单中 action 的值修改掉。


<body>    <div class="login">        <h2>Access Form</h2>        <div class="login-top">            <h1>登录验证</h1>            <form action="/login" method="post">                <input type="text" name="username" placeholder="username" />                <input type="password" name="password" placeholder="password" />                <div class="forgot">                    <a href="#">忘记密码</a>                    <input type="submit" value="登录" >                </div>            </form>        </div>        <div class="login-bottom">            <h3>新用户&nbsp;<a href="#">注&nbsp;册</a></h3>        </div>    </div></body>
复制代码

注意:

此时 form 表单中 action 的值,要写成”/login“,因为我们在配置类中通过“loginProcessingUrl("/login")”方法中做了明确的配置。

3. 定义错误处理页面

小伙伴又提问了,当我们输入了错误的用户名和密码后,这时候会怎么办呢?其实这里我们可以提供一个错误处理页面,当认证失败后跳转到这个页面即可。我们这里简单实现一下,另外 index.html 页面与该页面类似,就是简单模拟。


4. 代码结构

此时项目的包结构如下图所示,各位可以参考:


4. 启动项目测试

这时候我们如果访问自己的接口,比如/hello,会先重定向到/myLogin.html 页面,输入自己配置的用户名和密码,经过验证后,才会重定向到/index.html 页面中,否则会重定向到我们配置的/error.html 页面中。

4.1 重定向到/myLogin.html

访问资源时,会先重定向到自定义的登录页面。


4.2 重定向到/index.html

认证成功后,会重定向到 index.html 页面。


4.3 重定向到/error.html

认证失败后,会重定向到自定义的错误处理页面。



通过本案例,各位会发现,Spring Security 的配置是很灵活的,当然这只是我们学习的开端,后面还有更多内容要学习,坚持往下看哦!有什么问题请各位在评论区留言......

发布于: 13 小时前阅读数: 17
用户头像

一一哥

关注

还未添加个人签名 2022.08.30 加入

还未添加个人简介

评论

发布
暂无评论
Spring Security系列教程04--实现Form表单认证_springboot_一一哥_InfoQ写作社区