在现代Web应用中,用户认证是一个不可或缺的部分。除了传统的会话/cookie认证方式,Token认证提供了一种无状态、可扩展的认证机制。在本文中,我将向您展示如何在一个Spring Boot应用中实现一个简易的Token认证系统
什么是Token认证?
Token认证是一种安全机制,通常使用JSON Web Tokens (JWT) 来实现。服务器对用户凭证进行验证后,生成一个包含用户信息和有效期限的Token,随后客户端需要在每个请求中携带这个Token以验证用户身份。
为什么选择Token认证?
- 无状态:服务器不需要存储会话信息,易于扩展。
- 跨域:Token可以轻松地在不同域名的服务间传递。
- 安全性:JWTs可以被签名和验证,确保数据未被篡改。
实现步骤
-
添加依赖:在Spring Boot项目的
pom.xml
中添加Spring Security和JWT的依赖项。 -
配置Spring Security:创建一个配置类来配置Spring Security,禁用CSRF保护,并设置无状态会话。
-
创建Token工具类:编写一个工具类来生成和解析JWT。
-
实现登录逻辑:创建一个登录控制器,接收用户名和密码,验证后返回Token。
-
手动验证Token:在需要保护的API中手动验证Token。
示例代码
以下是一些关键代码片段,展示了如何实现上述步骤。
后端
1. 添加依赖
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version> <!-- 请检查最新版本 -->
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
2. Token工具类
package com.example.javaee.config;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtil {
private static final String SECRET_KEY = "your_secret_key";
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
// 10 hours
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
3. 实现登录逻辑
import com.example.javaee.entity.User;
import com.example.javaee.entity.Response;
import com.example.javaee.mapper.UserMapper;
// 其他导入...
public class UserService {
private UserMapper userMapper; // 假设已注入UserMapper
public Response login(String account, String password) {
User existingUser = userMapper.findByUsername(account);
if (existingUser != null && existingUser.checkPassword(password)) {
String token = JwtUtil.generateToken(account); // 生成Token
String permission = existingUser.getPermission();
return new Response("登录成功", token, permission);
}
return new Response("登录失败", null, null);
}
}
前端
1.router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from '../components/Home.vue';
import Login from '../components/Login.vue';
// 导入其他页面组件,例如 activitypost、superadmin 等
Vue.use(Router);
// 定义路由配置
const routes = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: Home,
meta: { requiresAuth: false },
},
{
path: '/login',
name: 'Login',
component: Login,
meta: { requiresAuth: false },
},
// 示例一个受保护的路由
{
path: '/activitypost',
name: 'activitypost',
component: () => import('../components/activitypost.vue'),
meta: { requiresAuth: true },
},
// ...其他受保护的路由配置
];
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
router.beforeEach((to, from, next) => {
const hasToken = localStorage.getItem(TOKEN_KEY);
if (to.matched.some(record => record.meta.requiresAuth)) {
if (hasToken) {
next(); // 有Token,允许进入受保护的路由
} else {
next({ path: '/login', query: { redirect: to.fullPath } }); // 无Token,重定向到登录页,并附带redirect查询参数
}
} else {
next(); // 公共路由,直接放行
}
});
export default router;
2.Login.vue
<template>
<div id="login">
<div class="me-login-box me-login-box-radius">
<h1>登录界面</h1>
<el-form ref="userForm" :model="userForm" :rules="rules">
<el-form-item prop="account">
<div class="my-form1">
<img src="../assets/img/user.png" id="user-img">
<input class="my-input" placeholder="用户名" v-model="userForm.account" ref="account" />
</div>
</el-form-item>
<el-form-item prop="password">
<div class="my-form1">
<img src="../assets/img/lock.png" id="password-img">
<input class="my-input" placeholder="密码" type="password" v-model="userForm.password" ref="password"
@keyup="onHCapitalize($event)" />
<img src="../assets/img/eyesclosed.png" id="eyes-img" ref="eyes" @click="show()">
</div>
</el-form-item>
<el-form-item size="small" class="me-login-button">
<el-button type="primary" @click.native.prevent="login('userForm')">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { login } from '../api/api'
import { TOKEN_KEY } from '../router/index'
export default {
name: 'Login',
data() {
return {
userForm: {
account: '',
password: ''
},
rules: {
account: [
{ required: true, message: '用户名不能为空' }
],
password: [
{ required: true, message: '密码不能为空' },
{ min: 6, message: 'password must be at least 6 characters' }
]
},
flag: 'false',
bigChar: 'false'
}
},
methods: {
login(formName) {
let that = this;
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.userForm.account.trim() !== "" && this.userForm.password.trim() !== "") {
let params = new URLSearchParams();
params.append('account', this.userForm.account);
params.append('password', this.userForm.password);
login(params)
.then(function (response) {
if (response.data.message === '登录成功') {
const token = response.data.token;
// 存储 Token 到 localStorage
localStorage.setItem('authToken', token);
// 根据返回的权限进行路由跳转
switch (response.data.permission) {
case '超级管理员':
that.$router.push({ name: 'superadmin' });
break;
case '普通管理员':
that.$router.push({ name: 'admin' });
break;
case '注册用户':
that.$router.push({ name: 'user' });
break;
default:
console.error('未知权限');
}
} else if (responseData.message === '登录失败') {
console.error('登录失败,用户名不存在或者密码错误');
}
})
.catch(function (error) {
console.error(error);
});
} else {
// 验证账号密码是否为空,并设置输入框边框颜色
if (this.userForm.account.trim() === "") {
this.$refs.account.style.borderColor = "red";
} else {
this.$refs.account.style.borderColor = "#797979";
}
if (this.userForm.password.trim() === "") {
this.$refs.password.style.borderColor = "red";
} else {
this.$refs.password.style.borderColor = "#797979";
}
return false;
}
}
});
},
show() {
if (this.flag) {
this.$refs.eyes.src = "../../static/img/eyes.png";
this.$refs.password.setAttribute("type", "text");
} else {
this.$refs.eyes.src = "../../static/img/eyesclosed.png";
this.$refs.password.setAttribute("type", "password");
}
this.flag = !this.flag;
},
}
}
</script>
<style>
/* 您的样式 */
</style>
测试和调试
在实现Token认证后,进行充分的测试是非常重要的。确保在各种情况下Token都能正确生成、验证和失效。
1.当登录成功后,查看控制台是否成功打印Token,并进行路由跳转。
2.删除Token,看看是否还能直接访问页面,如果不能,则说明已经成功。删除Token的方法如图:F12打开控制台后进行如下操作
结论
Token认证是一种强大且灵活的用户认证方式。通过本指南,本人进行了Token最简易最初步的实现,后续继续提高!