一、 背景
项目需要,自己编写登录和身份校验,于是采用了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*/@Overridepublic 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("***********没有被放行的请求*************");// 从请求头里面读取tokenString 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 {@Resourceprivate MyInterceptor loginInterceptor;@Overridepublic 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;
}