0、准備工作
0-1運行環境
- jdk1.8
- maven
- 一個能支持以上兩者的代碼編輯器,作者使用的是IDEA。
0-2知識儲備
- 對SpringBoot框架有所了解
- 對token的定義有了解
- 本案例簡單,代碼注釋較少,有不明白的地方,或者不正確的地方,歡迎聯系作者本人或留言。
1、設計思路
1-1 項目結構
本案例模擬用戶登錄/注冊后,服務器返回一個token用於用戶后續操作。
/controller/HelloController.java:測試token的接口
/controller/UserController.java:用戶注冊/登錄的接口
/entity/User.java:用戶實體類,屬性有 用戶ID,用戶名,密碼,郵箱,上次登錄時間。
/repository/UserRepository:實現SpringData接口的數據庫操作類,可以很方便的進行CRUD等操作。
/Reponse/UserResponse.java:接口返回數據的實體類。
/util/Constants.java:存放常量的工具類。
/util/JwtUtil.java:Jwt工具類,可以生成、解析Jwt。
1-2 實現難點
- 對Jwt的理解,可以參考http://blog.leapoahead.com/2015/09/06/understanding-jwt/
- JwtUtil工具類的開發
2 具體實現
2-1 JwtUtil.java
@Component
public class JwtUtil {
private static UserRepository userRepository;
@Autowired
public JwtUtil(UserRepository userRepository) {
JwtUtil.userRepository = userRepository;
}
public static final long EXPIRATION_TIME = 3600_000_000L; // 1000 hour
static final String SECRET = "ThisIsASecret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";
public static String generateToken(String username,Date generateTime) {
HashMap<String, Object> map = new HashMap<>();
//可以把任何安全的數據放到map里面
map.put("username", username);
map.put("generateTime",generateTime);
String jwt = Jwts.builder()
.setClaims(map)
.setExpiration(new Date(generateTime.getTime() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
return jwt;
}
/**
* @param token
* @return
*/
public static Map<String,Object> validateToken(String token) {
Map<String,Object> resp = new HashMap<String,Object>();
if (token != null) {
// 解析token
try {
Map<String, Object> body = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody();
String username = (String) (body.get("username"));
Date generateTime = new Date((Long)body.get("generateTime"));
if(username == null || username.isEmpty()){
resp.put("ERR_MSG",Constants.ERR_MSG_USERNAME_EMPTY);
return resp;
}
//賬號在別處登錄
if(userRepository.findByUsername(username).getLastLoginTime().after(generateTime)){
resp.put("ERR_MSG",Constants.ERR_MSG_LOGIN_DOU);
return resp;
}
resp.put("username",username);
resp.put("generateTime",generateTime);
return resp;
}catch (SignatureException | MalformedJwtException e) {
// TODO: handle exception
// don't trust the JWT!
// jwt 解析錯誤
resp.put("ERR_MSG",Constants.ERR_MSG_TOKEN_ERR);
return resp;
} catch (ExpiredJwtException e) {
// TODO: handle exception
// jwt 已經過期,在設置jwt的時候如果設置了過期時間,這里會自動判斷jwt是否已經過期,如果過期則會拋出這個異常,我們可以抓住這個異常並作相關處理。
resp.put("ERR_MSG",Constants.ERR_MSG_TOKEN_EXP);
return resp;
}
}else {
resp.put("ERR_MSG",Constants.ERR_MSG_TOKEN_EMPTY);
return resp;
}
}
}
2.2 UserController.java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserRepository userRepository;
//注冊或登錄
@RequestMapping("/login")
@Transactional
public UserResponse login(User user){
String username = user.getUsername();
String password = user.getPassword();
//TODO 檢驗參數的完整性
UserResponse userResponse = new UserResponse();
User tUser = userRepository.findByUsername(username);
//檢驗username是否存在
user.setLastLoginTime(new Date());
if(tUser!=null){
//檢驗密碼是否正確
if(!tUser.getPassword().equals(password)) {
userResponse.setErrorNum(Constants.ERR_NUM_PWD_ERR);
userResponse.setErrorMsg(Constants.ERR_MSG_PWD_ERR);
return userResponse;
}
userRepository.updateLastLoginTimeByUserName(user.getLastLoginTime(),username);
}else {
try {
tUser = userRepository.save(user);
} catch (Exception e) {
userResponse.setErrorNum(Constants.ERR_NUM_SERVER_ERR);
userResponse.setErrorMsg(Constants.ERR_MSG_SERVER_ERR);
return userResponse;
}
}
userResponse.setErrorNum(Constants.ERR_NUM_OK);
userResponse.setErrorMsg(Constants.ERR_MSG_OK);
userResponse.setUserName(username);
userResponse.setUserId(tUser.getId());
userResponse.setToken(JwtUtil.generateToken(username,user.getLastLoginTime()));
return userResponse;
}
}
2.3 HelloController.java
@RestController
public class HelloController {
@RequestMapping("/hello")
public Map login(HttpServletRequest request){
String token = request.getParameter("token");
return JwtUtil.validateToken(token);
}
}
2.4測試
用戶登錄后,會返回一串token,咱們可以用token進行下一步請求:
當我再次訪問登錄接口之后,卻用舊的token訪問測試接口時:
3 總結
上面是貼出的主要代碼,完整的請下載demo包,有不明白的地方請在下方評論,或者聯系郵箱yaoyunxiaoli@163.com。
我是妖雲小離,這是我第二次在Demo大師上發文章,感謝閱讀。
基於JWT的Token開發案例
注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權