springboot集成jwt令牌【前後端分離模式下的身份認證方式】

一,傳統Cookie+Session與JWT對比

1, 在傳統的用戶登錄認證中,因爲http是無狀態的,所以都是採用session方式。用戶登錄成功,服務端會保證一個session,當然會給客戶端一個sessionId,客戶端會把sessionId保存在cookie中,每次請求都會攜帶這個sessionId

2,cookie+session這種模式通常是保存在內存中,而且服務從單服務到多服務會面臨的session共享問題,隨着用戶量的增多,開銷就會越大。而JWT不是這樣的,只需要服務端生成token,客戶端保存這個token,每次請求攜帶這個token,服務端認證解析就可。

3, JWT方式校驗方式更加簡單便捷化,無需通過redis緩存,而是直接根據token取出保存的用戶信息,以及對token可用性校驗,單點登錄,驗證token更爲簡單。

二,springboot集成jwt

1,jwt的整合依賴

		<!-- JWT依賴 -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.7.0</version>
		</dependency>
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.4.0</version>
		</dependency>

2,jwt的自定義配置

server:
  port: 8080
spring:
  application:
    name: springboot-jwt

# 自定義配置
config:
  jwt:
    # 加密密鑰
    secret: abcdefg1234567
    # token有效時長
    expire: 3600
     # header 名稱
    header: token

3,編寫JwtConfig

JwtConfig負責

  • 生成token
  • 獲取token中的註冊信息
  • 驗證token是否過期失效
  • 獲取token失效時間
  • token中獲取用戶名
  • 獲取jwt發佈時間
package com.ftx.jwt.config;

/**
 * @author FanJiangFeng
 * @version 1.0.0
 * @ClassName JwtConfig.java
 * @Description TODO
 * @createTime 2020年06月22日 15:43:00
 */

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * JWT的token,區分大小寫
 */
@ConfigurationProperties(prefix = "config.jwt", ignoreUnknownFields = true)
@Component
public class JwtConfig {

    private String secret;
    private long expire;
    private String header;

    /**
     * 生成token
     * @param subject
     * @return
     */
    public String createToken (String subject){
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);//過期時間

        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(subject)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    /**
     * 獲取token中註冊信息
     * @param token
     * @return
     */
    public Claims getTokenClaim (String token) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }catch (Exception e){
//            e.printStackTrace();
            return null;
        }
    }
    /**
     * 驗證token是否過期失效
     * @param expirationTime
     * @return
     */
    public boolean isTokenExpired (Date expirationTime) {
        return expirationTime.before(new Date());
    }

    /**
     * 獲取token失效時間
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        return getTokenClaim(token).getExpiration();
    }
    /**
     * 獲取用戶名從token中
     */
    public String getUsernameFromToken(String token) {
        return getTokenClaim(token).getSubject();
    }

    /**
     * 獲取jwt發佈時間
     */
    public Date getIssuedAtDateFromToken(String token) {
        return getTokenClaim(token).getIssuedAt();
    }

    // --------------------- getter & setter ---------------------

    public String getSecret() {
        return secret;
    }
    public void setSecret(String secret) {
        this.secret = secret;
    }
    public long getExpire() {
        return expire;
    }
    public void setExpire(long expire) {
        this.expire = expire;
    }
    public String getHeader() {
        return header;
    }
    public void setHeader(String header) {
        this.header = header;
    }
}


4,配置攔截器

在攔截器中對token進行驗證。

package com.ftx.jwt.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * @author FanJiangFeng
 * @version 1.0.0
 * @ClassName TokenInterceptor.java
 * @Description TODO
 * @createTime 2020年06月22日 15:47:00
 */
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {

    @Resource
    private JwtConfig jwtConfig ;
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws SignatureException {
        /** 地址過濾 */
        String uri = request.getRequestURI() ;
        if (uri.contains("/login")){
            return true ;
        }
        /** Token 驗證 */
        String token = request.getHeader(jwtConfig.getHeader());
        if(StringUtils.isEmpty(token)){
            token = request.getParameter(jwtConfig.getHeader());
        }
        if(StringUtils.isEmpty(token)){
            throw new SignatureException(jwtConfig.getHeader()+ "不能爲空");
        }

        Claims claims = null;
        try{
            claims = jwtConfig.getTokenClaim(token);
            if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){
                throw new SignatureException(jwtConfig.getHeader() + "失效,請重新登錄。");
            }
        }catch (Exception e){
            throw new SignatureException(jwtConfig.getHeader() + "失效,請重新登錄。");
        }
		//該token可用,放行
        return true;
    }
}

註冊攔截器到SpringMvc

package com.ftx.jwt.config;

/**
 * @author FanJiangFeng
 * @version 1.0.0
 * @ClassName WebConfig.java
 * @Description TODO
 * @createTime 2020年06月22日 15:53:00
 */

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import javax.annotation.Resource;
import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    private TokenInterceptor tokenInterceptor ;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
    }
}

5,編寫測試controller接口

package com.ftx.jwt.controller;
import com.alibaba.fastjson.JSONObject;
import com.ftx.jwt.config.JwtConfig;
import com.ftx.jwt.util.ResultTool;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @author FanJiangFeng
 * @version 1.0.0
 * @ClassName TokenController.java
 * @Description TODO
 * @createTime 2020年06月22日 16:05:00
 */
@RestController
public class TokenController {
    @Resource
    private JwtConfig jwtConfig ;

    /**
     * 用戶登錄接口
     * @param userName
     * @param passWord
     * @return
     */
    @PostMapping("/login")
    public JSONObject login (@RequestParam("userName") String userName,
                              @RequestParam("passWord") String passWord){
        JSONObject json = new JSONObject();

        /** 驗證userName,passWord和數據庫中是否一致,如不一致,直接return ResultTool.errer(); 【這裏省略該步驟】*/

        // 這裏模擬通過用戶名和密碼,從數據庫查詢userId
        // 這裏把userId轉爲String類型,實際開發中如果subject需要存userId,則可以JwtConfig的createToken方法的參數設置爲Long類型
        String userId = 5 + "";
        String token = jwtConfig.createToken(userId) ;
        if (!StringUtils.isEmpty(token)) {
            json.put("token",token) ;
        }
        return ResultTool.success(json) ;
    }

    /**
     * 需要 Token 驗證的接口
     */
    @PostMapping("/info")
    public JSONObject info (){
        return ResultTool.success("info") ;
    }

    /**
     * 根據請求頭的token獲取userId
     * @param request
     * @return
     */
    @GetMapping("/getUserInfo")
    public JSONObject getUserInfo(HttpServletRequest request){
        String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token"));
        return ResultTool.success(usernameFromToken) ;
    }
}

PostMan測試工具測試一下,訪問登錄接口,當對賬號密碼驗證通過時,則返回一個token給客戶端
說明:token是在請求頭處,request.getHeader()得到token
在這裏插入圖片描述
當直接去訪問info接口時,會返回token爲空的異常
當在請求頭加上正確token時,則攔截器驗證通過,可以正常訪問到接口
在這裏插入圖片描述
當在請求頭加入一個錯誤token,則會返回token失效的異常
接下來測試一下獲取用戶信息,因爲這裏存的subjectuserId,所以直接返回上面寫死的假數據5
在這裏插入圖片描述
以上內容參考自https://blog.csdn.net/akiranicky/article/details/99307713

三,知識點概述

1,@Resource註解

1, @Autowired@Resource都可以用來裝配bean. 都可以寫在字段上,或寫在setter方法上。

2, @Autowired默認按類型裝配(這個註解是屬於spring的),默認情況下必須要求依賴對象必須存在,如果要允許null值,可以設置它的required屬性爲false,如:@Autowired(required=false)
3,@Resource(這個註解屬於J2EE的),默認按照名稱進行裝配,名稱可以通過name屬性進行指定,如果沒有指定name屬性,當註解寫在字段上時,默認取字段名進行安裝名稱查找,如果註解寫在setter方法上默認取屬性名進行裝配。當找不到與名稱匹配的bean時才按照類型進行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進行裝配。

2,@ConfigurationProperties註解

@ConfigurationProperties:告訴SpringBoot將本類中的所有屬性和配置文件中相關的配置進行綁定;prefix = "xxx":配置文件中哪個下面的所有屬性進行一一映射

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章