关于本文
其实用户登录拦截的这块不想这么早写,加个登录后面好多东西就要考虑登录状态了,我其实想把这个系列写成非必要关系,解耦性比较强
的系列。但是,写完redis,总是感觉登录是对它最简单的实践,那就加上吧,反正后面很多文章也会用到,但大多文章我仍会不考虑登录状态。
这里只是讲明白登录机制,如何实现。实际使用中会考虑很多别的,例如用户权限,登录机制限制等等~这里就先不做过多的叙述。
这里只讲技术和实现,不讲任何业务场景哈,牵扯到场景的问题就会复杂N倍,而且通用性往往不尽人意~
本文依赖于redis和mybatis plus,这些都是最基础的模块,所以都放在最前面写了,大家可以线过一下相关的文章。
[SpringBoot集成Redis]()
[SpringBoot集成Mybatis Plus]()
本文是基于jwt+redis来实现。接下来我们一起看看吧
什么是JWT
什么是JWT,JWT(全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。 该信息可以被验证和信任,因为它是数字签名的。
上面说法比较文绉绉,简单点说就是一种认证机制,让后台知道该请求是来自于受信的客户端。
JWT的优点
- json格式的通用性,所以JWT可以跨语言支持,比如Java、JavaScript、PHP、Node等等。
- 可以利用Payload存储一些非敏感的信息。
- 便于传输,JWT结构简单,字节占用小。
- 不需要在服务端保存会话信息,易于应用的扩展。
JWT的缺点
- 安全性没法保证,所以jwt里不能存储敏感数据。因为jwt的payload并没有加密,只是用Base64编码而已。
- 无法中途废弃。因为一旦签发了一个jwt,在到期之前始终都是有效的,如果用户信息发生更新了,只能等旧的jwt过期后重新签发新的jwt。
- 续签问题。当签发的jwt保存在客户端,客户端一直在操作页面,按道理应该一直为客户端续长有效时间,否则当jwt有效期到了就会导致用户需要重新登录。
补偿JWT的缺点
- 针对JWT的缺点,我们在使用的过程中,只储存常用的无敏感数据,比如用户ID,用户角色等。
- 中途废弃和续签问题,通过和redis配合使用,将token返回时,同步保存redis,通过控制token在redis的有效期来进行控制。
- 还可以通过统计redis有效数据,对在线用户进行统计或强制下线等操作。
用户登录流程
以用户登录功能为例,程序流程如下:
用户登录
token认证访问
注:系统中采用JWT对用户登录授权验证。
基于Token的身份验证流程
使用基于Token的身份验证,在服务端不需要存储用户的登录记录。大概的流程是这样的:
1、客户端使用用户名或密码请求登录;
2、服务端收到请求,去验证用户名与密码;
3、验证成功后,服务端会使用JWT签发一个Token,保存到Redis中,同时再把这个Token发送给客户端;
4、客户端收到Token以后可以把它存储起来,比如放在Cookie里或者Local Storage里;
5、客户端每次向服务端请求资源的时候需要在请求Header里面带着服务端签发的Token;
6、服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据。验证失败,返回失败原因。
功能实现
自动生成的User.java
、UserMapper.java
、UserMapper.xml
…就不贴代码了,没有业务代码,且占的篇幅过大。在[SpringBoot集成Mybatis Plus]()文章中创建过就可以忽略了哈~
代码生成见[SpringBoot集成Mybatis Plus]()一文。
涉及到的表sql
在[SpringBoot集成Mybatis Plus]()文章中创建过的就可以忽略了
CREATE TABLE `usc_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`account` varchar(30) DEFAULT NULL COMMENT '用户账号',
`user_name` varchar(30) DEFAULT NULL COMMENT '用户姓名',
`nick_name` varchar(30) DEFAULT NULL COMMENT '用户昵称',
`user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户,01小程序用户)',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`phone` varchar(11) DEFAULT '' COMMENT '手机号码',
`sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
`salt` varchar(32) DEFAULT NULL COMMENT '用户加密盐值',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`create_id` bigint(20) DEFAULT NULL COMMENT '创建人id',
`create_name` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_id` bigint(20) DEFAULT NULL COMMENT '更新人id',
`update_name` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`delete_flag` tinyint(1) DEFAULT '0' COMMENT '删除标志',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户中心-用户信息表';
引入依赖
首先我们在pom文件中引入依赖
com.auth0
java-jwt
3.18.3
通用类
先把我们的通用类创建一下,一些常量,我们统一放在一个配置中,方便日后维护。
- 在config.bean创建通用类
GlobalConfig.java
package com.maple.demo.config.bean;
/**
* @author 笑小枫
* @date 2022/7/20
*/
public class GlobalConfig {
private GlobalConfig() {
}
/**
* 用户储存在redis中的过期时间
*/
public static final long EXPIRE_TIME = 60 * 60 * 12L;
/**
* 生成token的私钥
*/
public static final String SECRET = "maple123";
/**
* 前端传递token的header名称
*/
public static final String TOKEN_NAME = "Authorization";
/**
* 用户登录token保存在redis的key值
*
* @param account 用户登录帐号
* @return token保存在redis的key
*/
public static String getRedisUserKey(String account) {
return "MAPLE_ADMIN:" + account;
}
}
- config.bean创建
TokenBean.java
保存的jwt的信息
package com.maple.demo.config.bean;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
/**
* @author 笑小枫
* @date 2022/7/20
*/
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
public class TokenBean {
/**
* 用户ID
*/
private Long userId;
/**
* 用户账号
*/
private String account;
/**
* 用户类型
*/
private String userType;
}
JWT工具类
在我们的util包下创建JwtUtil.java
工具类👇
package com.maple.demo.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.bean.GlobalConfig;
import com.maple.demo.config.bean.TokenBean;
import com.maple.demo.config.exception.MapleCheckException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* Jwt常用操作
*
* @author 笑小枫
* @date 2022/7/20
*/
public class JwtUtil {
private static final String ACCOUNT = "account";
private static final String USER_ID = "userId";
private static final String USER_TYPE = "userType";
/**
* 校验token是否正确
*
* @param token 密钥
* @return 是否正确
*/
public static boolean verify(String token, String account) {
try {
Algorithm algorithm = Algorithm.HMAC256(GlobalConfig.SECRET);
JWTVerifier verifier = JWT.require(algorithm).withClaim(ACCOUNT, account).build();
verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
/**
* 获得token中的信息无需secret解密也能获得
*
* @return token中包含的用户登录帐号
*/
public static String getAccount() {
try {
DecodedJWT jwt = getJwt();
if (jwt == null) {
return null;
}
return jwt.getClaim(ACCOUNT).asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 获得token中的信息无需secret解密也能获得
*
* @return token中包含的用户登录帐号
*/
public static String getAccount(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(ACCOUNT).asString();
} catch (JWTDecodeException e) {
return null;
}
}
public static Long getUserId() {
try {
DecodedJWT jwt = getJwt();
if (jwt == null) {
return null;
}
return jwt.getClaim(USER_ID).asLong();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 获得token中的信息无需secret解密也能获得
*
* @return token中包含的用户ID
*/
public static Long getUserId(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(USER_ID).asLong();
} catch (JWTDecodeException e) {
return null;
}
}
public static TokenBean getTokenMsg() {
TokenBean tokenBean = new TokenBean();
try {
DecodedJWT jwt = getJwt();
if (jwt == null) {
return tokenBean;
}
tokenBean.setUserId(jwt.getClaim(USER_ID).asLong());
tokenBean.setAccount(jwt.getClaim(ACCOUNT).asString());
return tokenBean;
} catch (JWTDecodeException e) {
return tokenBean;
}
}
private static DecodedJWT getJwt() {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (Objects.isNull(servletRequestAttributes)) {
throw new MapleCheckException(ErrorCode.PARAM_ERROR);
}
HttpServletRequest request = servletRequestAttributes.getRequest();
String authorization = request.getHeader(GlobalConfig.TOKEN_NAME);
if (authorization == null) {
return null;
}
return JWT.decode(authorization);
}
/**
* 校验token是否有效
*
* @param token token信息
* @return 返回结果
*/
public static boolean verifyToken(String token) {
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(GlobalConfig.SECRET)).build();
DecodedJWT jwt = verifier.verify(token);
jwt.getClaims();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 创建token
*
* @param tokenBean token保存的信息
* @return token
*/
public static String createToken(TokenBean tokenBean) {
Algorithm algorithm = Algorithm.HMAC256(GlobalConfig.SECRET);
return JWT.create()
.withClaim(USER_ID, tokenBean.getUserId())
.withClaim(ACCOUNT, tokenBean.getAccount())
.withClaim(USER_TYPE, tokenBean.getUserType())
.sign(algorithm);
}
}
Filter登录拦截器
牵扯到了三个异常code,我们可以在ErrorCode.java
里面补充一下,如果没有引入自定义异常,可以手动throw new RuntimeException("笑小枫的异常信息")
替换一下就行了。
NO_TOKEN("1001", "用户未登录"),
TOKEN_EXPIRE("1002", "登陆超时,请重新登录"),
TOKEN_EXCHANGE("1003", "账号在其他地方登录,账号被踢出"),
USER_LOGIN_ERROR("2001", "用户名或密码错误"),
USER_STATUS_ERROR("2002", "用户已被停用,请联系管理员"),
首先在MapleDemoApplication.java
启动项上添加注解@ServletComponentScan
。
SpringBootApplication
上使用@ServletComponentScan
注解后
Servlet
可以直接通过@WebServlet
注解自动注册
Filter
可以直接通过@WebFilter
注解自动注册
Listener
可以直接通过@WebListener
注解自动注册
创建一个存放拦截器的包filter
,然后在包内创建我们的拦截器JwtFilter
,代码如下:👇
package com.maple.demo.filter;
import com.alibaba.fastjson.JSON;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.bean.GlobalConfig;
import com.maple.demo.util.JwtUtil;
import com.maple.demo.util.RedisUtil;
import com.maple.demo.util.ResultJson;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* 判断用户登录token
*
* @author 笑小枫
* @date 2022/07/20
*/
@WebFilter(filterName = "jwtFilter", urlPatterns = {"/*"})
@AllArgsConstructor
@Order(1)
public class JwtFilter implements Filter {
private final List excludedUrlList;
@Override
public void init(FilterConfig filterConfig) {
excludedUrlList.addAll(Arrays.asList(
"/sso/login",
"/sso/logout",
"/example/*",
"/webjars/**",
"/swagger/**",
"/v2/api-docs",
"/doc.html",
"/swagger-ui.html",
"/swagger-resources/**",
"/swagger-resources"
));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String url = ((HttpServletRequest) request).getRequestURI();
boolean isMatch = false;
for (String excludedUrl : excludedUrlList) {
if (Pattern.matches(excludedUrl.replace("*", ".*"), url)) {
isMatch = true;
break;
}
}
if (isMatch) {
chain.doFilter(request, response);
} else {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//处理跨域问题,跨域的请求首先会发一个options类型的请求
if (httpServletRequest.getMethod().equals(HttpMethod.OPTIONS.name())) {
chain.doFilter(request, response);
}
BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
RedisUtil redisService = (RedisUtil) factory.getBean("redisUtil");
String account;
String authorization = httpServletRequest.getHeader(GlobalConfig.TOKEN_NAME);
// 判断token是否存在,不存在代表未登录
if (StringUtils.isEmpty(authorization)) {
writeRsp(httpServletResponse, ErrorCode.NO_TOKEN);
return;
} else {
account = JwtUtil.getAccount(authorization);
String token = (String) redisService.get(GlobalConfig.getRedisUserKey(account));
// 判断token是否存在,不存在代表登陆超时
if (StringUtils.isEmpty(token)) {
writeRsp(httpServletResponse, ErrorCode.TOKEN_EXPIRE);
return;
} else {
// 判断token是否相等,不相等代表在其他地方登录
if (!token.equalsIgnoreCase(authorization)) {
writeRsp(httpServletResponse, ErrorCode.TOKEN_EXCHANGE);
return;
}
}
}
// 保存redis,每次调用成功都刷新过期时间
redisService.set(GlobalConfig.getRedisUserKey(account), authorization, GlobalConfig.EXPIRE_TIME);
chain.doFilter(httpServletRequest, httpServletResponse);
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
private void writeRsp(HttpServletResponse response, ErrorCode errorCode) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setHeader("content-type", "application/json;charset=UTF-8");
try {
response.getWriter().println(JSON.toJSON(new ResultJson(errorCode.getCode(), errorCode.getMsg())));
} catch (IOException e) {
e.printStackTrace();
}
}
}
密码加密与验证
首先再自定义一个异常吧,创建一个通用点的MapleCommonException.java
,后续都偷懒统一抛这个异常了
代码如下:
package com.maple.demo.config.exception;
import com.maple.demo.config.bean.ErrorCode;
/**
* 通用异常,偷懒就抛出此异常吧
*
* @author 笑小枫
* @date 2022/07/20
*/
public class MapleCommonException extends MapleBaseException {
public MapleCommonException(String code, String errorMsg) {
super(code, errorMsg);
}
public MapleCommonException(ErrorCode code) {
super(code);
}
public MapleCommonException(ErrorCode code, String errorMsg) {
super(code, errorMsg);
}
}
密码加密就简单的使用md5加盐值吧,代码如下👇
package com.maple.demo.util;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.exception.MapleCommonException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
/**
* MD5撒盐加密 及MD5加密
*
* @author 笑小枫
* @date 2022/7/20
*/
@Slf4j
public class Md5Util {
private Md5Util() {
}
/**
* 密码加密处理
*
* @param password 密码明文
* @param salt 盐
* @return 加密后密文
*/
public static String encrypt(String password, String salt) {
if (StringUtils.isEmpty(password) || StringUtils.isEmpty(salt)) {
log.error("密码加密失败原因: password and salt cannot be empty");
throw new MapleCommonException(ErrorCode.PARAM_ERROR);
}
return DigestUtils.md5DigestAsHex((salt + password).getBytes());
}
/**
* 校验密码
*
* @param target 待校验密码
* @param source 原密码
* @param salt 加密原密码的盐
*/
public static boolean verifyPassword(String target, String source, String salt) {
if (StringUtils.isEmpty(target) || StringUtils.isEmpty(source) || StringUtils.isEmpty(salt)) {
log.info("校验密码失败,原因 target ={}, source ={}, salt ={}", target, source, salt);
return false;
}
String targetEncryptPwd = encrypt(target, salt);
return targetEncryptPwd.equals(source);
}
public static void main(String[] args) {
log.info(encrypt("admin111", "123456"));
}
}
通过main方法生成一个加密后的值吧,然后把盐值和密码都扔到数据库里面,后面我们就根据账号(account)和密码(password)进行登录。
注意:创建用户的时候先随机生成一个盐(salt),后续根据盐值再去生成密码。
用户登录接口
model和param
创建一个vo包吧,后续的model和query对象统一放在这里了~
在vo包下创建一个query包,然后创建登录请求对象LoginQuery.java
package com.maple.demo.vo.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author 笑小枫
* @date 2022/7/20
*/
@Data
@ApiModel(value = "用户登录请求对象", description = "用户中心-用户登录请求对象")
public class LoginQuery {
@ApiModelProperty(value = "登录账号")
private String account;
@ApiModelProperty(value = "登录密码")
private String password;
}
在vo包下创建一个model包,然后创建返回的用户信息对象UserModel.java
package com.maple.demo.vo.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
/**
* 用户中心-用户信息
*
* @author 笑小枫
* @date 2022/7/20
*/
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
@ApiModel(value = "用户视图对象", description = "用户中心-用户信息")
public class UserModel {
@ApiModelProperty(value = "用户ID")
private Long id;
@ApiModelProperty(value = "用户账号")
private String account;
@ApiModelProperty(value = "用户姓名")
private String userName;
@ApiModelProperty(value = "用户昵称")
private String nickName;
@ApiModelProperty(value = "用户类型")
private String userType;
@ApiModelProperty(value = "用户邮箱")
private String email;
@ApiModelProperty(value = "手机号码")
private String phone;
@ApiModelProperty(value = "用户性别")
private String sex;
@ApiModelProperty(value = "头像地址")
private String avatar;
@ApiModelProperty(value = "帐号状态")
private String status;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "用户验证Token")
private String token;
}
controller
package com.maple.demo.controller;
import com.maple.demo.service.IUserService;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 系统登录
*
* @author 笑小枫
* @date 2022/7/20
*/
@Api(tags = "管理系统-系统登录操作")
@RestController
@AllArgsConstructor
@RequestMapping(value = "/sso")
public class LoginController {
private final IUserService userService;
@ApiOperation(value = "用户登录")
@PostMapping("/login")
public UserModel login(@RequestBody LoginQuery req) {
return userService.login(req);
}
@ApiOperation(value = "用户退出登录")
@GetMapping("/logout")
public void logout() {
userService.logout();
}
}
service
package com.maple.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.maple.demo.entity.User;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
/**
*
* 用户中心-用户信息表 服务类
*
*
* @author 笑小枫
* @since 2022-07-11
*/
public interface IUserService extends IService {
/**
* 用户登录
*
* @param req 用户信息
* @return 用户登录信息
*/
UserModel login(LoginQuery req);
/**
* 退出系统,清除用户token
*/
void logout();
}
serviceImpl
package com.maple.demo.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.bean.GlobalConfig;
import com.maple.demo.config.bean.TokenBean;
import com.maple.demo.config.exception.MapleCheckException;
import com.maple.demo.entity.User;
import com.maple.demo.mapper.UserMapper;
import com.maple.demo.service.IUserService;
import com.maple.demo.util.JwtUtil;
import com.maple.demo.util.Md5Util;
import com.maple.demo.util.RedisUtil;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
*
* 用户中心-用户信息表 服务实现类
*
*
* @author Maple
* @since 2022-07-11
*/
@Service
@AllArgsConstructor
public class UserServiceImpl extends ServiceImpl implements IUserService {
private final UserMapper userMapper;
private final RedisUtil redisUtil;
@Override
public UserModel login(LoginQuery req) {
User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
.eq(User::getAccount, req.getAccount())
.last("LIMIT 1"));
if (Objects.isNull(user)) {
throw new MapleCheckException(ErrorCode.USER_LOGIN_ERROR);
}
if ("1".equals(user.getStatus())) {
throw new MapleCheckException(ErrorCode.USER_STATUS_ERROR);
}
if (!Md5Util.verifyPassword(req.getPassword(), user.getPassword(), user.getSalt())) {
throw new MapleCheckException(ErrorCode.USER_LOGIN_ERROR);
}
TokenBean tokenBean = TokenBean.builder()
.userId(user.getId())
.userType(user.getUserType())
.account(user.getUserName())
.build();
UserModel userModel = new UserModel();
BeanUtils.copyProperties(user, userModel);
String token;
try {
token = JwtUtil.createToken(tokenBean);
} catch (Exception e) {
log.error(e.getMessage());
throw new MapleCheckException(ErrorCode.COMMON_ERROR);
}
userModel.setToken(token);
redisUtil.set(GlobalConfig.getRedisUserKey(user.getAccount()), token);
return userModel;
}
@Override
public void logout() {
redisUtil.remove(GlobalConfig.getRedisUserKey(JwtUtil.getAccount()));
}
}
使用JWT的用户信息
直接使用JwtUtil.class
工具类里的方法,即可拿到对应的数据
JwtUtil.getUserId();
JwtUtil.getAccount();
功能测试
测试之前需要在数据中添加一条数据
INSERT INTO `maple`.`usc_user`( `account`, `user_name`, `nick_name`, `user_type`, `email`, `phone`, `sex`, `avatar`, `salt`, `password`, `status`, `create_id`, `create_name`, `create_time`, `update_id`, `update_name`, `update_time`, `delete_flag`, `remark`) VALUES ('admin', 'admin', '笑小枫', '00', '1150640979@qq.com', '18300000001', '0', '', '123456', 'e9c764f9b51772f00af80a54d38a692e', '0', 1, '笑小枫', '2022-07-11 13:48:44', 1, '笑小枫', '2022-07-11 13:48:44', 0, '管理员');
直接在LoginController.java
里面添加getUserId
方法进行测试,详细代码如下:
package com.maple.demo.controller;
import com.maple.demo.service.IUserService;
import com.maple.demo.util.JwtUtil;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 系统登录
*
* @author 笑小枫
* @date 2022/7/20
*/
@Api(tags = "管理系统-系统登录操作")
@RestController
@AllArgsConstructor
@RequestMapping(value = "/sso")
public class LoginController {
private final IUserService userService;
@ApiOperation(value = "用户登录")
@PostMapping("/login")
public UserModel login(@RequestBody LoginQuery req) {
return userService.login(req);
}
@ApiOperation(value = "用户退出登录")
@GetMapping("/logout")
public void logout() {
userService.logout();
}
@ApiOperation(value = "获取登录用户信息")
@GetMapping("/getUserId")
public String getUserId() {
return "当前登录用户的ID为" + JwtUtil.getUserId();
}
}
在未登录状态请求接口,返回信息如下:
调用登录接口,进行用户登录
登录后,拿到token,在请求头设置Authorization
参数
添加完之后,记得要把tab页关闭,再打开,然后header参数才会生效,在请求头部可以看到
再次调用,返回信息如下:
可以看到,到此我们的登录拦截功能就已经完全实现了。
小结
好啦,本文就到这里了,我们简单的总结一下,主要介绍了以下内容👇👇
- 介绍了什么是JWT
- 用户登录拦截
- 用户登录实现
关于笑小枫💕
本章到这里结束了,喜欢的朋友关注一下我呦😘😘,大伙的支持,就是我坚持写下去的动力。
老规矩,懂了就点赞收藏;不懂就问,日常在线,我会就会回复哈~🤪
微信公众号:笑小枫
笑小枫个人博客:https://www.xiaoxiaofeng.com
CSDN:https://zhangfz.blog.csdn.net
本文源码:https://github.com/hack-feng/maple-demo
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net