SpringSecurity(4)学习内容

一、创建vue项目

# 创建Vue项目
npm create vue@latest

# 进入项目目录
cd security-frontend

# 进入项目目录
cd security-frontend

# 安装依赖
npm install axios

# 安装其它依赖
npm i

配置main.js文件

配置路由router/index.js

登录组件Login.Vue

<script setup>
import { ref } from 'vue'
import {login} from "@/api/request.js"

// 表单数据
const username = ref('')
const password = ref('')
const errorMessage = ref('')
const isLoading = ref(false)

// 表单验证
const validateForm = () => {
  if (!username.value.trim()) {
    errorMessage.value = '请输入用户名'
    return false
  }
  if (!password.value.trim()) {
    errorMessage.value = '请输入密码'
    return false
  }
  errorMessage.value = ''
  return true
}

// 登录处理函数
const handleLogin = async () => {
  if (!validateForm()) return
  
  isLoading.value = true
  
  // 模拟登录请求
  try {
    // 在实际应用中,这里应该是一个API调用
   
  } catch (error) {
    errorMessage.value = '登录失败,请稍后再试'
  } finally {
    isLoading.value = false
  }
}
</script>

<template>
  <div class="login-container">
    <div class="login-form">
      <h2>用户登录</h2>
      
      <div v-if="errorMessage" class="error-message">
        {{ errorMessage }}
      </div>
      
      <div class="form-group">
        <label for="username">用户名</label>
        <input
          type="text"
          id="username"
          v-model="username"
          placeholder="请输入用户名"
          @keyup.enter="handleLogin"
        />
      </div>
      
      <div class="form-group">
        <label for="password">密码</label>
        <input
          type="password"
          id="password"
          v-model="password"
          placeholder="请输入密码"
          @keyup.enter="handleLogin"
        />
      </div>
      
      <button 
        class="login-button"
        :disabled="isLoading"
        @click="handleLogin"
      >
        {{ isLoading ? '登录中...' : '登录' }}
      </button>
    </div>
  </div>
</template>

<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  width: 100%;
}

.login-form {
  background-color: rgba(255, 255, 255, 0.1);
  padding: 2rem;
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
  backdrop-filter: blur(10px);
  width: 100%;
  max-width: 400px;
}

@media (prefers-color-scheme: light) {
  .login-form {
    background-color: rgba(255, 255, 255, 0.95);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  }
}

.login-form h2 {
  text-align: center;
  margin-bottom: 2rem;
  color: inherit;
}

.form-group {
  margin-bottom: 1.5rem;
}

.form-group label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
  color: inherit;
}

.form-group input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 8px;
  background-color: rgba(255, 255, 255, 0.1);
  color: inherit;
  font-size: 1rem;
  box-sizing: border-box;
  transition: border-color 0.25s, background-color 0.25s;
}

@media (prefers-color-scheme: light) {
  .form-group input {
    border: 1px solid #ddd;
    background-color: #fff;
  }
}

.form-group input:focus {
  outline: none;
  border-color: #646cff;
  background-color: rgba(255, 255, 255, 0.15);
}

@media (prefers-color-scheme: light) {
  .form-group input:focus {
    background-color: #fff;
  }
}

.login-button {
  width: 100%;
  padding: 0.75rem;
  border: none;
  border-radius: 8px;
  background-color: #646cff;
  color: white;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.25s, transform 0.1s;
}

.login-button:hover:not(:disabled) {
  background-color: #535bf2;
}

.login-button:active:not(:disabled) {
  transform: scale(0.98);
}

.login-button:disabled {
  background-color: #666;
  cursor: not-allowed;
}

.error-message {
  background-color: rgba(255, 0, 0, 0.1);
  color: #ff4d4f;
  padding: 0.75rem;
  border-radius: 8px;
  margin-bottom: 1.5rem;
  border: 1px solid rgba(255, 0, 0, 0.3);
}
</style>

样式文件style/style.css

根组件

配置Axios实例

实现前端登陆功能

api/request.js

在资料中导入vue项目,在登录页上不需要使用隐藏域(csrf)的这个参数了,因为它的值是通过security的session中获取的,既然是前后端分离了,显然获取不到session中的数据了。但是这样我们就不能预防csrf攻击了,但是不要紧,我们后续有解决方案(jwt)。

二、Security配置类配置

在配置类中我们首先将预防csrf的攻击禁用掉,同时添加上解决跨域问题的解决方案

跨域的解决方案中需要一个Bean,这个bean是系统提供的,所以需要我们自己来注册spring容器中

而这个bean是一个接口,那么很显然需要它的实现类来注册bean

我们采用标注的bean来实现,而该bean需要调用registerCorsConfiguration来定义跨域的规则

有无参构造方法,我们就通过这个方式创建这个bean,然后设置跨域规则就好了。

三、成功和失败的配置

当我们登录成功或者失败后,是不是应该有一个处理方案,因为这个时候,我们使用的前后端分离,就不能去使用我们之前的thymeleaf语法的页面了。

后端只需要返回json数据,然前端去进行页面之间的跳转就可以了。

那么接下来处理:

然后定义对应的成功和失败的处理器就好了, 返回给前端数据的时候,是不是应该有一个统一的格式呢?

成功的处理器:

失败的处理器:

然后通过依赖注入的方式注入进来就可以去使用了:

四、前端项目处理

路由配置/router/index.js

登录成功后跳转到首页

当登录成功后跳转到首页Index.vue中就好了。

但是有一个问题,我们登录成功后,在前端发送其它请求去后端,发现竟然不能访问,为啥呢,因为是前后端分离,后端服务器不保存登录的凭证了,也就是说哪怕登录成功也不能访问获取后端的数据了。如何解决呢?

没有session、前端cookie中也不会存储sessionid;那么这样的话,用户状态怎么保持呢?

使用JWT即可解决

五、JWT

解决方案:

5.1 什么是JWT

JSON Web Token (JWT)是一个开放标准(RFC 7519) ,它定义了一种紧凑和自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。此信息可以进行验证和信任,因为它是经过数字签名的。JWT 可以使用机密(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。

虽然可以对 JWT 进行加密,以便在各方之间提供保密性,但是我们将关注已签名的Token。签名Token可以验证其中包含的声明的完整性,而加密Token可以向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,该签名还证明只有持有私钥的一方才是对其进行签名的一方( 签名技术是保证传输的信息不可抵赖,并不能保证信息传输的安全 )。

官网:https://jwt.io/introduction

5.2 JWT能做什么

5.2.1 授权

这是使用jwt最常见方案。一旦用户登录,后续每个请求将包含jwt,从而允许用户访问该令牌允许的路由,服务和资源,单点登录就是其中一个使用场景,因为开销很小且可以在不同的域中轻松使用。

5.2.2 信息交换

jwt是在各方之间安全传输信息的好方法,因为可以对jwt进行签名,保证发送端和发送内容的没有被篡改

5.3 为什么是JWT

5.3.1 基于传统的session认证

认证方式

传统使用session就是前端cookie保存一个sessionID,下次发送请求自动携带

认证流程

暴露问题

  1. session保存在内存中,用户越多,内存负载越大
  2. 分布式应用的话限制了负载均衡能力
  3. cookie被截获容易受到跨站请求伪造攻击
  4. 在前后端分离系统重更加痛苦
5.3.2 基于JWT认证

认证流程:

  1. 前端发送请求将用户名和密码发送到后端。
  2. 后端核对用户名和密码成功后,将用户的ID等其他信息作为jwt的payload,将其与头部分别进行base64编码拼接后签名,形成一个jwt。
  3. 后端将jwt字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或者sessionStorage上,退出登录时,前端删除保存的jwt即可
  4. 每次发送请求时将jwt放入Http Header中的Authorization中
  5. 后端检查是否存在,如存在验证jwt的有效性。例如检查签名是否正确,token是否过期,token的接收方是不是自己
  6. 验证后,后端使用jwt包含的用户信息进行其他逻辑操作,返回相应结果

5.4 JWT结构

由三部分组成,组成部分以.隔开,结构类似 xxxxx.yyyyy.zzzzz,每部分使用 Base64Url 进行编码。

  1. Header 标头(一般是固定的)
  2. Payload 有效载荷(一般理解为传参的参数,官方给我们一些固定的key值,不一定要使用,可以自己定义)
  3. Signature 签名 (防伪标志)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

这种就是jwt的结构组成。

我们可以通过https://jwt.io/#debugger-io 或者 https://jwt.p2hp.com/ 中解析出三个部分的信息

5.4.1 Header 标头

报头通常有两部分组成:

Token的类型即JWT,所使用的签名算法(如 HMAC SHA256或 RSA)。 如上图所示:最终这个json由base64进行加密(该加密可以对称解密),构成JWT的第一部分。

5.4.2 Payload 有效载荷

有效载荷,包含声明,其实就是声明实体的数据,一般都是用户的数据。

三种声明类型为:registered claims,public claims,private claims

{
  "sub": "1234567890",// 注册声明
  "name": "John Doe",// 公共声明
  "admin": true // 私有声明
}

注册声明

这些是一组预定义的声明,不是强制性的,而是推荐的,以提供一组有用的,可互操作的声明

比如:iss:jwt签发者 sub:jwt所面向的用户 aud:接收jwt的一方 exp:jwt的过期时间 iat:jwt的签发时间

注意:声明名称只有三个字符,因为jwt意味着紧凑的

public claims(公共的声明)

使用JWT的人开一随意定义这些声明(可以自己声明一些有效信息,如用户id,nam等,但是不要设置一些敏感信息,如密码)。但是为了避免冲突,应该在JWT注册表中定义或者将他们定义为包含抗冲突名称空间的URI.

private claims(私人声明)

这些是创建用于在同一使用他们的各方之间共享信息的习惯声明(是提供者和消费者所共同定义的声明)

注意:对于已经签名的Token,这些信息虽然受到保护,不会被篡改,但是任何人都可以阅读。除非加密,否则不要将机密信息放在JWT的有效负载或者头元素中。

5.4.1 Signature 签名

要创建Signature,必须获取编码的标头(header),编码的有效载荷(payload)、secret(自定义,越复杂越好)、标头中指定的算法,并对其进行签名。

例如:如果想使用HMACSHA256算法,签名将按以下方式创建:

HMACSHA256(
    base64UrlEncode(header)+"."+base64UrlEncode(payload),secret
)

上面的Json将会通过HMACSA256算法结合secret进行加盐签名(私钥加密),其中header和payload将通过base64UrlEncode()方法进行base64加密然后通过字符串拼接"."生成新字符串,最终生成JWT的第三部分.

注意:secret是保存在服务器端的,jwt的签发生成也是正在服务器端的,secret就是用来进行jwt的签发和验证,所以,他就是服务器端的私钥,在任何场景都不要泄露出去,一旦客户端知道这个secret,那就以为这客户端可以自己签发jwt了。

JWT的生成与解析

JWT输出是三个由点分割的Base64-URL字符串,这些字符串在HTML与HTTP环境传递。

5.5 JWT基本使用

采用糊涂工具包:https://www.hutool.cn/ 官网

5.5.1 添加依赖

5.5.2 测试

5.5.1 项目中的应用

在验证成功后去后端服务器去生成token,并返回给前端

在yml中自定义属性:

5.5.1 如何在本地保存

sessionStorage:会话存储,只能在当前页生效(推荐)

localStorage:本地存储,只要是同一个浏览器

5.5.1 如何保持认证状态

因为jwt是无状态的(服务器不知道),这里我们采用每次发送请求带请求头加上生成的jwt,进入服务器后存入到redis中,再次请求的时候先验证jwt是否一致,然后去redis中取出存储的jwt匹配

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值