目录
1.token介绍
前端使用的layui,通过ajax异步提交表单来实现登录,刚好学习巩固一下ajax,后端主要学习一下token的使用。
token是什么?
百度百科:Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。
为什么使用token?
主要减轻服务器压力,用户登录生成一次token后,发送一份给客户端存放在localStorage或cookie中,请求时放在请求头携带。
服务端token存放在哪?
session:使用起来简单,分布式情况存在同步问题
redic:系统复杂性就要提高了 ,设置时间自动销毁,可以实现单点登录
不存储:jwt验证,项目小建议使用,无法实现自动销毁
token验证流程
- 客户端使用用户名跟密码请求登录;
- 通过ajax向后端发送请求;
- 服务端收到请求,去验证用户名与密码;
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端;
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里;
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Toke(不管是你自己写的)它都要通过ajax向后端发送请求,这时服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
2.前端代码
(1)首先是验证码功能,验证码功能可以看的这篇文章学习https://blog.csdn.net/qing_er_/article/details/125913325?spm=1001.2014.3001.5502ww
验证码代码
var code; //在全局定义验证码
function createCode() {
code = "";
var random = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; //随机数
for (var i = 0; i < 4; i++) { //循环操作
var index = Math.floor(Math.random() * 36); //取得随机数的索引(0~35)
code += random[index]; //根据索引取得随机数加到code上
}
$("#code").val(code) //把code值赋给验证码
}
需要注意的就是验证码刷新的地方,加载页面,点击验证码,登录失败都要刷新验证码。
(2)表单异步提交,这里之前每次点击都会刷新页面,可以通过onSubmit='return false'把提交禁掉,直接使用点击事件异步提交,在表单内数据很多的情况下,下面ajax提交的数据可以直接序列化表单数据提交,$("form").serialize(),还有之前直接把ajax换成post或get不知道为啥会报415错误。提交表单数据后,后端生成一个token字符串,具体要存入token哪些信息自己根据实际情况决定,返回的token可以存在localstorage和cookie中,这里存放在localstorage中,每次请求携带token。
<!--引入form模块-->
layui.use(['form', 'layer', 'jquery'], function () {
$ = layui.jquery;
var form = layui.form,
layer = layui.layer;
// 表单提交
$("#submit").click(function () {
can_sumbit = true;//重新点击后需要重新赋值,否则一直为false
// 表单校验
if ($("#username").val() == "") {
layer.msg("用户名不能为空");
can_sumbit = false;
} else if (can_sumbit && $("#password").val() == "") {
layer.msg("密码不能为空");
can_sumbit = false;
} else if (can_sumbit && $('#ctl00_txtcode').val() == "") {
layer.msg("请填写验证码");
can_sumbit = false;
} else if (can_sumbit && $("#ctl00_txtcode").val().toUpperCase() != $("#code").val().toString()) {//将输入验证码转大写,实际验证码的类型转为字符串类型
createCode();//刷新验证码
layer.msg("验证码不正确");
can_sumbit = false;
}
//异步提交表单数据,分两个请求,第一个获取后端传入的token并存入本地,第二请求头携带token登录,第二没有直接登录感觉也是可以的
if (can_sumbit) {
var data = {
username: $("#username").val(),
password: $("#password").val()
};
$.ajax({
url: "/login",
data: JSON.stringify(data),
contentType: 'application/json',
type: "post",
success: function (result) {
if (result.ok == false) {
createCode();//刷新验证码
layer.msg(result.message);
} else if (result.ok == true) {
localStorage.setItem("token", result.data.token);
$.ajax({
url: "/getInfo",
type: "get",
headers: {"token": localStorage.getItem("token")},
success: function (result) {
if (result.ok == true){
location.href = "../index.html"
}
}
})
}
}
})
} else {
return false;
}
});
});
(3)用户名框失去焦点进行异步验证用户名存在性
//异步请求是否存在该用户名
$("#username").blur(function () {
console.log($("#username").val())
$.ajax({
url: "/loginVerify",
data: {"username": $("#username").val()},
type: "get",
success: function (result) {
$("#zz1").text(result.message);
}
})
})
});
3.后端代码
后端使用springboot+mybatis-plus +mysql
主要来学习token的使用,使用jwt生成token,配置token的生效时间等,后续会整合把token字符串和过期时间存入redic
(1)pojo,使用了lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String username;
private String password;
}
(2)mybatis-plus配置类config,主要扫描作用,分页插件用不上
@Configuration
@MapperScan("com.xj.mapper")
public class MybatisConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// paginationInterceptor.setLimit(你的最大单页限制数量,默认 500 条,小于 0 如 -1 不受限制);
return paginationInterceptor;
}
}
(3)mapper的UserMapper
public interface UserMapper extends BaseMapper<User> {
}
(4)service的UserService接口和serviceImpl的UserServiceImpl
public interface UserService extends IService<User> {
User login(User user);
User getUserByUsername(String username);
User getUserById(Long userId);
}
@Service("userService")
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User login(User user) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", user.getUsername());
wrapper.eq("password", user.getPassword());
System.out.println(baseMapper.selectOne(wrapper).getUsername());
return baseMapper.selectOne(wrapper);
}
@Override
public User getUserByUsername(String username) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
return baseMapper.selectOne(wrapper);
}
@Override
public User getUserById(Long userId) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id",userId);
return baseMapper.selectOne(wrapper);
}
}
(5)controller的UserService
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody User user, HttpServletRequest request){
try {
User loginUser = userService.login(user);
Map<String, Object> map = new HashMap<>();
if (loginUser != null) {
String token = JwtHelper.createToken(loginUser.getId().longValue());
map.put("token", token);
return Result.ok(map);
} else {
throw new RuntimeException("用户名或密码错误");
}
} catch (RuntimeException e) {
return Result.fail().message("用户名或密码错误");
}
}
@GetMapping("/getInfo")
public Result getInfo(@RequestHeader String token){
boolean expiration = JwtHelper.isExpiration(token);//验证token是否过期
if (expiration) {
Result.build(null, ResultCodeEnum.CODE_ERROR);
}
Long userId = JwtHelper.getUserId(token);
Map<String, Object> map = new HashMap<>();
User user = userService.getUserById(userId);
map.put("user", user);
return Result.ok(user);
}
@GetMapping("/loginVerify")
public Result test(String username){
User user = userService.getUserByUsername(username);
if(user != null){
return Result.ok().message("√");
}else {
return Result.fail().message("用户不存在");
}
}
}
(5)工具类until,Result用来封装返回数据状态码等,JwtHelper生成token和解析token的工具类
Result工具类
@Data
@ApiModel(value = "全局统一返回结果")
public class Result<T> {
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private T data;
public Result() {
}
// 返回数据
protected static <T> Result<T> build(T data) {
Result<T> result = new Result<T>();
if (data != null)
result.setData(data);
return result;
}
public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
Result<T> result = build(body);
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
public static <T> Result<T> ok() {
return Result.ok(null);
}
/**
* 操作成功
*
* @param data
* @param <T>
* @return
*/
public static <T> Result<T> ok(T data) {
Result<T> result = build(data);
return build(data, ResultCodeEnum.SUCCESS);
}
public static <T> Result<T> fail() {
return Result.fail(null);
}
/**
* 操作失败
*
* @param data
* @param <T>
* @return
*/
public static <T> Result<T> fail(T data) {
Result<T> result = build(data);
return build(data, ResultCodeEnum.FAIL);
}
public Result<T> message(String msg) {
this.setMessage(msg);
return this;
}
public Result<T> code(Integer code) {
this.setCode(code);
return this;
}
public boolean isOk() {
if (this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
return true;
}
return false;
}
}
jwthelper工具类
/**
* token口令生成工具 JwtHelper
*/
public class JwtHelper {
private static long tokenExpiration = 24*60*60*1000;
private static String tokenSignKey = "123456";
//生成token字符串
public static String createToken(Long userId) {
String token = Jwts.builder()
.setSubject("YYGH-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("userId", userId)
// .claim("userName", userName)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
//从token字符串获取userid
public static Long getUserId(String token) {
if(StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Integer userId = (Integer)claims.get("userId");
return userId.longValue();
}
//从token字符串获取userType
public static Integer getUserType(String token) {
if(StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws
= Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (Integer)(claims.get("userType"));
}
//从token字符串获取userName
public static String getUserName(String token) {
if(StringUtils.isEmpty(token)) return "";
Jws<Claims> claimsJws
= Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String)claims.get("userName");
}
//判断token是否有效
public static boolean isExpiration(String token){
try {
boolean isExpire = Jwts.parser()
.setSigningKey(tokenSignKey)
.parseClaimsJws(token)
.getBody()
.getExpiration().before(new Date());
//没有过期,有效,返回false
return isExpire;
}catch(Exception e) {
//过期出现异常,返回true
return true;
}
}
/**
* 刷新Token
* @param token
* @return
*/
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = Jwts.parser()
.setSigningKey(tokenSignKey)
.parseClaimsJws(token)
.getBody();
refreshedToken = JwtHelper.createToken(getUserId(token));
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
}
(6)application.yml配置
spring:
#解决SpringBoot2.6.0与swagger冲突问题【原因是在springboot2.6.0中将SpringMVC 默认路径匹配策略从AntPathMatcher 更改为PathPatternParser,导致出错,解决办法是切换回原先的AntPathMatcher】
mvc:
pathmatch:
matching-strategy: ant_path_matcher
#配置数据源
datasource:
#配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
#配置数据库连接属性
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/study_xj?characterEncoding=utf-8&serverTimezone=GMT%2B8&userSSL=false
username: root
password: 334460977
#url: jdbc:mysql://r2czkq1vewxat78mnyg60oisurj5h4dp.mysql.qingcloud.link:3306/ssg_zhxy_db?characterEncoding=utf-8&serverTimezone=GMT%2B8&userSSL=false
#username: shangguigu
#password: shangguigu@QY123
#mybatis-plus内置连接池
hikari:
connection-test-query: SELECT 1
connection-timeout: 60000
idle-timeout: 500000
max-lifetime: 540000
maximum-pool-size: 12
minimum-idle: 10
pool-name: GuliHikariPool
thymeleaf:
#模板的模式,支持 HTML, XML TEXT JAVASCRIPT
mode: HTML5
#编码 可不用配置
encoding: UTF-8
#开发配置为false,避免修改模板还要重启服务器
cache: false
#配置模板路径,默认是templates,可以不用配置
prefix: classpath:/static/
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
servlet:
#设置文件上传上限
multipart:
max-file-size: 10MB
max-request-size: 100MB
mybatis-plus:
configuration:
#添加日志支持
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:/mapper/**/*.xml
全部代码地址:登录学习: 学习登录功能

被折叠的 条评论
为什么被折叠?



