天天看點

SpringBoot內建JWT實作前後端分離模式下的登陸狀态驗證

JWT簡介

JSON Web Token (JWT)是一個開放标準(RFC 7519),它定義了一種緊湊的、自包含的方式,用于作為JSON對象在各方之間安全地傳輸資訊。該資訊可以被驗證和信任,因為它是數字簽名的。

JWT的實作流程

SpringBoot內建JWT實作前後端分離模式下的登陸狀态驗證

1. 使用者輸入賬号和密碼發出POST請求;

2. 驗證通過後伺服器應用使用私鑰建立一個JWT;

3. 伺服器應用傳回JWT;

4. 浏覽器将JWT添加在請求頭中向伺服器發送請求;

5. 伺服器應用驗證JWT的正确性以及權限;

6. 驗證通過後伺服器應用傳回響應的資源。

JWT的結構

JWT标準的Token需要包含三個部分:

第一部分:Header(頭部),頭部主要包括參數的類型以及簽名的算法。

第二部分:Payload(有效載荷),有效載荷中包含部分資訊,例如使用者名、登入狀态、有效時間等,在非加密情況下,不要在有效載荷中存放敏感資訊,例如使用者ID、登陸密碼等。

第三部分:Signature(簽名),簽名由加密後的頭部以及加密後的負荷通過“.”拼接而成,伺服器應用在收到JWT資料之後,會首先對頭部和有效載荷用同一算法再次加密,驗證簽名的正确性,是以簽名可以在消息傳遞的過程中判斷資料是否被篡改,在生成簽名的過程中使用非對稱加密還可以判斷JWT發送方的正确性。

JWT的使用場景

第一種情況:授權(Authorization),授權是JWT的最常見的使用場景,一旦使用者輸入正确的驗證資訊,成功登陸之後,使用者送出的每個請求都将包含JWT,進而允許使用者通路指定的服務和資源。JWT也用于實作單點登陸,它占用的開銷很小,而且它可以在跨域時使用。

第二種情況:資訊交換(Information Exchange),JWT本身可以攜帶一些資訊,而且可以被簽名,伺服器可以通過簽名驗證内容是否被篡改。

JWT的特點

  1. 簡潔:JWT可以通過URL,POST參數或添加在請求頭中發送,資料量小,傳輸速度快。
  2. 自包含:負載中包含了所有使用者所需要的資訊,避免了多次查詢資料庫。
  3. 跨語言:由于Token是以JSON加密的形式儲存在用戶端,是以JWT是跨語言的,原則上任何web形式都支援。
  4. 适用于分布式:不需要在服務端儲存會話資訊,特别适用于分布式微服務。

JWT的代碼實作過程

第一步,在伺服器應用的maven依賴中添加對JWT的依賴

<dependency>

<groupId>com.auth0</groupId>

<artifactId>java-jwt</artifactId>

<version>3.4.1</version>

</dependency>

第二步,在登入接口驗證登陸資訊正确後,生成JWT

//生成JWT的工具類

public class JWTUtil {

    public static String createToken(String userName) {

        try {

            Map<String, Object> header = new HashMap<>();

            header.put("alg", "HS256");//alg為header和payload的加密方式

            header.put("typ","JWT");

//          設定過期時間為目前時間後倆小時

            Date nowDate = new Date();

            Date expireDate = DateUtils.getAfterDate(new Date(),0,0,0,2,0,0);

            String token = JWT.create()

//                    設定JWT中的header

                    .withHeader(header)

//                    設定使用者名

                    .withClaim("userName",userName)

//                    設定目前時間

                    .withIssuedAt(nowDate)

//                    設定到期時間

                    .withExpiresAt(expireDate)

//                    生成簽名的加密方式

                    .sign(Algorithm.HMAC256(Constant.SECRETKEY));

            return token;

        } catch (JWTCreationException e) {

            e.printStackTrace();

            return null;

        }

    }

//  重新整理令牌中的目前時間與到期時間

    public static String reFreshToken(String token){

//        首先擷取到token中的userName資訊,再生成新的token

        try{

            Claims claims = Jwts.parser()

                    .setSigningKey(Constant.SECRETKEY)

                    .parseClaimsJws(token).getBody();

            return createToken(claims.get("userName").toString());

        }catch (Exception e){

            throw new RuntimeException("解密失敗");

        }

    }

}

第三步,在伺服器應用中添加驗證JWT的攔截器

public class AuthenticationInterceptor implements HandlerInterceptor {

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//      如果請求的不是action,就直接通過

        if (!(handler instanceof HandlerMethod)) {

            return true;

        } else {

            String token = request.getHeader("Authorization");

//          如果請求中存在token,則驗證token,若token驗證通過,則請求通過,本項目未涉及權限管理,隻存在使用者登入狀态以及登陸逾時的檢測

            if (token != null&&token.length()>0) {

                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(Constant.SECRETKEY)).build();

                try {

                    jwtVerifier.verify(token);

                } catch (JWTVerificationException e) {

                    throw new RuntimeException("token未通過驗證");

                }

            } else {

//              若不存在token,則是前台發來的請求或者是背景第一次發來的登陸請求,隻允許請求部分接口

                String uri = request.getRequestURI();

                if (uri.indexOf("getList") != -1 || uri.indexOf("subscribe") != -1 || uri.indexOf("contact") != -1||uri.indexOf("login")!=-1) {

                } else {

                    throw new RuntimeException("無權限通路");

                }

            }

            return true;

        }

    }

    @Override

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

}

第四步,注冊攔截器

@Configuration

public class InterceptorConfig implements WebMvcConfigurer {

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

//        注冊JWT攔截器,攔截所有請問

        registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");

    }

    @Bean

    public AuthenticationInterceptor authenticationInterceptor(){

        return new AuthenticationInterceptor();

    }

}

第五步,實作登陸邏輯的控制器

@RestController

@RequestMapping("loginapi")

public class AdminLoginController {

//    登陸驗證,使用者名密碼應從資料庫中查詢驗證,目前設定為常量僅作展示

    @PostMapping("login")

    public ResultBean login(String username, String password, HttpServletRequest request){

        String ip = request.getRemoteAddr();

        if(Constant.USERNAME.equals(username)&&Constant.PASSWORD.equals(password)){

            return ResultUtil.setOK("登陸成功", JWTUtil.createToken(username));

        }else {

            return ResultUtil.setError(100,"使用者名或密碼錯誤",null);

        }

    }

}

第六步,前端實作發送登入請求并記錄的代碼

$.ajax({

           type: 'post',

           url: geturl('/loginapi/login'),

           async: true,

           data: data.field,

           success: function(data) {

           layer.msg(data.msg);

           if (data.code == 0) {

                 //登陸成功後會收到token資料,将token存儲到localStorage中

                 localStorage['token']= data.data;

                 window.location.href='index.html';

           } else {}

           }

})

第七步,在下次進行發起操作請求時,在Header中添加token

$.ajax({

                type: 'post',

                url: url,

                async: true,

                data: {

                    type: articleTypeDetail,

                    _method: "PATCH",

                    //你的資料

                },

                beforeSend: function(request) {

                    request.setRequestHeader("Authorization", localStorage['token'] != null ? localStorage['token'] : null);

                },

                success: function(data) {

                    if (data.code == 0) {

                        layer.msg('修改成功');

                    } else {

                        layer.msg('修改失敗');

                    }

                },

                error: function(XMLHttpResponse, textStatus, errorThrown) {

                    layer.msg('登陸逾時,請重新登入');

                }

            })

繼續閱讀