一、 背景
项目需要,自己编写登录和身份校验,于是采用了JWT的方式。
二、实现步骤
2.1 引入JWT组件
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version> <!-- 使用时请检查最新版本 -->
</dependency>
2.2 编写JWT工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
/**
* @author *****
* @description JWT 工具类
* @date 2024年04月08日 15:45
*/
public class JwtTokenUtil {
/**
* 生成token
* @param token存入的值,包括username userfullname 手机号 代理商id 代理商名称
* @return
*/
public static String generateToken(String userToken) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + Constants.TOKEN_EXPIRATION);
return Jwts.builder()
.setSubject(userToken)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, Constants.TOKEN_SECRET)
.compact();
}
/**
* 根据token获得Claims
* @param username
* @return
*/
public static Claims getClaimsFromToken(String token) {
try {
return Jwts.parser().setSigningKey(Constants.TOKEN_SECRET).parseClaimsJws(token).getBody();
} catch (Exception e) {
return null;
}
}
/**
* 根据token获得用户
* @param username
* @return
*/
public static String getUsernameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getSubject() : null;
}
/**
* 校验token是否有效
* @param token
* @param username
* @return
*/
public static boolean validateToken(String token) {
final String usernameFromToken = getUsernameFromToken(token);
return (usernameFromToken != null && !isTokenExpired(token));
}
/**
* token 是否超时
* @param token
* @return
*/
private static boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* token 得到token的超时时间
* @param token
* @return
*/
private static Date getExpirationDateFromToken(String token) {
final Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
}
2.3 用户登录生成token
public ResultBO<String> userLogin(SysUserBO sysUserBO)
{
ResultBO<String> resultBO = new ResultBO<String>();
if(StringUtil.isNullOrEmpty(sysUserBO.getUserName()) )
{
resultBO.setCode(CommonResult.USER_IS_NOT_NULL.getCode());
resultBO.setSuccess(false);
resultBO.setMessage("登录失败:请输入用户名");
return resultBO;
}
if(StringUtil.isNullOrEmpty(sysUserBO.getUserPassWord()) )
{
resultBO.setCode(CommonResult.USER_IS_NOT_NULL.getCode());
resultBO.setSuccess(false);
resultBO.setMessage("登录失败:请输入密码");
return resultBO;
}
// 密码规则:用户账号+用户密码+固定字符串 然后用AES加密
String passWord = sysUserBO.getUserName()+sysUserBO.getUserPassWord()+ Constants.PASS_WORD_SECRET;
passWord=AesUtil.encrypt(passWord);
SysUserDO sysUserDO = new SysUserDO();
sysUserDO = ModelMapperUtil.map(sysUserBO,SysUserDO.class);
sysUserDO.setUserPassWord(passWord);
List<SysUserDO> sysUserDOS = sysUserDao.selectUserList(sysUserDO);
if(sysUserDOS!=null && sysUserDOS.size()>0 )
{
// 查询到了用户
// 登录成功创建token 将用户相关关键的字段都放入到token里面去
TokenUser tokenUser = new TokenUser();
tokenUser.setCenterUserId(sysUserDOS.get(0).getCenterUserId());
tokenUser.setUserFullName(sysUserDOS.get(0).getUserFullName());
tokenUser.setId(sysUserDOS.get(0).getId());
tokenUser.setUserName(sysUserDOS.get(0).getUserName());
tokenUser.setAgentId(sysUserDOS.get(0).getAgentId());
tokenUser.setAgentName(sysUserDOS.get(0).getAgentName());
tokenUser.setPhoneNumber(sysUserDOS.get(0).getPhoneNumber());
String tokenUserDOStr = FastJsonUtil.toJsonString(tokenUser);
String token = JwtTokenUtil.generateToken(tokenUserDOStr); // sysUserDOS.get(0).getUserName()
resultBO.setCode(CommonResult.SUCCESS.getCode());
resultBO.setSuccess(true);
resultBO.setMessage("登录成功");
resultBO.setData(token);
}
else
{
// 查询不到数据
// 登录失败
resultBO.setCode(CommonResult.DATA_NOT_EXISTS_ERROR.getCode());
resultBO.setSuccess(false);
resultBO.setMessage("登录失败:账号或密码错误");
}
return resultBO;
}
2.4 编写拦截器校验token
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author ****
* @description 自定义拦截器
* @date 2024年04月08日 17:05
*/
@Component
public class MyInterceptor implements HandlerInterceptor {
/**
* 请求头
*/
private static final String HEADER_AUTH = "token";
/**
* 拦截过滤方法
* @param request
* @param response
* @param handler
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
response.setContentType("application/json; charset=utf-8");
String prop = request.getRequestURI().substring(request.getContextPath().length());
String url = prop;
// 登录和注册等请求不需要令牌
// 安全连接,无需拦截配置 一般开放登录方法
if (prop.contains("/login/") || prop.contains("/syncUser") ) {
return true;
}
// System.out.println("***********没有被放行的请求*************");
// 从请求头里面读取token
String token = request.getHeader(HEADER_AUTH);
if (token == null) {
throw new RuntimeException("请求失败,令牌为空");
}
System.out.println(token);
// 解析令牌
boolean validateToken = JwtTokenUtil.validateToken(token);
if(!validateToken)
{
throw new RuntimeException("请求失败,无效的令牌");
}
else
{
try
{
// 如果是有效的,则解析token里面的user信息提供给接口使用
String tokenStr = JwtTokenUtil.getUsernameFromToken(token);
TokenUser tokenUser = FastJsonUtil.toObj(tokenStr,TokenUser.class);
ContextHolder.setTokenUser(tokenUser);
// 将用户信息添加到请求属性中
// request.setAttribute("currentUser", tokenUser);
return true;
}catch(Exception ex)
{
throw new RuntimeException("请求失败,无效的令牌");
}
}
}
}
2.5 使用ContextHolder给接口调用获取当前登陆人
/**
* @author ****
* @description token解析用户信息Context类
* @date 2024年04月09日 10:59
*/
public class ContextHolder {
public static ThreadLocal<TokenUser> context = new ThreadLocal<>();
public static void setTokenUser(TokenUser tokenUserDO) {
context.set(tokenUserDO);
}
public static TokenUser getTokenUser() {
return context.get();
}
public static void shutdown() {
context.remove();
}
}
2.6 注册拦截器,注意文件放置的位置要跟启动类同一层,这样才能扫描到
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* @author ***8
* @description 自定义拦截器注册
* @date 2024年04月08日 17:11
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Resource
private MyInterceptor loginInterceptor;
@Override
public void addInterceptors (InterceptorRegistry registry) {
//注册LoginInterceptor拦截器
registry.addInterceptor(loginInterceptor);
}
}
2.7 TokenUser类
@Data
public class TokenUser {
/**
* 用户id
*/
private Long id;
/**
* 用户名(登录账号名)
*/
private String userName;
/**
* 代理商id
*/
private String agentId;
/**
* 代理商名称
*/
private String agentName;
/**
* 用户全名
*/
private String userFullName;
/**
* 手机号
*/
private String phoneNumber;
/**
* 中心系统 userid
*/
private String centerUserId;
}