Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。
JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案,JWT是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
引入maven依赖
<!-- JSON Web Tokens jar -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.0</version>
</dependency>
编写jwt工具类,用于生成token,解析token,代码如下:
/**
* @author 2019 shiwq
* 获取token工具类
*/
public class JwtHelper {
/**
* 传入用户ID,设备信息,封装到token中
*
* @param userId 用户id
* @return token token
* @throws Exception
*/
public String createTokenWithClaim(long userId, String deviceInfo) throws Exception {
//签名,自定义即可
Algorithm algorithm = Algorithm.HMAC256("****");
//token的HEADER
Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
//15天过期
Date nowDate = new Date();
Date expireDate = getAfterDate(nowDate, 0, 0, 15, 0, 0, 0);
String token = JWT.create()
/*设置头部信息 Header*/
.withHeader(map)
/*设置 载荷 Payload*/
//设备信息
.withClaim("deviceInfo", deviceInfo)
//用户ID
.withClaim("userId", userId)
//签名是有谁生成 例如 服务器
.withIssuer("SERVICE")
//签名的主题
.withSubject("login")
//定义在什么时间之前,该jwt都是不可用的.
//.withNotBefore(new Date())
//签名的观众 也可以理解谁接受签名的
.withAudience("aoBaoSystem")
//生成签名的时间
.withIssuedAt(nowDate)
//签名过期的时间
.withExpiresAt(expireDate)
/*签名 Signature */
.sign(algorithm);
JwtHelper jwtHelper = new JwtHelper();
return token;
}
/**
* 返回一定时间后的日期
*
* @param date 开始计时的时间
* @param year 增加的年
* @param month 增加的月
* @param day 增加的日
* @param hour 增加的小时
* @param minute 增加的分钟
* @param second 增加的秒
* @return
*/
public Date getAfterDate(Date date, int year, int month, int day, int hour, int minute, int second) {
if (date == null) {
date = new Date();
}
Calendar cal = new GregorianCalendar();
cal.setTime(date);
if (year != 0) {
cal.add(Calendar.YEAR, year);
}
if (month != 0) {
cal.add(Calendar.MONTH, month);
}
if (day != 0) {
cal.add(Calendar.DATE, day);
}
if (hour != 0) {
cal.add(Calendar.HOUR_OF_DAY, hour);
}
if (minute != 0) {
cal.add(Calendar.MINUTE, minute);
}
if (second != 0) {
cal.add(Calendar.SECOND, second);
}
return cal.getTime();
}
/**
* 将用户ID和设备信息,过期时间封装到map中返回
*
* @param token token 传入token
* @return 返回map,包含载荷信息,这里设置的是用户id和设备信息
* @throws Exception
*/
public Map<String, Object> verifyToken(String token) throws Exception {
Algorithm algorithm = Algorithm.HMAC256("****");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("SERVICE")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
String subject = jwt.getSubject();
Map<String, Claim> claims = jwt.getClaims();
long userId = claims.get("userId").asLong();
String deviceInfo = claims.get("deviceInfo").asString();
Date expireDate = jwt.getExpiresAt();
Map<String, Object> map = new HashMap<String, Object>();
map.put("userId", userId);
map.put("deviceInfo", deviceInfo);
map.put("expireDate", expireDate);
return map;
}
}
使用实例,登录后签发token
@Override
public ResponseResult loginByPassword(String userCode, String password, String deviceInfo) throws Exception {
//获取用户信息
UserInfo userInfo;
//前置代码不展示,正常逻辑走完后,进入token生成,并保存
if (true) {
JwtHelper jwtHelper = new JwtHelper();
//签发token
String token = jwtHelper.createTokenWithClaim(userInfo.getUserId(), deviceInfo);
//保存token到数据库
if (userLoginMapper.updateToken(userInfo.getUserId(), token) > 0) {
//token保存成功,返回前台
return new ResponseResult("success", "登陆成功", userInfo);
}
return new ResponseResult("error", "密码错误", null);
}
客户端拿到token后,每次请求将token带上,服务端的拦截器拦截并处理请求是否合法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setCharacterEncoding("UTF-8");
String token = request.getHeader("Authorization");
String deviceId = request.getHeader("deviceId");
JwtHelper jwtHelper = new JwtHelper();
PrintWriter out = null;
try {
Map<String, Object> map = jwtHelper.verifyToken(token);
//获取token中的用户id
long userId = (long) map.get("userId");
//从数据库中获取用户信息
UserLogin userLogin = userLoginMapper.getUserLogin(userId);
out = response.getWriter();
if(userLogin == null){
out.append(JSON.toJSONString(new ResponseResult("error", "token验证不通过", null)));
return false;
}
String deviceInfo = map.get("deviceInfo").toString();
//校验token是否过期
//这里我只做了简单的userId校验和过期时间校验,各位可以根据实际情况增加或减少相应的条件校验
Date expireDate = (Date) map.get("expireDate");
//判断当前请求的设备信息和token包含的设备信息是否一致
if (deviceId.equals(deviceInfo)) {
if (expireDate.getTime() > System.currentTimeMillis()) {
if (token.equals(userLogin.getOwnToken())){//校验成功,放行
return true;
}
}
}
out.append(JSON.toJSONString(new ResponseResult("error", "token验证不通过", null)));
return false;
} catch (Exception e) {
e.printStackTrace();
response.sendError(500);
return false;
}
如有错误,请及时指出,感谢大家。