激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - SpringBoot JWT實現token登錄刷新功能

SpringBoot JWT實現token登錄刷新功能

2022-01-21 00:57雨夜歸人93 Java教程

JWT本身是無狀態的,這點有別于傳統的session,不在服務端存儲憑證。這種特性使其在分布式場景,更便于擴展使用。接下來通過本文給大家分享SpringBoot JWT實現token登錄刷新功能,感興趣的朋友一起看看吧

1. 什么是JWT

Json web token (JWT) 是為了在網絡應用環境間傳遞聲明而執行的一種基于JSON的開放標準。簡答理解就是一個身份憑證,用于服務識別。
JWT本身是無狀態的,這點有別于傳統的session,不在服務端存儲憑證。這種特性使其在分布式場景,更便于擴展使用。

2. JWT組成部分

JWT有三部分組成,頭部(header),載荷(payload),是簽名(signature)。

  • 頭部

頭部主要聲明了類型(jwt),以及使用的加密算法( HMAC SHA256)

  • 載荷

載荷就是存放有自定義信息的地方,例如用戶標識,截止日期等

  • 簽名

簽名進行對之前的數據添加一層防護,防止被篡改。
簽名生成過程: base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密。

?
1
2
3
4
// base64加密后的header和base64加密后的payload使用.連接組成的字符串
String str=base64(header).base64(payload);
// 加鹽secret進行加密
String sign=HMACSHA256(encodedString, 'secret');

3. JWT加密方式

jwt加密分為兩種對稱加密和非對稱加密。

  • 對稱加密

對稱加密指使用同一秘鑰進行加密,解密的操作。加密解密的速度比較快,適合數據比較長時的使用。常見的算法為DES、3DES等

  • 非對稱加密

非對稱指通過公鑰進行加密,通過私鑰進行解密。加密和解密花費的時間長、速度相對較慢,但安全性更高,只適合對少量數據的使用。常見的算法RSA、ECC等。
兩種加密方法沒有誰更好,只有哪種場景更合適。

4.實戰

本例采用了spring2.x,jwt使用了nimbus-jose-jwt版本,當然其他的jwt版本也都類似,封裝的都是不錯的。

1.maven關鍵配置如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.12.1</version>
        </dependency>
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>

2.jwt工具類

對于這里的秘鑰:采用了userId+salt+uuid的方式保證,即使是同一個用戶每次生成的serect都是不同的

對于校驗token有效性,包含三個過程:

  • 格式是否合法
  • token是否在有效期內
  • token是否在刷新的有效期內

對于token超過有效期,但在刷新有效期內,返回特定的code,前端進行識別,發起請求刷新token,達到用戶無感知的過程。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
public class JwtUtil {
    private static final Logger log = LoggerFactory.getLogger(JwtUtil.class);
 
    private static final String BEARER_TYPE = "Bearer";
    private static final String PARAM_TOKEN = "token";
    /**
     * 秘鑰
     */
    private static final String SECRET = "dfg#fh!Fdh3443";
    /**
     * 有效期12小時
     */
    private static final long EXPIRE_TIME = 12 * 3600 * 1000;
    /**
     * 刷新時間7天
     */
    private static final long REFRESH_TIME = 7 * 24 * 3600 * 1000;
 
 
    public static String generate(PayloadDTO payloadDTO)  {
        //創建JWS頭,設置簽名算法和類型
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256)
                .type(JOSEObjectType.JWT)
                .build();
        //將負載信息封裝到Payload中
        Payload payload = new Payload(JSON.toJSONString(payloadDTO));
        //創建JWS對象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        try {
            //創建HMAC簽名器
            JWSSigner jwsSigner = new MACSigner(payloadDTO.getUserId() + SECRET+payloadDTO.getJti());
            //簽名
            jwsObject.sign(jwsSigner);
            return jwsObject.serialize();
        } catch (JOSEException e) {
            log.error("jwt生成器異常",e);
            throw new BizException(TOKEN_SIGNER);
        }
    }
 
 
    public static String freshToken(String token)   {
        PayloadDTO payloadDTO;
        try {
            //從token中解析JWS對象
            JWSObject jwsObject = JWSObject.parse(token);
            payloadDTO = JSON.parseObject(jwsObject.getPayload().toString(), PayloadDTO.class);
            // 校驗格式是否合適
            verifyFormat(payloadDTO, jwsObject);
        }catch (ParseException e) {
            log.error("jwt解析異常",e);
            throw new BizException(TOKEN_PARSE);
        } catch (JOSEException e) {
            log.error("jwt生成器異常",e);
            throw new BizException(TOKEN_SIGNER);
        }
        // 校驗是否過期,未過期直接返回原token
        if (payloadDTO.getExp() >= System.currentTimeMillis()) {
            return token;
        }
        // 校驗是否處于刷新時間內,重新生成token
        if (payloadDTO.getRef() >= System.currentTimeMillis()) {
            getRefreshPayload(payloadDTO);
            return generate(payloadDTO);
        }
        throw new BizException(TOKEN_EXP);
    }
 
 
 
    private static void verifyFormat(PayloadDTO payloadDTO, JWSObject jwsObject) throws JOSEException {
        //創建HMAC驗證器
        JWSVerifier jwsVerifier = new MACVerifier(payloadDTO.getUserId() + SECRET+payloadDTO.getJti());
        if (!jwsObject.verify(jwsVerifier)) {
            throw new BizException(TOKEN_ERROR);
        }
    }
 
 
    public static String getTokenFromHeader(HttpServletRequest request) {
        // 先從header取值
        String value = request.getHeader("Authorization");
        if (!StringUtils.hasText(value)) {
            // header不存在從參數中獲取
            value = request.getParameter(PARAM_TOKEN);
            if (!StringUtils.hasText(value)) {
                throw new BizException(TOKEN_MUST);
            }
        }
        if (value.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) {
            return value.substring(BEARER_TYPE.length()).trim();
        }
        return value;
    }
 
 
    public static PayloadDTO verify(String token)  {
        PayloadDTO payloadDTO;
        try {
            //從token中解析JWS對象
            JWSObject jwsObject = JWSObject.parse(token);
            payloadDTO = JSON.parseObject(jwsObject.getPayload().toString(), PayloadDTO.class);
            // 校驗格式是否合適
            verifyFormat(payloadDTO, jwsObject);
        }catch (ParseException e) {
            log.error("jwt解析異常",e);
            throw new BizException(TOKEN_PARSE);
        } catch (JOSEException e) {
            log.error("jwt生成器異常",e);
            throw new BizException(TOKEN_SIGNER);
        }
        // 校驗是否過期
        if (payloadDTO.getExp() < System.currentTimeMillis()) {
            // 校驗是否處于刷新時間內
            if (payloadDTO.getRef() >= System.currentTimeMillis()) {
                throw new BizException(TOKEN_REFRESH);
            }
            throw new BizException(TOKEN_EXP);
        }
        return payloadDTO;
    }
 
    public static PayloadDTO getDefaultPayload(Long userId) {
        long currentTimeMillis = System.currentTimeMillis();
        PayloadDTO payloadDTO = new PayloadDTO();
        payloadDTO.setJti(UUID.randomUUID().toString());
        payloadDTO.setExp(currentTimeMillis + EXPIRE_TIME);
        payloadDTO.setRef(currentTimeMillis + REFRESH_TIME);
        payloadDTO.setUserId(userId);
        return payloadDTO;
 
    }
 
    public static void getRefreshPayload(PayloadDTO payload) {
        long currentTimeMillis = System.currentTimeMillis();
        payload.setJti(UUID.randomUUID().toString());
        payload.setExp(currentTimeMillis + EXPIRE_TIME);
        payload.setRef(currentTimeMillis + REFRESH_TIME);
    }
}

3.權限攔截
本例中采用了自定義注解+切面的方式來實現token的校驗過程。
自定義Auth注解提供了是否開啟校驗token,sign的選項,實際操作中可以添加更多的功能。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Target(value = ElementType.METHOD)
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Auth {
    /**
     * 是否校驗token,默認開啟
     */
    boolean token() default true;
 
    /**
     * 是否校驗sign,默認關閉
     */
    boolean sign() default false;
}

切面部分指定了對Auth進行切面,這種方法比采用攔截器方式更加靈活些。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Component
@Aspect
public class AuthAspect {
    @Autowired
    private HttpServletRequest request;
 
    @Pointcut("@annotation(com.rain.jwt.config.Auth)")
    private void authPointcut(){}
 
    @Around("authPointcut()")
    public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        //獲取目標對象對應的字節碼對象
        Class<?> targetCls=joinPoint.getTarget().getClass();
        //獲取方法簽名信息從而獲取方法名和參數類型
        MethodSignature ms= (MethodSignature) joinPoint.getSignature();
        //獲取目標方法對象上注解中的屬性值
        Auth auth=ms.getMethod().getAnnotation(Auth.class);
        // 校驗簽名
        if (auth.token()) {
            String token = JwtUtil.getTokenFromHeader(request);
            JwtUtil.verify(token);
        }
        // 校驗簽名
        if (auth.sign()) {
            // todo
        }
        return joinPoint.proceed();
    }
}

4.測試接口

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@RestController
@RequestMapping(value="/user")
@Api(tags = "用戶")
public class UserController {
 
 
    @PostMapping(value = "/login")
    @Auth(token = false)
    @ApiOperation("登錄")
    public Result<String> login(String username,String password) {
        // 用戶常規校驗
        Long userId = 100L;
        // 用戶信息存入緩存
        // 生成token
        String token = JwtUtil.generate(JwtUtil.getDefaultPayload(userId));
        return Result.success(token);
    }
 
    @GetMapping(value = "refreshToken")
    @Auth
    @ApiOperation("刷新token")
    public Result<String> refreshToken(String token) {
        String freshToken = JwtUtil.freshToken(token);
        return Result.success(freshToken);
    }
 
    @GetMapping(value = "test")
    @Auth
    @ApiOperation("測試")
    public Result<String> test() {
        return Result.success("測試成功");
    }
}

5.總結

許多同學使用jwt經常將獲取到的token放在redis中,在服務器端控制其有效性。這是一種處理token的方式,但這種方式跟jwt的思路是背道而去的,jwt本身就提供了過期的信息,將token的生命周期放入服務器中,又何必采用jwt的方式呢?直接來個uuid不香么。
最后來個項目地址。

到此這篇關于SpringBoot JWT實現登錄刷新token的文章就介紹到這了,更多相關SpringBoot JWT實現token登錄內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/qq_34789577/article/details/120518212

延伸 · 閱讀

精彩推薦
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 久久久久电影网站 | 久久99国产精品视频 | 欧美 国产 综合 | 国产精品v片在线观看不卡 国产另类一区 | av在线播放网址 | 欧美性a视频 | 午夜男人在线观看 | 亚洲无av | 国产精品久久久久久久久久三级 | 斗破苍穹在线观看免费完整观看 | 午夜视频播放 | 91av资源在线 | 激情在线观看视频 | 青青久在线视频 | 中文字幕在线一 | 国产91久久久久久 | 一级黄色免费 | 黄色大片在线免费观看 | 狠狠操操 | 欧美人的天堂一区二区三区 | 在线观看免费毛片视频 | 久久国产精品网 | 日韩黄色三级视频 | 最新午夜综合福利视频 | 成人永久免费 | 一区二区三区日韩 | 一区二区三区四区视频在线观看 | 免费在线一区二区 | 日本精品一区二区 | 久久久一区二区 | av在线观| 97香蕉超级碰碰久久免费软件 | 国产精品久久久久久久久久 | 免费观看视频在线 | av在线电影网址 | 91网站链接 | 精品国产91久久久久久浪潮蜜月 | 有一婷婷色 | 亚洲资源网 | 一级黄色毛片播放 | 日韩欧美精品中文字幕 |