一,傳統Cookie+Session與JWT對比
1, 在傳統的用戶登錄認證中,因為http是無狀態的,所以都是采用session
方式。用戶登錄成功,服務端會保證一個session
,當然會給客戶端一個sessionId
,客戶端會把sessionId
保存在cookie
中,每次請求都會攜帶這個sessionId
。
2,cookie+session
這種模式通常是保存在內存中,而且服務從單服務到多服務會面臨的session
共享問題,隨着用戶量的增多,開銷就會越大。而JWT
不是這樣的,只需要服務端生成token,客戶端保存這個token
,每次請求攜帶這個token
,服務端認證解析就可。
3, JWT
方式校驗方式更加簡單便捷化,無需通過redis
緩存,而是直接根據token
取出保存的用戶信息,以及對token
可用性校驗,單點登錄,驗證token
更為簡單。
二,springboot集成jwt
1,jwt的整合依賴
<!-- JWT依賴 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2,jwt的自定義配置
server:
port: 8080
spring:
application:
name: springboot-jwt
# 自定義配置
config:
jwt:
# 加密密鑰
secret: abcdefg1234567
# token有效時長
expire: 3600
# header 名稱
header: token
3,編寫JwtConfig
JwtConfig
負責
- 生成
token
- 獲取
token
中的注冊信息 - 驗證
token
是否過期失效 - 獲取
token
失效時間 - 從
token
中獲取用戶名 - 獲取
jwt
發布時間
package com.ftx.jwt.config;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName JwtConfig.java
* @Description TODO
* @createTime 2020年06月22日 15:43:00
*/
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* JWT的token,區分大小寫
*/
@ConfigurationProperties(prefix = "config.jwt", ignoreUnknownFields = true)
@Component
public class JwtConfig {
private String secret;
private long expire;
private String header;
/**
* 生成token
* @param subject
* @return
*/
public String createToken (String subject){
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expire * 1000);//過期時間
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(subject)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 獲取token中注冊信息
* @param token
* @return
*/
public Claims getTokenClaim (String token) {
try {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}catch (Exception e){
// e.printStackTrace();
return null;
}
}
/**
* 驗證token是否過期失效
* @param expirationTime
* @return
*/
public boolean isTokenExpired (Date expirationTime) {
return expirationTime.before(new Date());
}
/**
* 獲取token失效時間
* @param token
* @return
*/
public Date getExpirationDateFromToken(String token) {
return getTokenClaim(token).getExpiration();
}
/**
* 獲取用戶名從token中
*/
public String getUsernameFromToken(String token) {
return getTokenClaim(token).getSubject();
}
/**
* 獲取jwt發布時間
*/
public Date getIssuedAtDateFromToken(String token) {
return getTokenClaim(token).getIssuedAt();
}
// --------------------- getter & setter ---------------------
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}
4,配置攔截器
在攔截器中對token
進行驗證。
package com.ftx.jwt.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName TokenInterceptor.java
* @Description TODO
* @createTime 2020年06月22日 15:47:00
*/
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
@Resource
private JwtConfig jwtConfig ;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws SignatureException {
/** 地址過濾 */
String uri = request.getRequestURI() ;
if (uri.contains("/login")){
return true ;
}
/** Token 驗證 */
String token = request.getHeader(jwtConfig.getHeader());
if(StringUtils.isEmpty(token)){
token = request.getParameter(jwtConfig.getHeader());
}
if(StringUtils.isEmpty(token)){
throw new SignatureException(jwtConfig.getHeader()+ "不能為空");
}
Claims claims = null;
try{
claims = jwtConfig.getTokenClaim(token);
if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){
throw new SignatureException(jwtConfig.getHeader() + "失效,請重新登錄。");
}
}catch (Exception e){
throw new SignatureException(jwtConfig.getHeader() + "失效,請重新登錄。");
}
//該token可用,放行
return true;
}
}
注冊攔截器到SpringMvc
package com.ftx.jwt.config;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName WebConfig.java
* @Description TODO
* @createTime 2020年06月22日 15:53:00
*/
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private TokenInterceptor tokenInterceptor ;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
}
}
5,編寫測試controller接口
package com.ftx.jwt.controller;
import com.alibaba.fastjson.JSONObject;
import com.ftx.jwt.config.JwtConfig;
import com.ftx.jwt.util.ResultTool;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName TokenController.java
* @Description TODO
* @createTime 2020年06月22日 16:05:00
*/
@RestController
public class TokenController {
@Resource
private JwtConfig jwtConfig ;
/**
* 用戶登錄接口
* @param userName
* @param passWord
* @return
*/
@PostMapping("/login")
public JSONObject login (@RequestParam("userName") String userName,
@RequestParam("passWord") String passWord){
JSONObject json = new JSONObject();
/** 驗證userName,passWord和數據庫中是否一致,如不一致,直接return ResultTool.errer(); 【這里省略該步驟】*/
// 這里模擬通過用戶名和密碼,從數據庫查詢userId
// 這里把userId轉為String類型,實際開發中如果subject需要存userId,則可以JwtConfig的createToken方法的參數設置為Long類型
String userId = 5 + "";
String token = jwtConfig.createToken(userId) ;
if (!StringUtils.isEmpty(token)) {
json.put("token",token) ;
}
return ResultTool.success(json) ;
}
/**
* 需要 Token 驗證的接口
*/
@PostMapping("/info")
public JSONObject info (){
return ResultTool.success("info") ;
}
/**
* 根據請求頭的token獲取userId
* @param request
* @return
*/
@GetMapping("/getUserInfo")
public JSONObject getUserInfo(HttpServletRequest request){
String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token"));
return ResultTool.success(usernameFromToken) ;
}
}
用PostMan
測試工具測試一下,訪問登錄接口,當對賬號密碼驗證通過時,則返回一個token
給客戶端
說明:token
是在請求頭處,request.getHeader()
得到token
。
當直接去訪問info
接口時,會返回token
為空的異常
當在請求頭加上正確token
時,則攔截器驗證通過,可以正常訪問到接口
當在請求頭加入一個錯誤token
,則會返回token
失效的異常
接下來測試一下獲取用戶信息,因為這里存的subject
為userId
,所以直接返回上面寫死的假數據5
以上內容參考自https://blog.csdn.net/akiranicky/article/details/99307713
三,知識點概述
1,@Resource注解
1, @Autowired
與@Resource
都可以用來裝配bean. 都可以寫在字段上,或寫在setter
方法上。
2, @Autowired
默認按類型裝配(這個注解是屬於spring
的),默認情況下必須要求依賴對象必須存在,如果要允許null
值,可以設置它的required
屬性為false
,如:@Autowired(required=false)
3,@Resource
(這個注解屬於J2EE
的),默認按照名稱進行裝配,名稱可以通過name
屬性進行指定,如果沒有指定name
屬性,當注解寫在字段上時,默認取字段名進行安裝名稱查找,如果注解寫在setter
方法上默認取屬性名進行裝配。當找不到與名稱匹配的bean
時才按照類型進行裝配。但是需要注意的是,如果name
屬性一旦指定,就只會按照名稱進行裝配。
2,@ConfigurationProperties注解
@ConfigurationProperties
:告訴SpringBoot
將本類中的所有屬性和配置文件中相關的配置進行綁定;prefix = "xxx"
:配置文件中哪個下面的所有屬性進行一一映射