一、JWT简介
1、JWT的全称是JSON WEB TOKEN
- 基于token进行身份验证的方案;
- 是一个字符串,由hearder、payload、signature组成;
- 具备安全、自包含、紧揍等特点。
2、JWT优点
- 安全性高,防止token被伪造和篡改;
- 自包含,减少存储的开销;
- 跨语言,支持多种语言的实现;
- 支持过期,发布者校验。
3、不足
- 消息体可以被base64解密成明文;
- 不适合存放大量信息;
- 无法作废未过期的JWT;
二、流程图
三、具体实现
1、引入jwt的依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.1.0</version>
</dependency>
2、JwtHelper工具类,主要用于生成和校验token
package com.mooc.house.user.utils;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;
import org.apache.commons.lang3.time.DateUtils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.collect.Maps;
public class JwtHelper {
private static final String SECRET = "session_secret";
private static final String ISSUER = "mooc_user";
public static String genToken(Map<String, String> claims){
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTCreator.Builder builder = JWT.create().withIssuer(ISSUER).withExpiresAt(DateUtils.addDays(new Date(), 1));
claims.forEach((k,v) -> builder.withClaim(k, v));
return builder.sign(algorithm).toString();
} catch (IllegalArgumentException | UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static Map<String, String> verifyToken(String token) {
Algorithm algorithm = null;
try {
algorithm = Algorithm.HMAC256(SECRET);
} catch (IllegalArgumentException | UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
DecodedJWT jwt = verifier.verify(token);
Map<String, Claim> map = jwt.getClaims();
Map<String, String> resultMap = Maps.newHashMap();
map.forEach((k,v) -> resultMap.put(k, v.asString()));
return resultMap;
}
}
3、User类
package com.house.user.model;
import java.util.Date;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.annotation.JSONField;
public class User {
private Long id;
private String name;
private String phone;
private String email;
private String aboutme;
private String passwd;
private String confirmPasswd;
private Integer type;
private Date createTime;
private Integer enable;
private String avatar;
@JSONField(deserialize=false,serialize=false)
private MultipartFile avatarFile;
private String newPassword;
private String key;
private Long agencyId;
private String token;
private String enableUrl;
private String agencyName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAboutme() {
return aboutme;
}
public void setAboutme(String aboutme) {
this.aboutme = aboutme;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public String getAgencyName() {
return agencyName;
}
public void setAgencyName(String agencyName) {
this.agencyName = agencyName;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getConfirmPasswd() {
return confirmPasswd;
}
public void setConfirmPasswd(String confirmPasswd) {
this.confirmPasswd = confirmPasswd;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Integer getEnable() {
return enable;
}
public void setEnable(Integer enable) {
this.enable = enable;
}
public MultipartFile getAvatarFile() {
return avatarFile;
}
public String getEnableUrl() {
return enableUrl;
}
public void setEnableUrl(String enableUrl) {
this.enableUrl = enableUrl;
}
public void setAvatarFile(MultipartFile avatarFile) {
this.avatarFile = avatarFile;
}
public String getNewPassword() {
return newPassword;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
public Long getAgencyId() {
return agencyId;
}
public void setAgencyId(Long agencyId) {
this.agencyId = agencyId;
}
}
4、Controller实现,
// 登录
@RequestMapping("auth")
public RestResponse<User> auth(@RequestBody User user){
User finalUser = userService.auth(user.getEmail(),user.getPasswd());
return RestResponse.success(finalUser);
}
// 鉴权
@RequestMapping("get")
public RestResponse<User> getUser(String token){
User finalUser = userService.getLoginedUserByToken(token);
return RestResponse.success(finalUser);
}
// 退出
@RequestMapping("logout")
public RestResponse<Object> logout(String token){
userService.invalidate(token);
return RestResponse.success();
}
5、Service
public User auth(String email, String passwd) {
if (StringUtils.isBlank(email) || StringUtils.isBlank(passwd)) {
throw new UserException(UserException.Type.USER_AUTH_FAIL,"User Auth Fail");
}
User user = new User();
user.setEmail(email);
user.setPasswd(HashUtils.encryPassword(passwd));
user.setEnable(1);
List<User> list = getUserByQuery(user);
if (!list.isEmpty()) {
User retUser = list.get(0);
onLogin(retUser);
return retUser;
}
throw new UserException(UserException.Type.USER_AUTH_FAIL,"User Auth Fail");
}
private void onLogin(User user) {
String token = JwtHelper.genToken(ImmutableMap.of("email", user.getEmail(), "name", user.getName(),"ts", Instant.now().getEpochSecond()+""));
renewToken(token,user.getEmail());
user.setToken(token);
}
private String renewToken(String token, String email) {
redisTemplate.opsForValue().set(email, token);
redisTemplate.expire(email, 30, TimeUnit.MINUTES);
return token;
}
private User getUserByEmail(String email) {
User user = new User();
user.setEmail(email);
List<User> list = getUserByQuery(user);
if (!list.isEmpty()) {
return list.get(0);
}
throw new UserException(UserException.Type.USER_NOT_FOUND,"User not found for " + email);
}
public User getLoginedUserByToken(String token) {
Map<String, String> map = null;
try {
map = JwtHelper.verifyToken(token);
} catch (Exception e) {
throw new UserException(UserException.Type.USER_NOT_LOGIN,"User not login");
}
String email = map.get("email");
Long expired = redisTemplate.getExpire(email);
if (expired > 0L) {
renewToken(token, email);
User user = getUserByEmail(email);
user.setToken(token);
return user;
}
throw new UserException(UserException.Type.USER_NOT_LOGIN,"user not login");
}
public void invalidate(String token) {
Map<String, String> map = JwtHelper.verifyToken(token);
redisTemplate.delete(map.get("email"));
}
6、UserExeption
package com.house.user.exception;
public class UserException extends RuntimeException implements WithTypeException {
private static final long serialVersionUID = 1L;
private Type type;
public Type type(){
return type;
}
public enum Type{
WRONG_PAGE_NUM,LACK_PARAMTER,USER_NOT_LOGIN,USER_NOT_FOUND,USER_AUTH_FAIL;
}
public UserException(String message){
super(message);
type = Type.LACK_PARAMTER;
}
public UserException(Type type,String message){
super(message);
this.type = type;
}
}
7、Postman测试
测试登录,返回token
测试鉴权
使用错误的token测试:
使用正确的token测试:
测试退出: