欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringBoot使用JWT实现登录验证的方法示例

程序员文章站 2023-01-08 17:10:59
什么是jwt json web token(jwt)是一个开放的标准(rfc 7519),它定义了一个紧凑且自包含的方式,用于在各方之间以json对象安全地传输信息。这些...

什么是jwt

json web token(jwt)是一个开放的标准(rfc 7519),它定义了一个紧凑且自包含的方式,用于在各方之间以json对象安全地传输信息。这些信息可以通过数字签名进行验证和信任。可以使用秘密(使用hmac算法)或使用rsa的公钥/私钥对来对jwt进行签名。
具体的jwt介绍可以查看官网的介绍:

jwt请求流程

引用官网的图片

SpringBoot使用JWT实现登录验证的方法示例

中文介绍:

  • 用户使用账号和面发出post请求;
  • 服务器使用私钥创建一个jwt;
  • 服务器返回这个jwt给浏览器;
  • 浏览器将该jwt串在请求头中像服务器发送请求;
  • 服务器验证该jwt;
  • 返回响应的资源给浏览器

jwt组成

jwt含有三部分:头部(header)、载荷(payload)、签证(signature)

头部(header)

头部一般有两部分信息:声明类型、声明加密的算法(通常使用hmac sha256)

头部一般使用base64加密:eyj0exaioijkv1qilcjhbgcioijiuzi1nij9

解密后:

{
  "typ":"jwt",
  "alg":"hs256"
}

载荷(payload)

该部分一般存放一些有效的信息。jwt的标准定义包含五个字段:

  • iss:该jwt的签发者
  • sub: 该jwt所面向的用户
  • aud: 接收该jwt的一方
  • exp(expires): 什么时候过期,这里是一个unix时间戳
  • iat(issued at): 在什么时候签发的

这个只是jwt的定义标准,不强制使用。另外自己也可以添加一些公开的不涉及安全的方面的信息。

签证(signature)

jwt最后一个部分。该部分是使用了hs256加密后的数据;包含三个部分:

  • header (base64后的)
  • payload (base64后的)
  • secret 私钥

secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

在springboot项目中应用

首先需要添加jwt的依赖:

  <dependency>
    <groupid>io.jsonwebtoken</groupid>
    <artifactid>jjwt</artifactid>
    <version>0.6.0</version>
  </dependency>

接下来在配置文件中添加jwt的配置信息:

##jwt配置
audience:
 clientid: 098f6bcd4621d373cade4e832627b4f6
 base64secret: mdk4zjziy2q0njixzdm3m2nhzgu0ztgzmjyyn2i0zjy=
 name: restapiuser
 expiressecond: 172800

配置信息的实体类,以便获取jwt的配置:

@data
@configurationproperties(prefix = "audience")
@component
public class audience {

  private string clientid;
  private string base64secret;
  private string name;
  private int expiressecond;

}

jwt验证主要是通过拦截器验证,所以我们需要添加一个拦截器来验证请求头中是否含有后台颁发的token,这里请求头的格式:这里bearer;后面就是服务器颁发的token

SpringBoot使用JWT实现登录验证的方法示例

public class jwtfilter extends genericfilterbean {

  @autowired
  private audience audience;

  /**
   * reserved claims(保留),它的含义就像是编程语言的保留字一样,属于jwt标准里面规定的一些claim。jwt标准里面定好的claim有:

   iss(issuser):代表这个jwt的签发主体;
   sub(subject):代表这个jwt的主体,即它的所有人;
   aud(audience):代表这个jwt的接收对象;
   exp(expiration time):是一个时间戳,代表这个jwt的过期时间;
   nbf(not before):是一个时间戳,代表这个jwt生效的开始时间,意味着在这个时间之前验证jwt是会失败的;
   iat(issued at):是一个时间戳,代表这个jwt的签发时间;
   jti(jwt id):是jwt的唯一标识。
   * @param req
   * @param res
   * @param chain
   * @throws ioexception
   * @throws servletexception
   */
  @override
  public void dofilter(final servletrequest req, final servletresponse res, final filterchain chain)
      throws ioexception, servletexception {

    final httpservletrequest request = (httpservletrequest) req;
    final httpservletresponse response = (httpservletresponse) res;
    //等到请求头信息authorization信息
    final string authheader = request.getheader("authorization");

    if ("options".equals(request.getmethod())) {
      response.setstatus(httpservletresponse.sc_ok);
      chain.dofilter(req, res);
    } else {

      if (authheader == null || !authheader.startswith("bearer;")) {
        throw new loginexception(resultenum.login_error);
      }
      final string token = authheader.substring(7);

      try {
        if(audience == null){
          beanfactory factory = webapplicationcontextutils.getrequiredwebapplicationcontext(request.getservletcontext());
          audience = (audience) factory.getbean("audience");
        }
        final claims claims = jwthelper.parsejwt(token,audience.getbase64secret());
        if(claims == null){
          throw new loginexception(resultenum.login_error);
        }
        request.setattribute(constants.claims, claims);
      } catch (final exception e) {
        throw new loginexception(resultenum.login_error);
      }

      chain.dofilter(req, res);
    }
  }
}

注册jwt拦截器,可以在配置类中,也可以直接在springboot的入口类中

public class jwtfilter extends genericfilterbean {

  @autowired
  private audience audience;

  /**
   * reserved claims(保留),它的含义就像是编程语言的保留字一样,属于jwt标准里面规定的一些claim。jwt标准里面定好的claim有:

   iss(issuser):代表这个jwt的签发主体;
   sub(subject):代表这个jwt的主体,即它的所有人;
   aud(audience):代表这个jwt的接收对象;
   exp(expiration time):是一个时间戳,代表这个jwt的过期时间;
   nbf(not before):是一个时间戳,代表这个jwt生效的开始时间,意味着在这个时间之前验证jwt是会失败的;
   iat(issued at):是一个时间戳,代表这个jwt的签发时间;
   jti(jwt id):是jwt的唯一标识。
   * @param req
   * @param res
   * @param chain
   * @throws ioexception
   * @throws servletexception
   */
  @override
  public void dofilter(final servletrequest req, final servletresponse res, final filterchain chain)
      throws ioexception, servletexception {

    final httpservletrequest request = (httpservletrequest) req;
    final httpservletresponse response = (httpservletresponse) res;
    //等到请求头信息authorization信息
    final string authheader = request.getheader("authorization");

    if ("options".equals(request.getmethod())) {
      response.setstatus(httpservletresponse.sc_ok);
      chain.dofilter(req, res);
    } else {

      if (authheader == null || !authheader.startswith("bearer;")) {
        throw new loginexception(resultenum.login_error);
      }
      final string token = authheader.substring(7);

      try {
        if(audience == null){
          beanfactory factory = webapplicationcontextutils.getrequiredwebapplicationcontext(request.getservletcontext());
          audience = (audience) factory.getbean("audience");
        }
        final claims claims = jwthelper.parsejwt(token,audience.getbase64secret());
        if(claims == null){
          throw new loginexception(resultenum.login_error);
        }
        request.setattribute(constants.claims, claims);
      } catch (final exception e) {
        throw new loginexception(resultenum.login_error);
      }

      chain.dofilter(req, res);
    }
  }
}

登录处理,也就是jwt的颁发

 @postmapping("login")
  public resultvo login(@requestparam(value = "usernameoremail", required = true) string usernameoremail,
             @requestparam(value = "password", required = true) string password,
             httpservletrequest request) {
    boolean is_email = matcherutil.matcheremail(usernameoremail);
    user user = new user();
    if (is_email) {
      user.setemail(usernameoremail);
    } else {
      user.setusername(usernameoremail);
    }
    user query_user = userservice.get(user);
    if (query_user == null) {
      return resultvoutil.error("400", "用户名或邮箱错误");
    }
    //验证密码
    passwordencoder encoder = new bcryptpasswordencoder();
    boolean is_password = encoder.matches(password, query_user.getpassword());
    if (!is_password) {
      //密码错误,返回提示
      return resultvoutil.error("400", "密码错误");
    }
   
    string jwttoken = jwthelper.createjwt(query_user.getusername(),
                      query_user.getid(),
                      query_user.getrole().tostring(),
                      audience.getclientid(),
                      audience.getname(),
                      audience.getexpiressecond()*1000,
                      audience.getbase64secret());

    string result_str = "bearer;" + jwttoken;
    return resultvoutil.success(result_str);
  }

这里将jwt的颁发处理抽离出来了,jwt工具类:

public class jwthelper {

  /**
   * 解析jwt
   */
  public static claims parsejwt(string jsonwebtoken, string base64security){
    try
    {
      claims claims = jwts.parser()
          .setsigningkey(datatypeconverter.parsebase64binary(base64security))
          .parseclaimsjws(jsonwebtoken).getbody();
      return claims;
    }
    catch(exception ex)
    {
      return null;
    }
  }

  /**
   * 构建jwt
   */
  public static string createjwt(string name, string userid, string role,
                  string audience, string issuer, long ttlmillis, string base64security)
  {
    signaturealgorithm signaturealgorithm = signaturealgorithm.hs256;

    long nowmillis = system.currenttimemillis();
    date now = new date(nowmillis);

    //生成签名密钥
    byte[] apikeysecretbytes = datatypeconverter.parsebase64binary(base64security);
    key signingkey = new secretkeyspec(apikeysecretbytes, signaturealgorithm.getjcaname());

    //添加构成jwt的参数
    jwtbuilder builder = jwts.builder().setheaderparam("typ", "jwt")
        .claim("role", role)
        .claim("unique_name", name)
        .claim("userid", userid)
        .setissuer(issuer)
        .setaudience(audience)
        .signwith(signaturealgorithm, signingkey);
    //添加token过期时间
    if (ttlmillis >= 0) {
      long expmillis = nowmillis + ttlmillis;
      date exp = new date(expmillis);
      builder.setexpiration(exp).setnotbefore(now);
    }

    //生成jwt
    return builder.compact();
  }
}

最后,jwt可能会出现跨域的问题,所以最好添加一下对跨域的处理

@configuration
public class corsconfig {

  @bean
  public filterregistrationbean corsfilter() {
    urlbasedcorsconfigurationsource source = new urlbasedcorsconfigurationsource();
    corsconfiguration config = new corsconfiguration();
    config.setallowcredentials(true);
    config.addallowedorigin("*");
    config.addallowedheader("*");
    config.addallowedmethod("options");
    config.addallowedmethod("head");
    config.addallowedmethod("get");
    config.addallowedmethod("put");
    config.addallowedmethod("post");
    config.addallowedmethod("delete");
    config.addallowedmethod("patch");
    source.registercorsconfiguration("/**", config);
    final filterregistrationbean bean = new filterregistrationbean(new corsfilter(source));
    bean.setorder(0);
    return bean;
  }

  @bean
  public webmvcconfigurer mvcconfigurer() {
    return new webmvcconfigureradapter() {
      @override
      public void addcorsmappings(corsregistry registry) {
        registry.addmapping("/**").allowedmethods("get", "put", "post", "get", "options");
      }
    };
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。