您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“JWT和Session的區(qū)別在哪里”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“JWT和Session的區(qū)別在哪里”吧!
在前面一篇講分布式session的時候,有讀者問了一句用JWT不香嗎,連Redis服務器都省了,又可以實現(xiàn)分布式環(huán)境下的人員信息認證。正好我也還沒寫過關于JWT的文章,剛好在項目中又用過這項技術,今天就來分享一下。還是一樣,先理論后實踐,有問題評論一起頭腦風暴。
JWT全程JSON Web Token,用戶會話信息存儲在客戶端瀏覽器中,各方之間通過JSON對象安全傳輸信息,這些信息可以通過加密算法進行加密。
這樣描述可能比較難懂,簡單來講,JWT就是在你登陸的時候生成一串加密字符串token,在每次請求的時候都帶上這串token,服務器如果可以解密這段字符串,說明就是登陸狀態(tài)的。
我們一般會將非敏感數(shù)據(jù)加密生成token,如果服務器端需要用到人員信息,就解密取人員信息。
你會發(fā)現(xiàn),使用JWT直接就解決了分布式集群環(huán)境下的人員認證問題,因為生成的這串token在哪臺服務器上都可以解析出來。如下圖所示:
了解了原理之后,來講講實際的,JWT的數(shù)據(jù)包含三個部分:
1、Header 2、Payload 3、Signature
三者通過"."組合在一起,經(jīng)過加密后生成一段token:
Header.Payload.Signature
下面是我生成的一段token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZheXoiLCJjcmVhdGVkYXRlIjoxNjExNzU1MDIxNjk1LCJpZCI6MSwiZXhwIjoxNjEyMzU5ODIxLCJ1c2VyTGV2ZWxJZCI6bnVsbH0.ahFWQ_BJ1WNWp9GnlTrSNThVa3i3dydzcaNxLmPb7HI
Header用來描述JWT元數(shù)據(jù),包含兩個數(shù)據(jù),其中alg表示簽名的算法,默認HS256,typ屬性表示令牌類型,這里就是JWT。上面生成的token中第一個“.”之前的數(shù)據(jù)base64解碼后就是下面的json
{ alg : "HS256", typ : "JWT" }
Payload用來以JSON格式記錄用戶信息,這里的用戶信息可以自定義,上面第一個“.”和第二個“.”之間的數(shù)據(jù)base64解碼后就是下面的json
{ "sub":"javayz", "createdate":1611755021695, "id":1, "exp":1612359821, "userLevelId":null }
Signature存放加密串,通過指定算法對Header和Payload加鹽加密,各部分通過“.”分割。組成了token。
看完上面的內(nèi)容,我相信你對JWT的認證已經(jīng)有認識了,其身份認證的流程如下所示:
1、用戶輸入用戶名和密碼登陸
2、服務器端校驗用戶名和密碼是否正確,如果正確就返回token給客戶端
3、客戶端將token存放在cookie或者local storage中
4、客戶端后續(xù)的每次請求,都要帶上這個token
5、服務器通過token判斷是哪個用戶
接下通過代碼模擬JWT的認證實現(xiàn),我寫這段代碼時的環(huán)境為springBoot2.4.2,引入JWT依賴:
<!--JWT依賴--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
在配置文件中配置一些接下來會用到的屬性:
jwt: tokenHeader: Authorization secret: javayz expiration: 604800 tokenHead: Bearer
寫個類來讀取這些參數(shù):
@Data @ConfigurationProperties(prefix = "jwt") @Component public class JwtProperties { private String tokenHeader; private String secret; private Long expiration; private String tokenHead; }
編寫JWT工具類,實現(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中 Map<String,Object> claims=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過期"); }catch (UnsupportedJwtException e){ throw new MyException("JWTtoken格式不支持"); }catch (MalformedJwtException e){ throw new MyException("無效的token"); }catch (SignatureException e){ throw new MyException("無效的token"); }catch (IllegalArgumentException e){ throw new MyException("無效的token"); } return claims; } }
再將這個工具類注入到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){ Map<String,Object> map=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的開頭和實際token值返回給后端。
接著編寫攔截器,攔截除/sso/*外的其他請求,這些請求是需要登陸才可以訪問的。
@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(); } }
接著是對攔截的處理,首先判斷header中是否有key為Authorization的數(shù)據(jù),如果沒有,說明未登陸,將未登錄的結(jié)果返回出去。如果header中存在key為Authorization的數(shù)據(jù),則先判斷是否以Bearer開頭(這個可以自定義),如果是的話說明登陸了,就直接返回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("進入攔截器"); String aa = request.getHeader("aa"); String authorization =request.getHeader(jwtProperties.getTokenHeader()); log.info("Authorization"+authorization); //如果不為空,說明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; } }
最后寫一個測試類:
@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")); } }
首先我直接訪問http://localhost:8189/index,返回結(jié)果為登陸失效,因為沒有傳header
于是先訪問登陸接口:http://localhost:8189/sso/jwtlogin
返回了具體的tokenHead和token實際值。 將這個token放進header里,再次訪問index接口:
操作成功,并且可以取到用戶信息。
前面一篇文章我用了Session實現(xiàn)了認證的功能,但是需要額外的Redis服務器才可以實現(xiàn)分布式的認證。而使用JWT不需要額外的服務器,它是把token放在header中的。
但是JWT同樣存在缺點,最明顯的就是這段token無法讓他手動失效,生成token后,就算注銷登陸,token依然是有效的。
第二點缺點是人員信息是base64后的數(shù)據(jù),相當于只要拿到就可以被使用,因此JWT只能傳輸非敏感的人員數(shù)據(jù)。
第三點缺點是由于每次請求都需要在header中攜帶token信息,增大了帶寬的壓力。別覺得一個請求的header就多占用了那么一點帶寬,如果是一萬個或者是十萬個請求呢?帶寬的資源是很珍貴的。
到此,相信大家對“JWT和Session的區(qū)別在哪里”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。