本篇內(nèi)容主要講解“JWT和Session的區(qū)別在哪里”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“JWT和Session的區(qū)別在哪里”吧!
為梅列等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及梅列網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都網(wǎng)站建設(shè)、做網(wǎng)站、梅列網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
在前面一篇講分布式session的時(shí)候,有讀者問(wèn)了一句用JWT不香嗎,連redis服務(wù)器都省了,又可以實(shí)現(xiàn)分布式環(huán)境下的人員信息認(rèn)證。正好我也還沒(méi)寫(xiě)過(guò)關(guān)于JWT的文章,剛好在項(xiàng)目中又用過(guò)這項(xiàng)技術(shù),今天就來(lái)分享一下。還是一樣,先理論后實(shí)踐,有問(wèn)題評(píng)論一起頭腦風(fēng)暴。
JWT全程JSON Web Token,用戶會(huì)話信息存儲(chǔ)在客戶端瀏覽器中,各方之間通過(guò)JSON對(duì)象安全傳輸信息,這些信息可以通過(guò)加密算法進(jìn)行加密。
這樣描述可能比較難懂,簡(jiǎn)單來(lái)講,JWT就是在你登陸的時(shí)候生成一串加密字符串token,在每次請(qǐng)求的時(shí)候都帶上這串token,服務(wù)器如果可以解密這段字符串,說(shuō)明就是登陸狀態(tài)的。
我們一般會(huì)將非敏感數(shù)據(jù)加密生成token,如果服務(wù)器端需要用到人員信息,就解密取人員信息。
你會(huì)發(fā)現(xiàn),使用JWT直接就解決了分布式集群環(huán)境下的人員認(rèn)證問(wèn)題,因?yàn)樯傻倪@串token在哪臺(tái)服務(wù)器上都可以解析出來(lái)。如下圖所示:
了解了原理之后,來(lái)講講實(shí)際的,JWT的數(shù)據(jù)包含三個(gè)部分:
1、Header 2、Payload 3、Signature
三者通過(guò)"."組合在一起,經(jīng)過(guò)加密后生成一段token:
Header.Payload.Signature
下面是我生成的一段token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZheXoiLCJjcmVhdGVkYXRlIjoxNjExNzU1MDIxNjk1LCJpZCI6MSwiZXhwIjoxNjEyMzU5ODIxLCJ1c2VyTGV2ZWxJZCI6bnVsbH0.ahFWQ_BJ1WNWp9GnlTrSNThVa3i3dydzcaNxLmPb7HI
Header用來(lái)描述JWT元數(shù)據(jù),包含兩個(gè)數(shù)據(jù),其中alg表示簽名的算法,默認(rèn)HS256,typ屬性表示令牌類型,這里就是JWT。上面生成的token中第一個(gè)“.”之前的數(shù)據(jù)base64解碼后就是下面的json
{ alg : "HS256", typ : "JWT" }
Payload用來(lái)以JSON格式記錄用戶信息,這里的用戶信息可以自定義,上面第一個(gè)“.”和第二個(gè)“.”之間的數(shù)據(jù)base64解碼后就是下面的json
{ "sub":"javayz", "createdate":1611755021695, "id":1, "exp":1612359821, "userLevelId":null }
Signature存放加密串,通過(guò)指定算法對(duì)Header和Payload加鹽加密,各部分通過(guò)“.”分割。組成了token。
看完上面的內(nèi)容,我相信你對(duì)JWT的認(rèn)證已經(jīng)有認(rèn)識(shí)了,其身份認(rèn)證的流程如下所示:
1、用戶輸入用戶名和密碼登陸
2、服務(wù)器端校驗(yàn)用戶名和密碼是否正確,如果正確就返回token給客戶端
3、客戶端將token存放在cookie或者local storage中
4、客戶端后續(xù)的每次請(qǐng)求,都要帶上這個(gè)token
5、服務(wù)器通過(guò)token判斷是哪個(gè)用戶
接下通過(guò)代碼模擬JWT的認(rèn)證實(shí)現(xiàn),我寫(xiě)這段代碼時(shí)的環(huán)境為springBoot2.4.2,引入JWT依賴:
io.jsonwebtoken jjwt 0.9.0
在配置文件中配置一些接下來(lái)會(huì)用到的屬性:
jwt: tokenHeader: Authorization secret: javayz expiration: 604800 tokenHead: Bearer
寫(xiě)個(gè)類來(lái)讀取這些參數(shù):
@Data @ConfigurationProperties(prefix = "jwt") @Component public class JwtProperties { private String tokenHeader; private String secret; private Long expiration; private String tokenHead; }
編寫(xiě)JWT工具類,實(shí)現(xiàn)生成token和解碼token的功能:
public class JwtKit { @Autowired private JwtProperties jwtProperties; /** * 創(chuàng)建token * @param user * @return */ public String generateJwtToken(User user){ //所有的用戶數(shù)據(jù)放在claims中 Mapclaims=new HashMap<>(); claims.put("sub",user.getUsername()); claims.put("createdate",new Date()); claims.put("id",user.getId()); claims.put("userLevelId",user.getLevelId()); return Jwts.builder() .setClaims(claims) .setHeaderParam("typ", "JWT") .setExpiration(new Date(System.currentTimeMillis()+jwtProperties.getExpiration()*1000)) .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret()) .compact(); } /** * 解碼token * @param jwtToken * @return */ public Claims parseJwtToken(String jwtToken){ Claims claims=null; try { claims=Jwts.parser() .setSigningKey(jwtProperties.getSecret()) .parseClaimsJws(jwtToken) .getBody(); }catch (ExpiredJwtException e){ throw new MyException("JWTtoken過(guò)期"); }catch (UnsupportedJwtException e){ throw new MyException("JWTtoken格式不支持"); }catch (MalformedJwtException e){ throw new MyException("無(wú)效的token"); }catch (SignatureException e){ throw new MyException("無(wú)效的token"); }catch (IllegalArgumentException e){ throw new MyException("無(wú)效的token"); } return claims; } }
再將這個(gè)工具類注入到Bean容器中:
@Configuration public class JwtConfiguration { @Bean public JwtKit jwtKit(){ return new JwtKit(); } }
然后就可以愉快地使用了
@RestController @RequestMapping("/sso") public class UserController extends BaseController { @Autowired private UserService userService; @Autowired private JwtKit jwtKit; @Autowired private JwtProperties jwtProperties; @PostMapping("jwtlogin") public CommonResult jwtLogin(@RequestParam String username,@RequestParam String password){ User user = userService.login(username, password); if (user!=null){ Mapmap=new HashMap<>(); String token=jwtKit.generateJwtToken(user); map.put("tokenHead",jwtProperties.getTokenHead()); map.put("token",token); return new CommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),map); } return new CommonResult(ResponseCode.USER_NOT_EXISTS.getCode(),ResponseCode.USER_NOT_EXISTS.getMsg(),""); } }
登陸成功的話將token的開(kāi)頭和實(shí)際token值返回給后端。
接著編寫(xiě)攔截器,攔截除/sso/*外的其他請(qǐng)求,這些請(qǐng)求是需要登陸才可以訪問(wèn)的。
@Configuration public class IntercepterConfiguration implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { List list=new ArrayList(); list.add("/sso/**"); registry.addInterceptor(authInterceptorHandler()) .addPathPatterns("/**") .excludePathPatterns(list); } @Bean public AuthInterceptorHandler authInterceptorHandler(){ return new AuthInterceptorHandler(); } }
接著是對(duì)攔截的處理,首先判斷header中是否有key為Authorization的數(shù)據(jù),如果沒(méi)有,說(shuō)明未登陸,將未登錄的結(jié)果返回出去。如果header中存在key為Authorization的數(shù)據(jù),則先判斷是否以Bearer開(kāi)頭(這個(gè)可以自定義),如果是的話說(shuō)明登陸了,就直接返回true不攔截。
@Slf4j public class AuthInterceptorHandler implements HandlerInterceptor { @Autowired private JwtKit jwtKit; @Autowired private JwtProperties jwtProperties; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("進(jìn)入攔截器"); String aa = request.getHeader("aa"); String authorization =request.getHeader(jwtProperties.getTokenHeader()); log.info("Authorization"+authorization); //如果不為空,說(shuō)明head里存了數(shù)據(jù), if(StringUtils.isNotEmpty(authorization) && authorization.startsWith(jwtProperties.getTokenHead())){ String authToken = authorization.substring(jwtProperties.getTokenHead().length()); Claims claims=null; try { claims=jwtKit.parseJwtToken(authToken); if (claims!=null){ return true; } }catch (MyException e){ log.info(e.getMessage()+":"+authToken); } } response.setHeader("Content-Type","application/json"); response.setCharacterEncoding("UTF-8"); String result = new ObjectMapper().writeValueAsString(new CommonResult(ResponseCode.NEED_LOGIN.getCode(), ResponseCode.NEED_LOGIN.getMsg(), "")); response.getWriter().println(result); return false; } }
最后寫(xiě)一個(gè)測(cè)試類:
@RestController public class IndexController extends BaseController{ @Autowired private JwtProperties jwtProperties; @Autowired private JwtKit jwtKit; @RequestMapping(value = "/index",method = RequestMethod.GET) public CommonResult index(){ String authorization = getRequest().getHeader(jwtProperties.getTokenHeader()); String authToken = authorization.substring(jwtProperties.getTokenHead().length()); Claims claims=jwtKit.parseJwtToken(authToken); return new CommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),claims.get("sub")); } }
首先我直接訪問(wèn)http://localhost:8189/index,返回結(jié)果為登陸失效,因?yàn)闆](méi)有傳header
于是先訪問(wèn)登陸接口:http://localhost:8189/sso/jwtlogin
返回了具體的tokenHead和token實(shí)際值。 將這個(gè)token放進(jìn)header里,再次訪問(wèn)index接口:
操作成功,并且可以取到用戶信息。
前面一篇文章我用了Session實(shí)現(xiàn)了認(rèn)證的功能,但是需要額外的Redis服務(wù)器才可以實(shí)現(xiàn)分布式的認(rèn)證。而使用JWT不需要額外的服務(wù)器,它是把token放在header中的。
但是JWT同樣存在缺點(diǎn),最明顯的就是這段token無(wú)法讓他手動(dòng)失效,生成token后,就算注銷登陸,token依然是有效的。
第二點(diǎn)缺點(diǎn)是人員信息是base64后的數(shù)據(jù),相當(dāng)于只要拿到就可以被使用,因此JWT只能傳輸非敏感的人員數(shù)據(jù)。
第三點(diǎn)缺點(diǎn)是由于每次請(qǐng)求都需要在header中攜帶token信息,增大了帶寬的壓力。別覺(jué)得一個(gè)請(qǐng)求的header就多占用了那么一點(diǎn)帶寬,如果是一萬(wàn)個(gè)或者是十萬(wàn)個(gè)請(qǐng)求呢?帶寬的資源是很珍貴的。
到此,相信大家對(duì)“JWT和Session的區(qū)別在哪里”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!