前言
什么是JWT?
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
。
那么我们现在回到JWT的主题上。
JWT长什么样?
JWT是由三段信息构成的,将这三段信息文本用.
链接一起就构成了Jwt字符串。就像这样:
jwtString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
Spring Boot 中引入JWT
pom文件引入依赖 :
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>2.2.0</version>
</dependency>
jwt工具代码实现 :
import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.JWTVerifier;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @author: zly
* @date: 2020/03/27 13:54
*/
@Slf4j
public class JwtUtil {
/**
* 密钥 (这个密钥需要替换成你自己的密钥,尽可能复杂,并不对外开放)
*/
private static final String SECRET = "SECRET";
/**
* token过期时间
*/
private static final String EXP = "exp";
/**
* token生成时间
*/
private static final String PAYLOAD = "payload";
/**
* 一天毫秒值
*/
private static final long ONE_DAY_MSECS = 86400000;
/**
* 一分钟毫秒值
*/
private static final long ONE_MINUTE_MSECS = 60000;
/**
* 加密
*
* @param object 加密数据
* @param minutes 有效期(分钟)
*/
public static <T> String encode(T object, long minutes) {
try {
final JWTSigner signer = new JWTSigner(SECRET);
final Map<String, Object> data = new HashMap<>(10);
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = objectMapper.writeValueAsString(object);
data.put(PAYLOAD, jsonString);
long exp = System.currentTimeMillis() + (ONE_MINUTE_MSECS * minutes);
log.info("超时时间:[{}]", Instant.ofEpochMilli(exp));
data.put(EXP, exp);
String sign = signer.sign(data);
log.info("jwt签名:[{}]", sign);
return sign;
} catch (IOException e) {
log.error("生成token错误 : [{}] ", e.getMessage());
return null;
}
}
/**
* 数据解密
*
* @param jwt 解密数据
* @param tClass 解密对象类型
*/
public static <T> T decode(String jwt, Class<T> tClass) {
final JWTVerifier jwtVerifier = new JWTVerifier(SECRET);
try {
final Map<String, Object> data = jwtVerifier.verify(jwt);
String json = (String) data.get(PAYLOAD);
long exp = (Long) data.get(EXP);
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis > exp) {
log.error("token超时 : [{}] ", "token超时...");
throw new TimeoutException("token expired! "
+ "expired time is: " + Instant.ofEpochMilli(exp)
+ ", current time is: " + Instant.ofEpochMilli(currentTimeMillis) + ".");
}
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, tClass);
} catch (Throwable e) {
log.error("token解密错误 : [{}] ", e.getMessage());
return null;
}
}
}
登录逻辑使用:
/**
* 登录控制器
*
* @author zly
* @date 2020/3/27 15:29
*/
@Slf4j
@RestController
@RequestMapping("api")
@Api(tags = "登录接口文档")
public class LoginController {
@Resource
private UserService userService;
@CheckLogin
@PostMapping("login")
@ApiOperation(value = "登录", notes = "登录")
@SuppressWarnings("unchecked")
public Result<UserVO> login(@RequestBody UserLoginDTO userLoginDTO) {
UserVO userVO = null;
User queryUser = this.getUser(userLoginDTO);
/* 用户是否存在 */
if (Objects.isNull(queryUser)) {
return ResultGenerator.genFailResult("用户不存在!");
}
/* 校验密码 */
if (!queryUser.getPassword().equals(userLoginDTO.getPassword())) {
return ResultGenerator.genFailResult("账号或密码错误!");
}
/* 校验用户状态 */
if (!queryUser.getStatus().equals(INIT_USER_STATUS)) {
return ResultGenerator.genFailResult("账户已被停用! 请联系管理员!");
}
String token = JwtUtil.encode(queryUser, TOKEN_INIT_TIME);
userVO.setToken(token);
return ResultGenerator.genSuccessResult(userVO);
}
/**
* 获取用户
*
* @param userLoginDTO 登录信息
* @return User
*/
private User getUser(UserLoginDTO userLoginDTO) {
User queryUser = userService.findByUsername(userLoginDTO.getUsername());
return queryUser;
}
}
反解析token: 这里客户端每次请求都将token放到heard中
private final HttpServletRequest httpServletRequest;
@Autowired
public UserServiceImpl(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
@Override
public User decodeUser() {
String token = this.httpServletRequest.getHeader("token");
if (StringUtils.isBlank(token)) {
return null;
}
return JwtUtil.decode(token, User.class);
}