SpringBoot使用JWT实现登录验证的方法示例
什么是jwt
json web token(jwt)是一个开放的标准(rfc 7519),它定义了一个紧凑且自包含的方式,用于在各方之间以json对象安全地传输信息。这些信息可以通过数字签名进行验证和信任。可以使用秘密(使用hmac算法)或使用rsa的公钥/私钥对来对jwt进行签名。
具体的jwt介绍可以查看官网的介绍:
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
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"); } }; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: java程序员常见的sql错误
下一篇: 硬盘电路板的测试步骤以及维修方法详细介绍