前言
在软件开发过程中,确保系统的安全性是至关重要的一环。它不仅关乎保护用户数据的完整性和隐私性,也是维护系统稳定运行的基石。我认为,从宏观角度审视,软件开发的安全性保障主要可分为两大类:功能性的安全性保障和系统性的安全性校验。
功能性的安全性保障专注于应用程序层面,它着眼于那些直接影响用户数据和交互过程安全的特性。这些特性是构建用户信任和保障数据安全的关键。
而系统性的安全性校验则放眼于更为广阔的视角,它涵盖了整个系统架构和网络层面,确保从服务器到网络的每一环节都具备足够的防御能力,以抵御各种潜在的攻击。
在前文功能性的安全性保障:实现强制登录和密码加密功能,我们已经讨论了功能性的安全性保障中的两个核心议题:身份验证和密码加密。所以在本文中,我们将继续从功能性的安全性保障这个话题展开,通过对概念的细致解读、实施策略的全面分析,以及实际代码实现的展示,深入讨论其中的另外一个核心议题:TOKEN鉴权校验。
1. Token的定义
当我们准备使用Token进行鉴权校验时,首先得先了解Token是什么,我们才能扩展下去。这里简单讲下,所谓Token,是一种数据对象,通常由一组字符组成,主要用户身份验证和授权信息。通常所说的Token可以是多种形式,例如Session ID、Bearer Token、Access Token等。它们的安全性取决于如何生成、存储和传输。
2. JWT(Json Web Token):用户的身份和权限验证
在实际项目中,常用JWT作为Token来验证用户的身份和权限,确保请求的合法性。这种机制允许系统在不直接存储用户敏感信息的情况下,对用户进行身份验证和授权。
JSON Web Tokens (JWT) 是一种广泛采用的、用于在不同实体间安全传输信息的开放标准(RFC
7519)。它定义了一种紧凑且自包含的方式,允许通过JSON对象在通信双方之间传递安全性声明。这种机制不仅确保了信息的完整性和可验证性,还通过使用数字签名技术,保障了其安全性。在JWT的框架下,令牌(Token)本身封装了一系列声明(Claims),这些声明可以被应用程序用来控制对资源的访问权限。这些声明包括但不限于用户的身份信息、权限级别以及令牌的有效期等。JWT的令牌通常采用JSON Web Signature (JWS) 进行签名,确保了其内容的不可篡改性和验证的可靠性。
2.1 JWT的构成
一个JWT由三部分组成,用点 .
分隔:
- Header(头部):通常包含令牌的类型(即JWT)和所使用的签名算法(如HS256或RS256)。
- Payload(负载):包含所谓的Claims(声明),它们是关于实体(通常是用户)和其他数据的声明。例如,用户ID、用户名、角色信息等。
- Signature(签名):用于验证消息在传输过程中未被篡改,并且,对于使用私钥签名的令牌,还可以验证发送者的身份。
2.2 签名机制
特别地,JWT的签名过程常采用RSA公钥-私钥对机制。这种方式不仅提供了强大的安全保障,还使得JWT在各种应用场景中,如单点登录(SSO)、移动应用认证等,都能发挥其独特的优势。通过这种方式,JWT成为了一种理想的解决方案,用于发布和验证接入令牌(Access Tokens),从而在不同的服务和应用程序之间实现安全、高效的用户认证和授权。
2.3 过期时间
JWT通常包含一个过期时间(exp),这有助于限制Token的生命周期,增加安全性。其他类型的Token可能也需要设置过期时间,但它们的管理方式可能不同。
3. Spring Security:基于令牌的认证机制
另外,还有个话题我们不得不关注,那就是Spring Security。Spring Security是一个功能强大且高度可自定义的认证和访问控制框架,是Spring项目的一部分。它为JVM(Java虚拟机)语言提供了一套全面的安全服务,主要用于保护基于Spring的应用程序。
简单来说,当用户尝试访问一个需要认证的网站或应用时,Spring Security会要求提供Token。只有提供了正确的Token,Spring Security才会让请求通过。
3.1 核心功能
- 认证(Authentication):
确认用户身份,通常通过用户名和密码,但也支持多种其他认证方法(如OAuth2、JWT、LDAP等)。
提供内置的表单登录和HTTP基本认证。 - 授权(Authorization,也称为访问控制):
决定已认证的用户是否有权访问某些资源或执行某些操作。
支持基于角色和权限的访问控制。 - 保护Web应用程序:
通过声明式的URL访问配置,定义哪些URL需要认证,哪些URL可以匿名访问。
支持防止常见的Web攻击,如CSRF(跨站请求伪造)、XSS(跨站脚本)等。 - 方法级安全:
使用注解(如 @Secured, @PreAuthorize, @PostAuthorize)对方法调用进行访问控制。
整合其他Spring项目: - 无缝集成Spring Boot、Spring MVC、Spring Data等项目。
4. 代码实现:登录生成TOKEN,并使用TOKEN进行权限校验
4.1 前端代码实现
首先,我们基于之前开发的前端代码进行扩展,在主页创建一个头部导航栏、侧边栏。
<template>
<el-container style="height: 100vh;">
<el-header style="padding: 0; background-color:#545c64; height: 60px;">
<TopNavbar/>
</el-header>
<el-container style="flex: 1; display: flex;">
<SideNavbar />
<el-main style="flex: 1; display: flex; justify-content: center; align-items: center;">
<router-view class="my"></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
import TopNavbar from './TopNavbar.vue';
import SideNavbar from "@/views/SideNavbar.vue";
export default {
name: 'HomePage',
components: {
SideNavbar,
TopNavbar,
}
};
</script>
<style>
.el-main {
padding: 0;
}
</style>
头部导航栏主要定义两个按钮,分别是登录按钮和登出按钮,主要为了实现对TOKEN的创建和销毁。
<template>
<div class="navbar">
<div class="navbar-links">
<div class="nav-item" v-if="true">
<el-button @click="goToLogin">登录</el-button>
</div>
<div class="nav-item" v-if="true">
<el-button @click="loginOut">退出</el-button>
</div>
</div>
</div>
</template>
<script >
import {
ref } from 'vue';
export default {
methods: {
goToLogin() {
this.$router.push({
name: 'Login'});
},
loginOut() {
fetch('http://localhost:8081/user/logout', {
method: 'POST',
headers: {
Authorization: localStorage.getItem('token')
}
}).then(response => {
if (response.ok) {
localStorage.removeItem('token');
this.$router.push('/login');
}
}).catch(error => {
console.log(error);
});
}
}
}
</script>
<style scoped>
.navbar {
display: flex;
justify-content: flex-end; /* 确保内容靠右对齐 */
align-items: center; /* 垂直居中对齐 */
width: 100%;
}
.navbar-links {
display: flex;
justify-content: flex-end; /* 确保按钮靠右对齐 */
}
.nav-item {
margin-right: 15px; /* 可选:增加按钮之间的间距 */
margin-top: 10px;
}
</style>
左侧导航菜单包含两个主项:“首页” 和 “我的”,其中"我的"菜单项下包含个人信息。实现用户界面与路由的集成。
<template>
<el-aside style="width: 200px; background-color: #545c64;">
<el-menu active-text-color="#ffd04b" background-color="#545c64" class="el-menu-vertical-demo"
text-color="#fff" @select="handleSelect" :default-active="activeIndex">
<el-sub-menu index="1">
<template #title>
<span>首页</span>
</template>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>
<span>我的</span>
</template>
<<