SpringBoot框架集成token實(shí)現(xiàn)登錄校驗(yàn)功能
簡介
公司新項(xiàng)目,需要做移動(dòng)端(Android和IOS),登錄模塊,兩個(gè)移動(dòng)端人員提出用token來校驗(yàn)登錄狀態(tài),一臉懵懵的,沒做過,對(duì)于token的基本定義都模棱兩可,然后查資料查查查,最終OK完成,寫篇博客記錄一下
思路:
1、基于session登錄
基于session的登錄(有回話狀態(tài)),用戶攜帶賬號(hào)密碼發(fā)送請(qǐng)求向服務(wù)器,服務(wù)器進(jìn)行判斷,成功后將用戶信息放入session,用戶發(fā)送請(qǐng)求判斷session中是否有用戶信息,有的話放行,沒有的話進(jìn)行攔截,但是考慮到時(shí)App產(chǎn)品,牽扯到要判斷用戶的session,需要sessionID,還要根據(jù)sessionId來獲取session,在進(jìn)行校驗(yàn),還有sessionId的一個(gè)存儲(chǔ)等等,所以沒考慮用session
2、基于token登錄
基于token的登錄,是不存在回話狀態(tài),大概思路,在用戶初次等路的時(shí)候,校驗(yàn)用戶賬號(hào)密碼,成功后給其生成一個(gè)token,token=用戶ID+時(shí)間戳+過期時(shí)間+一個(gè)自己平臺(tái)規(guī)定的簽名,使用jjwt生成一個(gè)令牌,然后對(duì)其進(jìn)行存庫,用戶每次訪問接口,都會(huì)在頭部Headers中帶上token,后來攔截器對(duì)其進(jìn)行攔截,如果token為空或錯(cuò)誤則讓其登錄,如果有token,獲取token進(jìn)行其解析,取出里面的用戶ID,根據(jù)用戶ID查詢數(shù)據(jù)庫中所存token,判斷其是否正確,正確使其登錄,錯(cuò)誤則提示登錄,大致思路就是這樣,下面開始代碼
導(dǎo)入jar包
<!-- 生成token --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
開發(fā)步驟
1、創(chuàng)建token庫

2、創(chuàng)建token實(shí)體類
package com.prereadweb.user.entity;
import lombok.Data;
/**
* @Description: Token實(shí)體類
* @author: Yangxf
* @date: 2019/4/14 12:53
*/
@Data
public class TokenEntity {
/* tokenId */
private Long id;
/* 用戶ID */
private Long userId;
/* 刷新時(shí)間 */
private int buildTime;
/* token */
private String token;
}
3、編寫token的三個(gè)方法(添加、查詢、修改)
package com.prereadweb.user.mapper;
import com.prereadweb.user.entity.TokenEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* @Description: Token數(shù)據(jù)庫持久層接口
* @author: Yangxf
* @date: 2019/4/14 13:00
*/
@Mapper
public interface TokenMapper {
/* 添加token */
void addToken(TokenEntity token);
/* 修改token */
void updataToken(TokenEntity token);
/* 查詢token */
TokenEntity findByUserId(Long userId);
}
4、創(chuàng)建攔截器
package com.prereadweb.user.interceptor;
import com.prereadweb.user.entity.TokenEntity;
import com.prereadweb.user.mapper.TokenMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
/**
* @Description:攔截器
* @author: Yangxf
* @date: 2019/4/14 12:58
*/
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
protected TokenMapper tokenMapper;
//提供查詢
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {}
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {}
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
//此處為不需要登錄的接口放行
if (arg0.getRequestURI().contains("/login") || arg0.getRequestURI().contains("/register") || arg0.getRequestURI().contains("/error") || arg0.getRequestURI().contains("/static")) {
return true;
}
//權(quán)限路徑攔截
//PrintWriter resultWriter = arg1.getOutputStream();
// TODO: 有時(shí)候用PrintWriter 回報(bào) getWriter() has already been called for this response
//換成ServletOutputStream就OK了
arg1.setContentType("text/html;charset=utf-8");
ServletOutputStream resultWriter = arg1.getOutputStream();
final String headerToken=arg0.getHeader("token");
//判斷請(qǐng)求信息
if(null==headerToken||headerToken.trim().equals("")){
resultWriter.write("你沒有token,需要登錄".getBytes());
resultWriter.flush();
resultWriter.close();
return false;
}
//解析Token信息
try {
Claims claims = Jwts.parser().setSigningKey("preRead").parseClaimsJws(headerToken).getBody();
String tokenUserId=(String)claims.get("userId");
long iTokenUserId = Long.parseLong(tokenUserId);
//根據(jù)客戶Token查找數(shù)據(jù)庫Token
TokenEntity myToken= tokenMapper.findByUserId(iTokenUserId);
//數(shù)據(jù)庫沒有Token記錄
if(null==myToken) {
resultWriter.write("我沒有你的token?,需要登錄".getBytes());
resultWriter.flush();
resultWriter.close();
return false;
}
//數(shù)據(jù)庫Token與客戶Token比較
if( !headerToken.equals(myToken.getToken()) ){
resultWriter.print("你的token修改過?,需要登錄");
resultWriter.flush();
resultWriter.close();
return false;
}
//判斷Token過期
Date tokenDate= claims.getExpiration();
int overTime=(int)(new Date().getTime()-tokenDate.getTime())/1000;
if(overTime>60*60*24*3){
resultWriter.write("你的token過期了?,需要登錄".getBytes());
resultWriter.flush();
resultWriter.close();
return false;
}
} catch (Exception e) {
resultWriter.write("反正token不對(duì),需要登錄".getBytes());
resultWriter.flush();
resultWriter.close();
return false;
}
//最后才放行
return true;
}
}
5、配置攔截器
package com.prereadweb.user.config;
import com.prereadweb.user.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Description: 攔截器配置
* @author: Yangxf
* @date: 2019/4/14 13:09
*/
@Configuration
public class LoginConfiguration implements WebMvcConfigurer {
/**
* @Function: 這個(gè)方法才能在攔截器中自動(dòng)注入查詢數(shù)據(jù)庫的對(duì)象
* @author: YangXueFeng
* @Date: 2019/4/14 13:10
*/
@Bean
LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
/**
* @Function: 配置生成器:添加一個(gè)攔截器,攔截路徑為login以后的路徑
* @author: YangXueFeng
* @Date: 2019/4/14 13:10
*/
@Override
public void addInterceptors(InterceptorRegistry registry ){
registry.addInterceptor(loginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login", "/register", "/static");
}
}
6、登錄
controller層
@RequestMapping("/getlogin")
public Object login(@Param("..") LoginQueryForm loginForm) {
return userViewService.login(loginForm);
}
serrvice層
@Override
public Map<String, Object> login(LoginQueryForm loginForm) {
Map<String, Object> map = new HashMap<>();
//手機(jī)驗(yàn)證碼登錄
if(!Util.isEmpty(loginForm.getPhoneCode())) {
return phoneCodeLogin(loginForm, map);
}
//判斷用戶信息為空
if (Util.isEmpty(loginForm.getPhone()) || Util.isEmpty(loginForm.getLoginPwd())) {
return checkParameter(map);
}
//根據(jù)手機(jī)號(hào)查詢user對(duì)象
UserEntity user = userMapper.getUser(loginForm.getPhone());
//判斷用戶不存在
if (Util.isEmpty(user)) {
map.put("code", UserStatusEnum.USER_NON_EXISTENT.intKey());
map.put("msg", UserStatusEnum.USER_NON_EXISTENT.value());
return map;
}
/* 判斷密碼 */
if(!MD5Util.string2MD5(loginForm.getLoginPwd()).equals(user.getLoginPwd())){
map.put("code", UserStatusEnum.PWD_ERROR.intKey());
map.put("msg", UserStatusEnum.PWD_ERROR.value());
return map;
}
//根據(jù)數(shù)據(jù)庫的用戶信息查詢Token
return operateToKen(map, user, user.getId());
}
token操作
private Map<String, Object> operateToKen(Map<String, Object> map, UserEntity user, long userId) {
//根據(jù)數(shù)據(jù)庫的用戶信息查詢Token
TokenEntity token = tokenmapper.findByUserId(userId);
//為生成Token準(zhǔn)備
String TokenStr = "";
Date date = new Date();
int nowTime = (int) (date.getTime() / 1000);
//生成Token
TokenStr = creatToken(userId, date);
if (null == token) {
//第一次登陸
token = new TokenEntity();
token.setToken(TokenStr);
token.setBuildTime(nowTime);
token.setUserId(userId);
token.setId(Long.valueOf(IdUtils.getPrimaryKey()));
tokenmapper.addToken(token);
}else{
//登陸就更新Token信息
TokenStr = creatToken(userId, date);
token.setToken(TokenStr);
token.setBuildTime(nowTime);
tokenmapper.updataToken(token);
}
UserQueryForm queryForm = getUserInfo(user, TokenStr);
/* 將用戶信息存入session */
/*SessionContext sessionContext = SessionContext.getInstance();
HttpSession session = sessionContext.getSession();
httpSession.setAttribute("userInfo", user);*/
//返回Token信息給客戶端
successful(map);
map.put("data", queryForm);
return map;
}
生成token
private String creatToken(Long userId, Date date) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT") // 設(shè)置header
.setHeaderParam("alg", "HS256").setIssuedAt(date) // 設(shè)置簽發(fā)時(shí)間
.setExpiration(new Date(date.getTime() + 1000 * 60 * 60))
.claim("userId",String.valueOf(userId) ) // 設(shè)置內(nèi)容
.setIssuer("lws")// 設(shè)置簽發(fā)人
.signWith(signatureAlgorithm, "簽名"); // 簽名,需要算法和key
String jwt = builder.compact();
return jwt;
}
至此,token登錄OK
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringIOC?BeanDefinition的加載流程詳解
這篇文章主要為大家介紹了SpringIOC?BeanDefinition的加載流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Java編程實(shí)現(xiàn)基于用戶的協(xié)同過濾推薦算法代碼示例
這篇文章主要介紹了Java編程實(shí)現(xiàn)基于用戶的協(xié)同過濾推薦算法代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
在Mybatis中association標(biāo)簽多層嵌套的問題
這篇文章主要介紹了在Mybatis中association標(biāo)簽多層嵌套的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
仿京東平臺(tái)框架開發(fā)開放平臺(tái)(包含需求,服務(wù)端代碼,SDK代碼)
現(xiàn)在開放平臺(tái)越來越多了,下面針對(duì)仿京東開放平臺(tái)框架,封裝自己的開放平臺(tái),分享給大家。先感謝一下京東開放平臺(tái)的技術(shù)大佬們,下面從開放平臺(tái)需求,服務(wù)端代碼,SDK代碼三大塊進(jìn)行分享2021-06-06
G1垃圾回收器在并發(fā)場景調(diào)優(yōu)詳解
這篇文章主要為大家介紹了G1垃圾回收器在并發(fā)場景調(diào)優(yōu)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
IDEA 項(xiàng)目創(chuàng)建Mapper的xml文件的方法
這篇文章主要介紹了IDEA 項(xiàng)目創(chuàng)建Mapper的xml文件的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11

