Lison
<dreamlison@163.com>
, v1.0.0
, 2024.06.01
Spring Security-02-Spring Security认证方式-HTTP基本认证、Form表单认证、HTTP摘要认证、前后端分离安全处理方案
文章目录
- Spring Security-02-Spring Security认证方式-HTTP基本认证、Form表单认证、HTTP摘要认证、前后端分离安全处理方案
- 认证方式
- HTTP基本认证
- 基本认证简介
- 基本认证核心API
- 基本认证的步骤
- 基本认证的弊端
- HTTP基本认证代码实现
- 创建SecurityConfig配置类
- 测试验证
- Basic认证详解
- 基本认证过程
- 注销Basic认证
- Form表单认证
- 表单认证简介
- 表单认证效果
- 表单认证中的预置url和页面
- 自定义表单认证配置
- 创建SecurityConfig配置类
- 测试应用
- 自定义表单认证的登录界面
- 服务端代码定义
- 页面定义
- 测试应用
- 细化表单认证配置
- 定义SecurityConfig类
- 自定义登录页面
- 定义错误处理页面
- 测试验证
- HTTP摘要认证
- HTTP摘要认证简介
- HTTP摘要认证核心参数
- 摘要认证代码实现
- 编写测试接口
- 创建SecurityConfig配置类
- 测试接口
- HTTP摘要认证弊端
- 前后端分离时的安全处理方案
- 前后端分离简介
- 认证处理时的相关API
- 页面跳转的相关API
- 返回JSON格式的处理器
- 认证成功时的处理方案
- 1. successHandler()方法
- 2. onAuthenticationSuccess参数
- 定义SecurityAuthenticationSuccessHandler类
- 配置successHandler
- 验证结果
- 认证失败时的处理方案
- failureHandler()
- 代码实现
- 配置failureHandler
- 验证结果
- 退出登录时的处理方案
- 1. logoutSuccessHandler()
- 2. 定义SecurityLogoutSuccessHandler类
- 配置logoutSuccessHandler
- 验证结果
- 未认证时的处理方案
- 1. authenticationEntryPoint()
- 2. 定义SecurityAuthenticationEntryPoint类
- 配置authenticationEntryPoint
- 验证结果
认证方式
认证: 所谓的认证,就是用来判断系统中是否存在某用户,并判断该用户的身份是否合法的过程,解决的其实是用户登录的问题。认证的存在,是为了保护系统中的隐私数据与资源,只有合法的用户才可以访问系统中的资源。
在Spring Security中,常见的认证方式可以分为HTTP层面和表单层面,常见的认证方式如下:
- HTTP基本认证;
- Form表单认证;
- HTTP摘要认证;
HTTP基本认证
基本认证简介
在Spring Security 4.x版本中,默认采用的登录方式是Http基本认证,该方式会弹出一个对话框,要求用户输入用户名和密码。在每次进行基本认证请求时,都会在Authorization请求头中利用Base64对 “用户:密码” 字符串进行编码。这种方式并不安全,并不适合在Web项目中使用,但它是一些现代主流认证的基础,而且在Spring Security的OAuth中,内部认证的默认方式就是用的Http基本认证。
基本认证核心API
执行流程如下:
Filter->构造Token->AuthenticationManager->转给Provider处理->认证处理成功后续操作或者不通过抛异常
基本认证的步骤
HTTP基本认证是在RFC2616标准中定义的一种认证模式,它以一种很简单的方式与用户进行交互。HTTP基本认证可以分为如下4个步骤:
- ①. 客户端首先发起一个未携带认证信息的请求;
- ②. 然后服务器端返回一个401 Unauthorized的响应信息,并在WWW-Authentication头部中说明认证形式:当进行HTTP基本认证时,WWW-Authentication会被设置为Basic realm=“被保护的页面”;
- ③. 接下来客户端会收到这个401 Unauthorized响应信息,并弹出一个对话框,询问用户名和密码。当用户输入后,客户端会将用户名和密码使用冒号进行拼接并用Base64编码,然后将其放入到请求的Authorization头部并发送给服务器;
- ④. 最后服务器端对客户端发来的信息进行解码得到用户名和密码,并对该信息进行校验判断是否正确,最终给客户端返回响应内容。
基本认证的弊端
HTTP基本认证是一种无状态的认证方式,与表单认证相比,HTTP基本认证是一种基于HTTP层面的认证方式,无法携带Session信息,也就无法实现Remember-Me功能。另外,用户名和密码在传递时仅做了一次简单的Base64编码,几乎等同于以明文传输,极易被进行密码窃听和重放攻击。所以在实际开发中,很少会使用这种认证方式来进行安全校验。
HTTP基本认证代码实现
创建SecurityConfig配置类
这里我们先创建一个config配置类,命名为SecurityConfig,并且继承自WebSecurityConfigurerAdapter父类,代码如下:
package com.lison.springsecurity.config.security;
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;
/**
* @className: com.lison.springsecurity.config.security-> SecurityConfig
* @description:
* @author: Lison
* @createDate: 2024-06-01
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//1.配置基本认证方式
http.authorizeRequests()
//对任意请求都进行认证
.anyRequest()
.authenticated()
.and()
//开启basic认证
.httpBasic();
}
}
httpBasic()方法,就是用来开启基本认证的,而且默认采用的就是基本认证!
测试验证
访问自己的/hello接口,这时候我们就可以看到浏览器中弹出了一个登陆窗口。提示我们输入自己的用户名和密码 lison/123456,认证成功后,即可访问自己的web接口。
Basic认证详解
基本认证过程
此时的响应码为401,什么情况下会导致401状态码?
根据401和以上响应头信息,浏览器会弹出一个对话框,要求输入 用户名/密码,Basic认证会将其拼接成 “用户名:密码” 格式,中间是一个冒号,并利用Base64编码成加密字符串xxx;然后在请求头中附加 Authorization: Basic xxx 信息,发送给后台认证;后台需要利用Base64来进行解码xxx,得到用户名和密码,再校验 用户名:密码 信息。
- 如果认证错误,浏览器会保持弹框;
- 如果认证成功,浏览器会缓存有效的Base64编码,在之后的请求中,浏览器都会在请求头中添加该有效编码。
注销Basic认证
在成功认证之后,Basic认证会把Authorization认证信息缓存在浏览器中一段时间,之后每次请求接口时都会自动带上,所以直到 用户关闭浏览器才会销毁认证信息,也就是说我们无法在服务端进行有效的注销。
不过在请求注销时,前端也可以手动 在请求头配置一个错误的Authorization,或者在浏览器的命令行执行 document.execuCommand(“ClearAuthenticationCache”)方法 来清空认证信息,但该方式对Chrome浏览器无效。我们在调试基本认证时,可以直接开启无痕模式,避免很多因为缓存造成的问题。
Form表单认证
表单认证简介
对于表单认证,其实在SpringBoot开发环境中,只要我们添加了Spring Security的依赖包,就会自动实现表单认证。在WebSecurityConfigurerAdapter类的config(HttpSecurity http)方法中,可以看到如下默认实现。
在SpringBoot环境中,默认支持的就是表单认证方式。
表单认证效果
第一个Spring Security项目中实现的效果,其实就是表单认证。每次我们在访问某个Web接口之前,都会重定向到一个Security自带的login登录页面上,这个登录页面,就是表单认证的效果
表单认证中的预置url和页面
这时候有的小伙伴可能就会很好奇,为什么表单认证会有以上效果?这是因为在默认的formLogin配置中,自动配置了一些url和页面:
- /login(get): get请求时会跳转到这个页面,只要我们访问任意一个需要认证的请求时,都会跳转到这个登录界面。
- /login(post): post请求时会触发这个接口,在登录页面点击登录时,默认的登录页面表单中的action就是关联这个login接口。
- /login?error: 当用户名或密码错误时,会跳转到该页面。
- /: 登录成功后,默认跳转到该页面,如果配置了index.html页面,则 ”/“ 会重定向到index.html页面,当然这个页面要由我们自己实现。
- /logout: 注销页面。
- /login?logout: 注销成功后跳转到的页面。
由此可见,SpringSecurity默认有两个login,即登录页面和登录接口的地址都是 /login:
- GET http://localhost:8080/login
- POST http://localhost:8080/login
如果是GET 请求,表示你想访问登录页面;如果是 POST 请求,表示你想提交登录数据。
对于这几个URL接口,我们简单了解即可。
自定义表单认证配置
创建SecurityConfig配置类
我们先编写一个类,继承自WebSecurityConfigurerAdapter父类,该类的作用如下:
- 验证所有请求;
- 允许用户使用表达登录进行身份验证;
- 允许用户使用Http基本认证
package com.lison.springsecurity.config.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
/**
* @className: com.lison.springsecurity.config.security-> SecurityConfig
* @description:
* @author: Lison
* @createDate: 2024-06-01
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置表单认证方式
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
//开启表单认证
.formLogin();
}
}
SecurityConfig类上添加@EnableWebSecurity注解后,会自动被Spring发现并注册。在configure()方法中,我执行了formLogin()方法,该方法的功能就是开启表单认证。
测试应用
启动项目,访问我们定义的/hello接口时,首先会重定向到/login页面。输入自己配置的用户名和密码后,才可以正常访问/hello接口。
当认证成功后,内部再次发生了302重定向:可见从/login接口重定向到了/hello接口。
自定义表单认证的登录界面
Spring Security一个特点就在于可以高度自定义,灵活配置,可以自定义一个登录页面。
服务端代码定义
package com.lison.springsecurity.config.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
/**
* @className: com.lison.springsecurity.config.security-> SecurityConfig
* @description:
* @author: Lison
* @createDate: 2024-06-01
*/
@Configuration
@EnableWebSecurity
public 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 {
//2.配置自定义的登录页面
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
//加载自定义的登录页面地址
.loginPage("/myLogin.html")
.permitAll()
.and()
//注意:需禁用crsf防护功能,否则登录不成功
.csrf()
.disable();
}
}
1、WebSecurity执行流程
在configure(WebSecurity web)方法中,有个核心参数:WebSecurity类!在这个类里定义了一个securityFilterChainBuilders集合,可以同时管理多个SecurityFilterChain过滤器链,各位可以回顾我们学习Web基础时,关于过滤器的知识点,这些过滤器的执行是不是比Servlet更早?
当WebSecurity在执行时,会构建出一个名为 ”springSecurityFilterChain“ 的 Spring BeanFilterChainProxy代理类,它的作用是来 定义哪些请求可以忽略安全控制,哪些请求必须接受安全控制;以及在合适的时候 清除SecurityContext 以避免内存泄漏,同时也可以用来 定义请求防火墙和请求拒绝处理器,也可以在这里 开启Spring Security 的Debug模式。
上面这一系列的Filter过滤器,我们就可以利用web.ignoring() 方法来配置想要忽略的静态资源 URL 地址,这样这些静态资源就可以不被拦截,从而可以被识别访问
2、HttpSecurity作用;
HttpSecurity用来构建包含一系列的过滤器链SecurityFilterChain,平常我们的配置就是围绕着这个SecurityFilterChain进行。
页面定义
首先自定义一个登陆页面,主要是编写html代码和css样式,其核心代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Page</title>
<style>
body {
background-color: #f2f2f2;
font-family: Arial, sans-serif;
}
.container {
max-width: 400px;
margin: 0 auto;
padding: 40px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
}
.btn {
display: block;
width: 100%;
padding: 10px;
background-color: #4caf50;
color: #fff;
font-weight: bold;
text-align: center;
text-decoration: none;
border: none;
border-radius: 3px;
cursor: pointer;
}
.btn:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<h2>Login</h2>
<form action="/myLogin.html" method="post">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" placeholder="Enter your username">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" placeholder="Enter your password">
</div>
<button class="btn" type="submit">Login</button>
</form>
</div>
</body>
</html>
测试应用
启动项目后,我们访问接口时,就会自动跳转到自己定义的/myLogin.html页面上,输入用户名和密码后,就可以成功访问自己的接口。
细化表单认证配置
修改表单认证页面中请求参数的名称,定义认证失败时的错误处理页面,处理退出登录时的操作等,这些都可以自定义配置,实现代码如下。
定义SecurityConfig类
package com.lison.springsecurity.config.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
/**
* @className: com.lison.springsecurity.config.security-> SecurityConfig
* @description:
* @author: Lison
* @createDate: 2024-06-01
*/
@Configuration
@EnableWebSecurity
public 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();
}
}
自定义登录页面
登录页面也跟着修改一下,主要是把form表单中action的值修改掉为 login
<body>
<div class="container">
<h2>Login</h2>
<form action="/login" method="post">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" placeholder="Enter your username">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" placeholder="Enter your password">
</div>
<button class="btn" type="submit">Login</button>
</form>
</div>
</body>
注意:此时form表单中action的值,要写成”/login“,因为我们在配置类中通过“loginProcessingUrl(“/login”)”方法中做了明确的配置
定义错误处理页面
输入了错误的用户名和密码后,可以提供一个错误处理页面,当认证失败后跳转到这个页面即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1> ERROR 错误页面</h1>
</body>
</html>
测试验证
如果访问自己的接口,比如/hello,会先重定向到/myLogin.html页面,输入自己配置的用户名和密码,经过验证后,才会重定向到/index.html页面中,否则会重定向到我们配置的/error.html页面中。
1、重定向到/myLogin.html
访问资源时,会先重定向到自定义的登录页面。
2、重定向到/index.html
认证成功后,会重定向到index.html页面。
3、重定向到/error.html
认证失败后,会重定向到自定义的错误处理页面。
HTTP摘要认证
HTTP摘要认证简介
HTTP摘要认证和HTTP基本认证一样,也是在RFC2616中定义的一种认证方式,它的出现是为了弥补HTTP基本认证存在的安全隐患,但该认证方式也并不是很安全**。HTTP摘要认证会使用对通信双方来说都可知的口令进行校验,且最终以密文的形式来传输数据,所以相对于基本认证来说,稍微安全了一些。
HTTP摘要认证与基本认证类似,基于简单的“挑战-回应”模型。当发起一个未经认证的请求时,服务器会返回一个401回应,并给客户端返回与验证相关的参数,期待客户端依据这些参数继续做出回应,从而完成整个验证过程
HTTP摘要认证核心参数
服务端给客户端返回的验证相关参数如下:
- username: 用户名。
- password: 用户密码。
- realm: 认证域,由服务器返回。
- opaque: 透传字符串,客户端应原样返回。
- method: 请求的方法。
- nonce: 由服务器生成的随机字符串,包含过期时间(默认过期时间300s)和密钥。
- nc: 即nonce-count,指请求的次数,用于计数,防止重放攻击。qop被指定时,nc也必须被指定。
- cnonce: 客户端发给服务器的随机字符串,qop被指定时,cnonce也必须被指定。
- qop: 保护级别,客户端根据此参数指定摘要算法。若取值为 auth,则只进行身份验证;若取值为auth-int,则还需要校验内容完整性,默认的qop为auth。
- uri: 请求的uri。
- response: 客户端根据算法算出的摘要值,这个算法取决于qop。
- algorithm: 摘要算法,目前仅支持MD5。
- entity-body: 页面实体,非消息实体,仅在auth-int中支持。
通常服务器端返回的数据包括realm、opaque、nonce、qop等字段,如果客户端需要做出验证回应,就必须按照一定的算法得到一些新的数据并一起返回**。在以上各种参数中,对服务器而言,最重要的字段是nonce;对客户端而言,最重要的字段是response**。
摘要认证代码实现
编写测试接口
package com.lison.springsecurity.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @className: com.lison.springsecurity.controller-> IndexController
* @description:
* @author: Lison
* @createDate: 2024-06-01
*/
@RestController
public class IndexController {
@GetMapping("/admin/hello")
public String helloAdmin() {
return "hello, admin";
}
@GetMapping("/user/hello")
public String helloUser() {
return "hello, user";
}
@GetMapping("/visitor/hello")
public String helloVisitor() {
return "hello, visitor";
}
}
创建SecurityConfig配置类
这里比较重要的是配置摘要认证入口端点DigestAuthenticationEntryPoint
package com.lison.springsecurity.config.security;
import com.lison.springsecurity.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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;
import org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
/**
* @className: com.lison.springsecurity.config.security-> SecurityConfig
* @description:
* @author: Lison
* @createDate: 2024-06-01
*/
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DigestAuthenticationEntryPoint digestAuthenticationEntryPoint;
@Autowired
private MyUserDetailsService userDetailsService;
//配置认证入口端点,主要是设置认证参数信息
@Bean
public DigestAuthenticationEntryPoint digestAuthenticationEntryPoint(){
DigestAuthenticationEntryPoint point=new DigestAuthenticationEntryPoint();
point.setKey("Security Demos");
point.setRealmName("lison");
point.setNonceValiditySeconds(500);
return point;
}
public DigestAuthenticationFilter digestAuthenticationFilter(){
DigestAuthenticationFilter filter=new DigestAuthenticationFilter();
filter.setAuthenticationEntryPoint(digestAuthenticationEntryPoint);
filter.setUserDetailsService(userDetailsService);
return filter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.hasRole("USER")
.antMatchers("/visitor/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf()
.disable()
//当未认证时访问某些资源,则由该认证入口类来处理.
.exceptionHandling()
.authenticationEntryPoint(digestAuthenticationEntryPoint)
.and()
//添加自定义过滤器到过滤器链中
.addFilter(digestAuthenticationFilter());
}
}
测试接口
访问一下需要认证的接口/admin/hello接口,这时候会发现浏览器弹出了一个用户名密码的认证窗口。然后我们在浏览器中可以看到HTTP摘要认证信息,realm是我们自定义的“lison”,qop是默认的“auth”方式。
由此可见,此时摘要认证的方式已经生效。
HTTP摘要认证弊端
HTTP摘要认证与HTTP基本认证一样,都是基于HTTP层面的认证方式,也不能使用Session对象,因而也不支持Remember-Me功能。该方式虽然解决了HTTP基本认证中以明文传输密码的问题,但并未解决密码明文存储的问题,所以依然有安全隐患,所以在开发中,摘要认证的方式也不怎么使用。
以上三种认证方式,我们需要重点掌握表单认证;
前后端分离时的安全处理方案
前后端分离简介
企业开发中,前后端分离已成为互联网项目开发的业界标准方式,其核心思想就是前端HTML页面通过AJAX,调用后端的RESTFUL API接口,并使用JSON数据进行交互****。
该方式可以有效的在前后端项目之间进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如 浏览器,车载终端,安卓,IOS等)打下坚实的基础。
认证处理时的相关API
页面跳转的相关API
1、登录成功时的跳转API
- defaultSuccessUrl
- successForwardUrl
2、登录失败时的跳转API
- failureUrl()
- failureForwardUrl()
返回JSON格式的处理器
在前后端分离模式下,既然后端没有页面,页面都在前端,那就可以考虑使用JSON来进行信息交互了,我们把认证成功或认证失败的信息,以JSON的格式传递给前端,由前端来决定到底该往哪个页面跳转;
我们要返回JSON格式的信息,有如下相关方法:
- successHandler()
- failureHandler()
- logoutSuccessHandler()
- authenticationEntryPoint()
- …
认证成功时的处理方案
相关的方法及其核心参数,即successHandler()和onAuthenticationSuccess参数。
1. successHandler()方法
successHandler()方法的功能十分强大,甚至也囊括了 defaultSuccessUrl()和 successForwardUrl() 的功能。
successHandler()方法的参数是一个 AuthenticationSuccessHandler 对象,这个对象中我们要实现的方法是 onAuthenticationSuccess()。
2. onAuthenticationSuccess参数
onAuthenticationSuccess() 方法中有三个参数,分别是:
- HttpServletRequest: 利用该参数我们可以实现服务端的跳转;
- HttpServletResponse: 利用该参数我们可以做客户端的跳转,也可以返回 JSON 数据;
- Authentication: 这个参数则保存了我们刚刚登录成功的用户信息。
定义SecurityAuthenticationSuccessHandler类
r认证成功后需要处理的类,要实现AuthenticationSuccessHandler 接口
/**
* 处理登录成功时的业务逻辑
*/
public class SecurityAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
/**
* Authentication:携带登录的用户名及角色等信息
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
//直接输出json格式的响应信息
Object principal = authentication.getPrincipal();
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
//以json格式对外输出身份信息
out.write(new ObjectMapper().writeValueAsString(principal));
out.flush();
out.close();
}
}
配置successHandler
在SecurityConfig配置类中,调用successHandler()方法,把前面定义的SecurityAuthenticationSuccessHandler类关联进来
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
//认证成功时的处理器
.successHandler(new SecurityAuthenticationSuccessHandler())
.and()
.csrf()
.disable();
}
}
验证结果
我们进行登录验证,在认证成功后,就可以看到登录成功的用户信息是通过 JSON 返回到前端的,如下图所示:
Spring Security会把认证的用户信息以JSON格式展示出来,比如我们的用户名、密码、角色等信息。
认证失败时的处理方案
相关的API方法及参数。
failureHandler()
failureHandler()方法的参数是一个 AuthenticationFailureHandler 对象,这个对象中我们要实现的方法是 onAuthenticationFailure()。
onAuthenticationFailure()方法有三个参数,分别是:
- HttpServletRequest: 利用该参数我们可以实现服务端的跳转;
- HttpServletResponse: 利用该参数我们可以做客户端的跳转,也可以返回 JSON 数据;
- AuthenticationException: 这个参数则保存了登录失败的原因。
代码实现
同样的,我们也要编写一个类SecurityAuthenticationFailureHandler,实现AuthenticationFailureHandler接口,来专门处理认证失败时的返回结果。
/**
* 处理登录失败时的业务逻辑
*/
public class SecurityAuthenticationFailureHandler implements AuthenticationFailureHandler {
/**
* AuthenticationException:异常信息
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//直接输出json格式的响应信息
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(e.getMessage());
out.flush();
out.close();
}
}
配置failureHandler
接着我们在SecurityConfig配置类中,调用failureHandler()方法来关联上面定义的SecurityAuthenticationFailureHandler类对象。
核心代码如下:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
//认证成功时的处理器
.successHandler(new SecurityAuthenticationSuccessHandler())
//认证失败时的处理器
.failureHandler(new SecurityAuthenticationFailureHandler())
.and()
.csrf()
.disable();
}
}
验证结果
配置完成后,我们再去登录,在认证失败时,就可以看到登录失败的用户信息通过 JSON 返回到前端了
退出登录时的处理方案
认证成功和认证失败后的处理方案后,退出登录后处理方案
1. logoutSuccessHandler()
负责退出登录的方法是logoutSuccessHandler(),这个方法中需要一个参数LogoutSuccessHandler;在LogoutSuccessHandler类中有一个方法 onLogoutSuccess(),该方法中的参数与登录成功时的参数一样。
2. 定义SecurityLogoutSuccessHandler类
我们先来定义一个SecurityLogoutSuccessHandler类,实现LogoutSuccessHandler接口,在这里负责输出退出登录时的JSON结果。
public class SecurityLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("注销成功");
out.flush();
out.close();
}
}
配置logoutSuccessHandler
然后我们在SecurityConfig配置类中,调用logoutSuccessHandler()方法来关联上面定义的SecurityLogoutSuccessHandler对象。
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
//认证成功时的处理器
.successHandler(new SecurityAuthenticationSuccessHandler())
//认证失败时的处理器
.failureHandler(new SecurityAuthenticationFailureHandler())
.and().logout()
//退出登录时的处理器
.logoutSuccessHandler(new SecurityLogoutSuccessHandler())
.and()
.csrf()
.disable();
}
}
验证结果
配置完成后,我们去访问/logout接口,退出登录成功,会有如下所示结果:
未认证时的处理方案
1. authenticationEntryPoint()
未认证时,同样有个专门的方法来处理,即authenticationEntryPoint()方法,这个方法中需要一个参数LoginUrlAuthenticationEntryPoint,在LoginUrlAuthenticationEntryPoint类中有一个方法 commence()。
2. 定义SecurityAuthenticationEntryPoint类
我们定义一个SecurityAuthenticationEntryPoint类,实现AuthenticationEntryPoint接口,在这里负责输出未认证时的JSON结果。
/**
* @className: com.lison.springsecurity.config.security.handler-> SecurityAuthenticationEntryPoint
* @description: 处理未登录认证时的响应信息
* @author: Lison
* @createDate: 2024-06-01
*/
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("尚未登录,请先登录");
out.flush();
out.close();
}
}
配置authenticationEntryPoint
然后我们在SecurityConfig配置类中,调用authenticationEntryPoint()方法来关联上面定义的SecurityAuthenticationEntryPoint对象。
验证结果
配置完成后,我们在未登录时,直接去访问项目中的某个接口,就会看到未登录时返回的JSON信息,如下图所示: