上文介绍了JWT的创建 与 拦截器的配置
这篇基于上篇文档jwt生成token后的在登录中的具体使用
(ps:因为我也是探索使用 所以陆续更新。有好的建议也欢迎指出)
首先拦截器和 JwtTokenUtil我重新放一个。可能会有些许区别
此篇文章加入了会话管理token的存储。当然你可以自己切换为redis管理。
因为隐私问题 不方便透漏包名。请自行添加 若少什么不会的东西 可以留言 我会提供
/**
* @author chunying
* @Date 2020.12.06
* @Description 关于jwt的操作
*/
@Component
public class JwtTokenUtil {
private static final Logger LOG = LoggerFactory.getLogger(JwtTokenUtil.class);
//加密key
private static String KEY = null;
@Value("${token.encrypt.key}")
public void setKey(String key) {
KEY = key;
}
/**
* 生成token 以用户基本信息为payload基础生成token 这样解析出来知道是哪个用户
*/
public static String createToken(UserToken userToken)throws Exception {
StringBuffer sbResult = new StringBuffer();
JSONObject jwtFirstJson = new JSONObject();
String jwtSecondStr = Base64Util.getEncodeStr(JSON.toJSONString(userToken), TokenConstant.FORMAT);
//拼接jwt格式
jwtFirstJson.put(TokenConstant.TYPE_KEY, TokenConstant.TYPE_VALUE);
String jwtFirstStr = Base64Util.getEncodeStr(JSON.toJSONString(jwtFirstJson), TokenConstant.FORMAT);
sbResult.append(jwtFirstStr).append(TokenConstant.SPOT).append(jwtSecondStr);
String hexString = AesUtil.parseEncrypt(sbResult.toString(), KEY);
//设置过期时间
// setLocalUser(userToken);
// String jwtString = TOKEN_PREFIX + JWT.create()
// .withSubject(jsonString)
// .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
// .sign(AesUtil.parseEncrypt(jsonString, KEY));
return sbResult.append(TokenConstant.SPOT).append(hexString).toString();
}
/**
* 解析返回的token
*/
public static UserToken verifyToken(String value) {
if (StringUtils.isBlank(value)) {
throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_FORMAT);
}
String[] valueArray = value.split("\\.");
if (valueArray.length < 3) {
throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_FORMAT);
}
String thirdStr = AesUtil.parseDesEncrypt(valueArray[2], KEY);
//校验是否是真实用户?暂定先返回传过来的信息
if (StringUtils.isBlank(thirdStr)) {
throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_FORMAT);
}
String[] thirdDesStr = thirdStr.split("\\.");
if (thirdDesStr == null || thirdDesStr.length < 2) {
throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_CONTENT);
}
//校验是否符合格式
try {
//检验payload 与 header 是否被动过
if (!valueArray[0].equals(thirdDesStr[0]) || !valueArray[1].equals(thirdDesStr[1])) {
throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_MODIFIED);
}
//first str
String firstStr = Base64Util.getDecodeStr(thirdDesStr[0], TokenConstant.FORMAT);
JSONObject jsonObject = JSON.parseObject(firstStr);
String typeValue = jsonObject.getString(TokenConstant.TYPE_KEY);
if (!StringUtils.isNotBlank(typeValue) || !typeValue.equals(TokenConstant.TYPE_VALUE)) {
throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_CONTENT);
}
//second str
String secondStr = Base64Util.getDecodeStr(thirdDesStr[1], TokenConstant.FORMAT);
UserToken userToken = JSON.parseObject(secondStr, UserToken.class);
// if (isExpire(userToken)) {
// throw new JwtException(ErrorCode.TOKEN_EXPIRE, "token is expire");
// }
return userToken;
}catch(Exception e) {
LOG.error(e.getMessage(), e);
e.printStackTrace();
}
return null;
}
/**
* TODO
* 判断用户token是否过期
* @param userToken
* @return
*/
@Deprecated
public static Boolean isExpire(UserToken userToken) {
return null;
}
/**
* 刷新token生效时间
* @param usertoken
*/
public static void refreshExpireTime(HttpSession session, UserToken usertoken) {
setLocalUser(session, usertoken);
}
/**
* 将token放入本地线程缓存 可以知道当前线程用户信息
* @param userToken
*/
public static void setLocalUser(HttpSession session, UserToken userToken) {
session.setAttribute(TokenConstant.SESSION_KEY, userToken);
}
/**
* 校验session中用户信息是否过期
* @param session
* @param userToken
* @return
*/
public static Boolean validateSession(HttpSession session, UserToken userToken) {
if (session == null) {
return true;
}
Object attribute = session.getAttribute(TokenConstant.SESSION_KEY);
return attribute == null;
}
/**
* 获取当前session域的用户token信息
* @return
*/
public static UserToken getUserToken(HttpSession session) {
return (UserToken)session.getAttribute(TokenConstant.SESSION_KEY);
}
/**
* 清空当前session
* @param session
*/
public static void deleteSession(HttpSession session) {
if (session == null) {
return;
}
Enumeration<String> attributeNames = session.getAttributeNames();
while (attributeNames.hasMoreElements()) {
// 获取session的属性名称
String name = attributeNames.nextElement();
session.removeAttribute(name);
}
}
/**
* 获取当前请求会话中的用户信息
* @return
* @throws Exception
*/
public static UserToken getUserTokenBySession() throws CommonException {
HttpSession session = ServletUtil.getRequest().getSession(false);
if (session == null) {
throw new TokenException(HttpCode.FORBIDDEN, ErrorCode.TOKEN_EXPIRE);
}
UserToken userToken = getUserToken(session);
return userToken;
}
}
因为隐私问题 不方便透漏包名。请自行添加 若少什么不会的东西 可以留言 我会提供
/**
* @author chunying
* @Date 2020.12.07
* @Descrip 全局方法拦截 若方法不想使用此拦截器 请看JwtIgnore
*/
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private CommonPropertyUtil commonPropertyUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Boolean tokenIsOpen = commonPropertyUtil.getTokenIsOpen();
if (!tokenIsOpen) {
return true;
}
//swagger页面的请求路径 放行
// 由于SpringBoot异常处理机制 在发生系统错误时 ErrorPageCustomize 会发送/error请求进行处理
// 为了程序正常抛出错误 放行此url
String requestURI = request.getRequestURI();
String[] swaggerUri={"/swagger-resources","/webjars/bycdao-ui/images/api.ico","/swagger-resources/configuration/ui", "/error"};
for (String s : swaggerUri) {
if(requestURI.startsWith(s)){
return true;
}
}
//从http请求头中取出token
final String token = request.getHeader(TokenConstant.AUTH_HEADER_KEY);
//如果不是映射到方法,直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
//如果是方法探测,直接通过
if (HttpMethod.OPTIONS.equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
//如果方法有JwtIgnore注解,直接通过
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (method.isAnnotationPresent(JwtIgnore.class)) {
JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);
if (jwtIgnore.value()) {
return true;
}
return true;
}
if (StringUtils.isBlank(token)) {
throw new TokenException(HttpCode.FORBIDDEN, ErrorCode.TOKEN_NULL);
}
//验证,并获取token内部信息
UserToken userToken = JwtTokenUtil.verifyToken(token);
//校验是否登录
Boolean validateSession = JwtTokenUtil.validateSession(request.getSession(false), userToken);
if (validateSession) {
throw new TokenException(HttpCode.FORBIDDEN, ErrorCode.TOKEN_EXPIRE);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//方法结束后,移除缓存的token
// JwtTokenUtil.removeUserToken();
}
}
因为这篇文章是改良后的
有一些异常的和取属性的东西放入了配置文件或者继承自己写的底层异常 我就不提供了 可以去上篇文章寻找旧版整合 或者依据各自需要各自修改。
注意 我这里导入了其他的工具类。如果你也想这么做 请将拦截器交给spring去管理 否则注入的类是null
说一下思路
我这里会校验三个部分
一、token是否存在
二、token是否符合我们的加密要求
三、登录会将token存入服务器会话session并设置过期时间 时间请自定义、每次会校验会话是否超时、是否存在token
以下是登录controller
@Api(tags = "用户登录相关")
@RestController
@PropertySource("classpath:application.yml")
public class UserLoginController {
@Value("${login.error.maxCount:5}")
private Integer maxLoginErrorCount;
@Value("${token.session.expire:300}")
private Integer tokenSessionMax;
@Autowired
private LoginService loginService;
@Autowired
private CommonPropertyUtil commonPropertyUtil;
/**
* 登录
* @param
* @return
*/
@ApiOperation(value="用户登录")
@JwtIgnore
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Object login(@RequestBody LoginParamVO parameter, HttpServletRequest request,
HttpServletResponse response) {
//查询 校验
if (parameter.isNull()) {
return ResultContent.error(HttpCode.BAD_REQUEST, ErrorCode.LOGIN_PARAM_NULL);
}
//解密密码
String decryptPassword = AesUtil.decryptByKeyAndIV(parameter.getPassword());
parameter.setPassword(decryptPassword);
//判断登录用户是否存在
SysUser sysUserByName = loginService.findSysUserByName(parameter);
if (sysUserByName == null) {
return ResultContent.error(HttpCode.BAD_REQUEST, ErrorCode.LOGIN_USERNAME_NOT_FOUND);
}
//校验密码
Boolean validatePassword = loginService.validatePassword(parameter.getPassword(), sysUserByName);
if (!validatePassword) {
Integer loginErrorCount = sysUserByName.getLoginErrorCount();
if (loginErrorCount > maxLoginErrorCount) {
return ResultContent.error(HttpCode.BAD_REQUEST, ErrorCode.LOGIN_ERROR_MAX);
}
sysUserByName.setLoginErrorCount(loginErrorCount + 1);
loginService.updateSysUserById(sysUserByName);
return ResultContent.error(HttpCode.BAD_REQUEST, ErrorCode.LOGIN_PASSWORD_FAIL);
}
loginService.loginSuccess(sysUserByName);
//生成token
UserToken userToken = new UserToken(sysUserByName);
String token = null;
try {
token = JwtTokenUtil.createToken(userToken);
response.setHeader(TokenConstant.AUTH_HEADER_KEY, token);
}catch(Exception e) {
e.printStackTrace();
}
//放入session
HttpSession session = request.getSession();
session.setMaxInactiveInterval(tokenSessionMax);
JwtTokenUtil.setLocalUser(session, userToken);
ResultContent<Map> resultContent = new ResultContent<>();
resultContent.setExtra("token", token);
resultContent.successByExtra(resultContent);
return resultContent;
}
/**
* 登出|注销
* @param
* @param request
* @return
*/
@JwtIgnore
@ApiOperation(value = "用户注销")
@RequestMapping(value = "/logout", method = RequestMethod.POST)
public Object logout(HttpServletRequest request) {
// 清空session
try {
JwtTokenUtil.deleteSession(request.getSession());
}catch(Exception e) {
return ResultContent.error(HttpCode.INTERNAL_SERVER_ERROR, ErrorCode.UN_KNOWN_ERROR);
}
return ResultContent.success();
}
}
Service
数据库存的是md5带盐值的密码
前端传输的是带key和偏移量IV的Aes加密key
后端拿到后需要先解密 然后按照盐值进行加密对比
具体加解密工具类 这个有很多 就不提供了哈 如果实在查不到需要的话请留言
或者等等下篇文章我会提供
@Service
public class LoginService {
//mapper 我就不提供了
@Autowired
private SysUserMapper sysUserMapper;
/**
* jiao
* @param paramPassword
* @param sysUser
* @return
*/
public Boolean validatePassword(String paramPassword, SysUser sysUser) {
String salt = sysUser.getSalt();
String encryptData = EncryptionUtil.encryptByMD5WithSalt(paramPassword, salt);
return encryptData.equals(sysUser.getPassword());
}
public void loginSuccess(SysUser sysUser) {
sysUser.setLoginCount(sysUser.getLoginCount() + 1);
sysUser.setLastLoginTime(sysUser.getLoginTime());
sysUser.setLoginTime(new Date());
sysUser.setLoginErrorCount(0);
updateSysUserById(sysUser);
}
public SysUser findSysUserByName(LoginParamVO parameter) {
SysUser sysUser = new SysUser();
sysUser.setUsername(parameter.getUsername());
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>(sysUser);
SysUser sysUserByUserName = sysUserMapper.selectOne(queryWrapper);
return sysUserByUserName;
}
@Transactional(rollbackFor = Exception.class)
public void updateSysUserById(SysUser sysUser) {
sysUserMapper.updateById(sysUser);
}
}
这篇文章提供了登录的基本思路 一些VO、mapper 和加解密并没有提供、太麻烦了。。。。这些根据自己需要适当修改或查询就行了。或者实在不会可以留言~