SpringBoot集成JWT实现前后端分离模式下的登陆状态验证

JWT简介

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

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 {

    /**

     * @param userName 用户名

     * @return token

     */

    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('登陆超时,请重新登录');                 }             })