最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JWT整合springboot自定義定時(shí)更換秘鑰方式

 更新時(shí)間:2025年11月18日 08:53:20   作者:騎豬少年  
文章介紹了JWT( JSON Web Token)的基本概念、結(jié)構(gòu)和使用場(chǎng)景,并重點(diǎn)講解了在Spring Boot中自定義定時(shí)更換JWT秘鑰的過(guò)程,通過(guò)定義用戶(hù)信息類(lèi)、JWT工具類(lèi)、配置攔截器和定時(shí)修改秘鑰工具類(lèi),實(shí)現(xiàn)了一個(gè)基本的JWT認(rèn)證和密鑰管理機(jī)制

JWT整合springboot自定義定時(shí)更換秘鑰

jwt概要:

JWT(JSON WEB TOKEN):JSON網(wǎng)絡(luò)令牌,JWT是一個(gè)輕便的安全跨平臺(tái)傳輸格式,定義了一個(gè)緊湊的自包含的方式在不同實(shí)體之間安全傳輸信息(JSON格式)。

它是在Web環(huán)境下兩個(gè)實(shí)體之間傳輸數(shù)據(jù)的一項(xiàng)標(biāo)準(zhǔn)。實(shí)際上傳輸?shù)木褪且粋€(gè)字符串。廣義上講JWT是一個(gè)標(biāo)準(zhǔn)的名稱(chēng);狹義上JWT指的就是用來(lái)傳遞的那個(gè)token字符串。

jwt是一種無(wú)狀態(tài)token,可用于oss單點(diǎn)登錄

JWT的數(shù)據(jù)結(jié)構(gòu)以及簽發(fā)的過(guò)程

JWT由三部分構(gòu)成:header(頭部)、payload(載荷)和signature(簽名)。

  • Header 頭部信息:指定類(lèi)型和算法
  • Payload 荷載信息:存放Claims聲明信息
  • Signature簽名:把前兩者對(duì)應(yīng)的Json結(jié)構(gòu)進(jìn)行base64url編碼之后的字符串拼接起來(lái)和密鑰放一起加密后的簽名 組成方式為header.payload.signature

Header的結(jié)構(gòu)

  • type 聲明類(lèi)型
  • algorithm 聲明加密算法
  • 格式: {“typ”: “JWT”,“alg”: “HS256”}

payload的結(jié)構(gòu)

  • 聲明信息
  • payload用來(lái)承載要傳遞的數(shù)據(jù),它的json結(jié)構(gòu)實(shí)際上是對(duì)JWT要傳遞的數(shù)據(jù)的聲明,這些聲明被JWT標(biāo)準(zhǔn)稱(chēng)為claims(聲明),它的每個(gè)屬性鍵值對(duì)其實(shí)就是一個(gè)claim,JWT常用的有兩種聲明,一種是
  • Reserved claims(保留聲明),也就是JWT規(guī)定的標(biāo)準(zhǔn)聲明。
  • 一種是Private claims(自定義聲明),我們?cè)谶@里定義要傳遞的信息
  • 還有一種是public claims(公共聲明),這個(gè)目前沒(méi)用到。

標(biāo)準(zhǔn)聲明(JWT保留聲明)

  • iss(Issuser):代表這個(gè)JWT的簽發(fā)主體;
  • sub(Subject):代表這個(gè)JWT的主體,即它的所有人;
  • aud(Audience):代表這個(gè)JWT的接收對(duì)象;
  • exp(Expiration time):是一個(gè)時(shí)間戳,代表這個(gè)JWT的過(guò)期時(shí)間;
  • nbf(Not Before):是一個(gè)時(shí)間戳,代表這個(gè)JWT生效的開(kāi)始時(shí)間,意味著在這個(gè)時(shí)間之前驗(yàn)證JWT是會(huì)失敗的;
  • iat(Issuedat):是一個(gè)時(shí)間戳,代表這個(gè)JWT的簽發(fā)時(shí)間; jti(JWT ID):是JWT的唯一標(biāo)識(shí)。

signature

  • 簽名
  • 把header和payload對(duì)應(yīng)的json結(jié)構(gòu)進(jìn)行base64url編碼之后得到的字符串用點(diǎn)號(hào)拼接起來(lái),然后根據(jù)header里面alg指定的簽名算法生成出來(lái)的,然后添加自己設(shè)定的key進(jìn)行加密簽名

java代碼實(shí)現(xiàn)

maven依賴(lài)

<dependency>
	<groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>

定義一個(gè)用戶(hù)信息類(lèi),存放一些用戶(hù)基本信息(如用戶(hù)名、權(quán)限(可進(jìn)行菜單鑒權(quán)))

/**
 * @author : ljt
 * @version V1.0
 * @Description: 用戶(hù)信息
 * @date Date : 2021年08月06日 13:56
 */
@Data
public class UserLoginBO {
    /**
     * 用戶(hù)id
     */
    private Long id;
    /**
     * 用戶(hù)名
     */
    private String userName;
    /**
     * 用戶(hù)昵稱(chēng)
     */
    private String nickName;
    /**
     * 用戶(hù)真實(shí)姓名
     */
    private String realName;

    /**
     * 用戶(hù)openid
     */
    private String openId;
    /**
     * 用戶(hù)token
     */
    private String accessToken;

}

JWT工具類(lèi):

/**
 * @author : ljt
 * @version V1.0
 * @Description: jwt工具類(lèi)
 * @date Date : 2021年08月06日 13:22
 */
@Component
public class JwtTokenUtil {

    /**
     * jwt生成token秘鑰,此處動(dòng)態(tài)更新所以為空,可隨便自定義
     */
    public static String TOKEN_SECRET = "";

    /**
     * 定義token有效期 秒
     */
    public static Integer tokenExpiration=1800;

    /**
     * 生成token
     * @param subject  用戶(hù)名
     * @param claims 用戶(hù)信息
     * @return token字符串
     */
    public static String generateToken(String subject, Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate(tokenExpiration))
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(SignatureAlgorithm.HS256, TOKEN_SECRET)
                .compact();
    }

    /***
     * 解析token 信息
     * @param token token字符串
     * @return 用戶(hù)map信息
     */
    private static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(TOKEN_SECRET)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }


    /**
     * 生成失效時(shí)間
     * @param expiration 失效時(shí)長(zhǎng)
     * @return 到期時(shí)間 時(shí)間格式
     */
    private static Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 根據(jù)token 獲取用戶(hù)信息
     * @param token token
     * @return 自定義的用戶(hù)對(duì)象
     */
    public static UserLoginBO getUserFromToken(String token) {
        UserLoginBO userDetail;
        try {
            final Claims claims = getClaimsFromToken(token);
            userDetail = new UserLoginBO();
            userDetail.setId(Long.parseLong(claims.get("id").toString()));
            userDetail.setUserName(String.valueOf(claims.get("userName")));
            userDetail.setNickName(String.valueOf(claims.get("nickName")));
            userDetail.setRealName(String.valueOf(claims.get("UserName")));
            userDetail.setOpenId(String.valueOf(claims.get("openId")));
        } catch (Exception e) {
            userDetail = null;
        }
        return userDetail;
    }

    /**
     * 根據(jù)token 獲取用戶(hù)ID
     * 和獲取用戶(hù)信息一致 此處新定義一個(gè)方法是用著方便
     * @param token token
     * @return 返回用戶(hù)id
     */
    private Long getUserIdFromToken(String token) {
        Long userId;
        try {
            final Claims claims = getClaimsFromToken(token);
            userId = Long.parseLong(claims.get("id").toString());
        } catch (Exception e) {
            userId = 0L;
        }
        return userId;
    }
    /**
     * 根據(jù)token 獲取用戶(hù)名
     * 和獲取用戶(hù)信息一致 此處新定義一個(gè)方法是用著方便
     * @param token token字符串
     * @return 用戶(hù)昵稱(chēng)
     */
    public static String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 刷新token
     *
     * @param token 原token
     * @return 新token
     */
    public static String refreshToken(String token,Integer tokenExpiration) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            refreshedToken = generateToken(claims.getSubject(), claims,tokenExpiration);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 根據(jù)token 獲取生成時(shí)間
     * @param token token字符串
     * @return 時(shí)間
     */
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    /**
     * 根據(jù)token 獲取過(guò)期時(shí)間
     * @param token token
     * @return 時(shí)間
     */
    public static Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }


    /**
     * 驗(yàn)證token 是否合法
     * @param token  token
     * @param userDetail  用戶(hù)信息
     * @return true 或 false
     */
    public boolean validateToken(String token, UserLoginBO userDetail) {
        final long userId = getUserIdFromToken(token);
        final String username = getUsernameFromToken(token);
        return (userId == userDetail.getId()
                && username.equals(userDetail.getUserName())
                && !isTokenExpired(token)
        );
    }

    /**
     * 判斷令牌是否過(guò)期
     * @param token 令牌
     * @return 是否過(guò)期
     */
    public static Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }



}

配置攔截器:

@Slf4j
public class LoginInterceptor extends HandlerInterceptorAdapter {

    /**
     * token剩余過(guò)期時(shí)間
     * 此處設(shè)定剩余到期時(shí)間自動(dòng)刷新token
     * 根據(jù)需要手動(dòng)編輯刷新token接口
     */
    private final Long TOKEN_DATE = 5*60*1000L;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //token約定放置在請(qǐng)求頭中以access_token的方式發(fā)送
        String token = request.getHeader("access_token");
        log.debug("用戶(hù)請(qǐng)求鑒權(quán)token = {}", token);
        //判斷token是否存在 不存在無(wú)權(quán)限 直接返回
        if(StringUtils.isNotEmpty(token))   {
            //工具類(lèi) 直接獲取用戶(hù)信息
            UserLoginBO userFromToken = JwtTokenUtil.getUserFromToken(token);
            //獲取不到 用戶(hù)授權(quán)過(guò)期 直接返回
            if (userFromToken != null && !JwtTokenUtil.isTokenExpired(token)) {
                //TODO 能正常獲取用戶(hù)信息 可判斷用戶(hù)信息是否一致 一致則鑒權(quán)成功 這里我懶省事
              
                //判斷token剩余過(guò)期時(shí)間 將過(guò)期自動(dòng)簽發(fā)新token 以response方式返回
                if ( JwtTokenUtil.getExpirationDateFromToken(token).getTime() - System.currentTimeMillis() < TOKEN_DATE ){
                    String refreshToken = JwtTokenUtil.refreshToken(token,JwtTokenUtil.tokenExpiration);
                    Object value = CacheUtil.getInstance().getValue(token);
                    CacheUtil.getInstance().putValue(refreshToken,value, CacheConstant.PERMS_INFO);
                    response.setContentType("text/html; charset=UTF-8");
                    Map<String,String> map = new HashMap<>(16);
                    map.put("accessToken",refreshToken);
                    response.setHeader("access_token",refreshToken);
                }
                return true;
            }
        }
        response.setContentType("text/html; charset=UTF-8");
        ResponseBO responseBO = new ResponseBO();
        responseBO.setCode(ResponseCode.L001.getCode());
        responseBO.setMsg(ResponseCode.L001.getTitle());
        response.getWriter().write(JSONObject.toJSONString(responseBO));
        return false;
    }
}

注冊(cè)攔截器,定義攔截規(guī)則

@Configuration
public class AuthConfigurer extends WebMvcConfigurationSupport {
    /**
     * 攔截器注入
     */
    @Bean
    public LoginInterceptor loginInterceptor() {
        return new LoginInterceptor();
    }

    /**
     * 添加攔截
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry
         		//注冊(cè)攔截器 定義攔截接口
        		.addInterceptor(loginInterceptor()).addPathPatterns("/**")
                // 排除登錄接口攔截
                .excludePathPatterns("/user/login")
        super.addInterceptors(registry);
    }


    /**
     * 替換Spring默認(rèn)JSON轉(zhuǎn)換器為fastjson
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(fastConverter);

    }

   
    /**
     * 發(fā)現(xiàn)如果繼承了WebMvcConfigurationSupport,則在yml中配置的相關(guān)內(nèi)容會(huì)失效。 需要重新指定靜態(tài)資源
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        //將所有/static/** 訪問(wèn)都映射到classpath:/static/ 目錄下
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");

    }


    /**
     * 配置servlet處理
     */
    @Override
    public void configureDefaultServletHandling(
            DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

生成JWT秘鑰工具類(lèi):

/**
 * @author : ljt
 * @version V1.0
 * @Description: 生成JWT秘鑰
 * @date Date : 2021年11月25日 15:34
 */
@Slf4j
public class CreateJWTKeyUtil {
	//生成60位秘鑰串
    public static String createJwtKey(){
        String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+<>?:,|./;'";
        Random random=new Random();
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<60;i++){
            int number=random.nextInt(84);
            sb.append(str.charAt(number));
        }
        log.info("key====" + sb.toString());
        return sb.toString();
    }
}

定時(shí)修改JWT秘鑰:

/**
 * @author : ljt
 * @version V1.0
 * @Description: 定時(shí)修改jwtkey
 * @date Date : 2021年11月25日 15:43
 */
@Component
public class CreateJWTKey {

    /**
     * jwt秘鑰存儲(chǔ),可選擇mysql、redis
     */
   @Autowired
   private IJwtKeyService jwtKeyService;

    /**
     * 可自定義更換周期
     * 注意:秘鑰更換后,已簽發(fā)token將無(wú)法解密,返回結(jié)果為token失效,需要重新簽發(fā)
     */
    @Scheduled(cron = "0 0 0 1/7 * ?")
    public void createJtw(){
        //獲取新秘鑰
        String jwtKey = CreateJWTKeyUtil.createJwtKey();
        //秘鑰存儲(chǔ)修改
        jwtKeyService.updateJwtKey(jwtKey);
        //秘鑰靜態(tài)變量修改 可減少實(shí)現(xiàn)層查詢(xún)次數(shù) 直接使用靜態(tài)數(shù)據(jù)
        JwtTokenUtil.TOKEN_SECRET = jwtKey;
    }
}

項(xiàng)目啟動(dòng)時(shí)獲取秘鑰:

@Component
public class ApplicationRunnerImplConfig implements ApplicationRunner {

    @Autowired
    private IJwtKeyService jwtKeyService;

    /**
     * 項(xiàng)目啟動(dòng)獲取jwt秘鑰
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
    	//從存儲(chǔ)庫(kù)中獲取秘鑰
        JwtTokenUtil.TOKEN_SECRET = jwtKeyService.findJwtKey();
    }
}

使用示例:登錄成功后返回token

Map<String, Object> map = new HashMap<>(16);
map.put("id",login.getId());
map.put("userName",login.getUserName());
map.put("nickName",login.getNickName());
map.put("realName",login.getRealName());
String token = JwtTokenUtil.generateToken("user", map,JwtTokenUtil.userTokenExpiration);
login.setAccessToken(token);

return login;

總結(jié)

使用流程就是:

前端發(fā)起登錄請(qǐng)求->后端簽發(fā)token->前端每次請(qǐng)求都攜帶此token->后端經(jīng)過(guò)攔截器校驗(yàn)->后端邏輯處理->返回前端

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java 什么是注解及注解原理詳細(xì)介紹

    Java 什么是注解及注解原理詳細(xì)介紹

    這篇文章主要介紹了Java 注解的原理實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2016-12-12
  • Spring AOP之@Around,@AfterReturning使用、切不進(jìn)去的解決方案

    Spring AOP之@Around,@AfterReturning使用、切不進(jìn)去的解決方案

    這篇文章主要介紹了Spring AOP之@Around,@AfterReturning使用、切不進(jìn)去的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java日期時(shí)間類(lèi)及計(jì)算詳解

    Java日期時(shí)間類(lèi)及計(jì)算詳解

    這篇文章主要介紹了Java日期時(shí)間類(lèi)及計(jì)算詳解,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助
    2022-07-07
  • Spring三級(jí)緩存解決循環(huán)依賴(lài)的過(guò)程分析

    Spring三級(jí)緩存解決循環(huán)依賴(lài)的過(guò)程分析

    這篇文章主要介紹了Spring三級(jí)緩存解決循環(huán)依賴(lài),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • log4j與slf4j的使用與區(qū)別詳解

    log4j與slf4j的使用與區(qū)別詳解

    這篇文章主要介紹了log4j與slf4j的使用與區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Java 多線程之兩步掌握

    Java 多線程之兩步掌握

    Java 多線程編程 Java給多線程編程提供了內(nèi)置的支持。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)
    2021-10-10
  • Spring框架 注解配置事務(wù)控制的流程

    Spring框架 注解配置事務(wù)控制的流程

    這篇文章主要介紹了Spring框架 注解配置事務(wù)控制的流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 使用ObjectMapper解析json不用一直new了

    使用ObjectMapper解析json不用一直new了

    這篇文章主要為大家介紹了使用ObjectMapper解析json不用一直new了的方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • JSON反序列化中的字段命名沖突問(wèn)題的解決方案解析

    JSON反序列化中的字段命名沖突問(wèn)題的解決方案解析

    在現(xiàn)代Java開(kāi)發(fā)中,JSON已成為前后端交互、微服務(wù)通信和數(shù)據(jù)存儲(chǔ)的標(biāo)準(zhǔn)格式之一,本文將通過(guò)一個(gè)真實(shí)的錯(cuò)誤案例,詳細(xì)分析SerializationException的產(chǎn)生原因,并提供多種解決方案
    2025-08-08
  • IDEA 當(dāng)前在線人數(shù)和歷史訪問(wèn)量的示例代碼

    IDEA 當(dāng)前在線人數(shù)和歷史訪問(wèn)量的示例代碼

    這篇文章主要介紹了IDEA 當(dāng)前在線人數(shù)和歷史訪問(wèn)量的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08

最新評(píng)論

冕宁县| 兴安县| 堆龙德庆县| 宣汉县| 定襄县| 四平市| 四子王旗| 泸定县| 当涂县| 闸北区| 灵石县| 西城区| 双流县| 永平县| 绥滨县| 双柏县| 龙岩市| 会同县| 河北省| 南乐县| 铜梁县| 普兰县| 辽中县| 油尖旺区| 新乡县| 兴宁市| 禄丰县| 遂川县| 志丹县| 景泰县| 西贡区| 苗栗市| 云林县| 宁乡县| 五河县| 东丰县| 韶关市| 巢湖市| 五常市| 称多县| 云梦县|