溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JWT和Session的區(qū)別在哪里

發(fā)布時間:2021-07-20 12:14:10 來源:億速云 閱讀:322 作者:chen 欄目:編程語言

本篇內(nèi)容主要講解“JWT和Session的區(qū)別在哪里”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“JWT和Session的區(qū)別在哪里”吧!


(一)前言

在前面一篇講分布式session的時候,有讀者問了一句用JWT不香嗎,連Redis服務器都省了,又可以實現(xiàn)分布式環(huán)境下的人員信息認證。正好我也還沒寫過關于JWT的文章,剛好在項目中又用過這項技術,今天就來分享一下。還是一樣,先理論后實踐,有問題評論一起頭腦風暴。

(二)什么是JWT

JWT全程JSON Web Token,用戶會話信息存儲在客戶端瀏覽器中,各方之間通過JSON對象安全傳輸信息,這些信息可以通過加密算法進行加密。

這樣描述可能比較難懂,簡單來講,JWT就是在你登陸的時候生成一串加密字符串token,在每次請求的時候都帶上這串token,服務器如果可以解密這段字符串,說明就是登陸狀態(tài)的。

我們一般會將非敏感數(shù)據(jù)加密生成token,如果服務器端需要用到人員信息,就解密取人員信息。

你會發(fā)現(xiàn),使用JWT直接就解決了分布式集群環(huán)境下的人員認證問題,因為生成的這串token在哪臺服務器上都可以解析出來。如下圖所示:

JWT和Session的區(qū)別在哪里

了解了原理之后,來講講實際的,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。

(三)JWT身份認證流程

看完上面的內(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

JWT和Session的區(qū)別在哪里

于是先訪問登陸接口:http://localhost:8189/sso/jwtlogin

JWT和Session的區(qū)別在哪里

返回了具體的tokenHead和token實際值。 將這個token放進header里,再次訪問index接口:

JWT和Session的區(qū)別在哪里

操作成功,并且可以取到用戶信息。

(六)JWT和Session的對比

前面一篇文章我用了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ù)學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI