spring boot 2 集成JWT实现api接口认证
json web token(jwt)是目前流行的跨域身份验证解决方案。
官网:
本文使用spring boot 2 集成jwt实现api接口验证。
一、jwt的数据结构
jwt由header(头信息)、payload(有效载荷)和signature(签名)三部分组成的,用“.”连接起来的字符串。
jwt的计算逻辑如下:
(1)signature = hmacsha256(base64urlencode(header) + "." + base64urlencode(payload), secret)
其中私钥secret保存于服务器端,不能泄露出去。
(2)jwt = base64urlencode(header) + "." + base64urlencode(payload) + signature
下面截图以官网的例子,简单说明
二、jwt工作机制
客户端使用其凭据成功登录时,服务器生成jwt并返回给客户端。
当客户端访问受保护的资源时,用户代理使用bearer模式发送jwt,通常在authorization header中,如下所示:
authorization: bearer <token>
服务器检查authorization header中的有效jwt ,如果有效,则允许用户访问受保护资源。jwt包含必要的数据,还可以减少查询数据库或缓存信息。
三、spring boot集成jwt实现api接口验证
开发环境:
intellij idea 2019.2.2
jdk1.8
spring boot 2.1.11
1、创建一个springboot项目,pom.xml引用的依赖包如下
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>com.auth0</groupid> <artifactid>java-jwt</artifactid> <version>3.8.3</version> </dependency> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> <version>1.18.10</version> <scope>provided</scope> </dependency> <dependency> <groupid>com.alibaba</groupid> <artifactid>fastjson</artifactid> <version>1.2.62</version> </dependency>
2、定义一个接口的返回类
package com.example.jwtdemo.entity; import lombok.data; import lombok.noargsconstructor; import lombok.tostring; import java.io.serializable; @data @noargsconstructor @tostring public class responsedata<t> implements serializable { /** * 状态码:0-成功,1-失败 * */ private int code; /** * 错误消息,如果成功可为空或success * */ private string msg; /** * 返回结果数据 * */ private t data; public static responsedata success() { return success(null); } public static responsedata success(object data) { responsedata result = new responsedata(); result.setcode(0); result.setmsg("success"); result.setdata(data); return result; } public static responsedata fail(string msg) { return fail(msg,null); } public static responsedata fail(string msg, object data) { responsedata result = new responsedata(); result.setcode(1); result.setmsg(msg); result.setdata(data); return result; } }
3、统一拦截接口返回数据
package com.example.jwtdemo.config; import com.alibaba.fastjson.json; import com.example.jwtdemo.entity.responsedata; import org.springframework.core.methodparameter; import org.springframework.http.mediatype; import org.springframework.http.converter.httpmessageconverter; import org.springframework.http.server.serverhttprequest; import org.springframework.http.server.serverhttpresponse; import org.springframework.web.bind.annotation.restcontrolleradvice; import org.springframework.web.servlet.mvc.method.annotation.responsebodyadvice; /** * 实现responsebodyadvice接口,可以对返回值在输出之前进行修改 */ @restcontrolleradvice public class globalresponsehandler implements responsebodyadvice<object> { //判断支持的类型 @override public boolean supports(methodparameter methodparameter, class<? extends httpmessageconverter<?>> aclass) { return true; } @override public object beforebodywrite(object o, methodparameter methodparameter, mediatype mediatype, class<? extends httpmessageconverter<?>> aclass, serverhttprequest serverhttprequest, serverhttpresponse serverhttpresponse) { // 判断为null构建responsedata对象进行返回 if (o == null) { return responsedata.success(); } // 判断是responsedata子类或其本身就返回object o本身,因为有可能是接口返回时创建了responsedata,这里避免再次封装 if (o instanceof responsedata) { return (responsedata<object>) o; } // string特殊处理,否则会抛异常 if (o instanceof string) { return json.tojson(responsedata.success(o)).tostring(); } return responsedata.success(o); } }
4、统一异常处理
package com.example.jwtdemo.exception; import com.example.jwtdemo.entity.responsedata; import org.springframework.web.bind.annotation.exceptionhandler; import org.springframework.web.bind.annotation.restcontrolleradvice; @restcontrolleradvice public class globalexceptionhandler { @exceptionhandler(exception.class) public responsedata exceptionhandler(exception e) { e.printstacktrace(); return responsedata.fail(e.getmessage()); } }
5、创建一个jwt工具类
package com.example.jwtdemo.common; import com.auth0.jwt.jwtverifier; import com.auth0.jwt.algorithms.algorithm; import com.auth0.jwt.interfaces.decodedjwt; import com.auth0.jwt.jwt; import java.util.date; public class jwtutils { public static final string token_header = "authorization"; public static final string token_prefix = "bearer "; // 过期时间,这里设为5分钟 private static final long expire_time = 5 * 60 * 1000; // 密钥 private static final string secret = "jwtsecretdemo"; /** * 生成签名,5分钟后过期 * * @param name 名称 * @param secret 密码 * @return 加密后的token */ public static string sign(string name) { date date = new date(system.currenttimemillis() + expire_time); algorithm algorithm = algorithm.hmac256(secret); //使用hs256算法 string token = jwt.create() //创建令牌实例 .withclaim("name", name) //指定自定义声明,保存一些信息 //.withsubject(name) //信息直接放在这里也行 .withexpiresat(date) //过期时间 .sign(algorithm); //签名 return token; } /** * 校验token是否正确 * * @param token 令牌 * @param secret 密钥 * @return 是否正确 */ public static boolean verify(string token) { try{ string name = getname(token); algorithm algorithm = algorithm.hmac256(secret); jwtverifier verifier = jwt.require(algorithm) .withclaim("name", name) //.withsubject(name) .build(); decodedjwt jwt = verifier.verify(token); return true; } catch (exception e){ return false; } } /** * 获得token中的信息 * * @return token中包含的名称 */ public static string getname(string token) { try { decodedjwt jwt = jwt.decode(token); return jwt.getclaim("name").asstring(); }catch(exception e){ return null; } } }
6、新建两个自定义注解:一个需要认证、另一个不需要认证
package com.example.jwtdemo.config; import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; @target({elementtype.method, elementtype.type}) @retention(retentionpolicy.runtime) public @interface logintoken { boolean required() default true; }
package com.example.jwtdemo.config; import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; @target({elementtype.method, elementtype.type}) @retention(retentionpolicy.runtime) public @interface passtoken { boolean required() default true; }
7、新建拦截器并验证token
package com.example.jwtdemo.config; import com.example.jwtdemo.common.jwtutils; import org.springframework.web.method.handlermethod; import org.springframework.web.servlet.handlerinterceptor; import org.springframework.web.servlet.modelandview; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import java.lang.reflect.method; public class authenticationinterceptor implements handlerinterceptor { @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception { // 如果不是映射到方法直接通过 if(!(handler instanceof handlermethod)){ return true; } handlermethod handlermethod=(handlermethod)handler; method method=handlermethod.getmethod(); //检查是否有passtoken注释,有则跳过认证 if (method.isannotationpresent(passtoken.class)) { passtoken passtoken = method.getannotation(passtoken.class); if (passtoken.required()) { return true; } } //检查有没有需要用户权限的注解 if (method.isannotationpresent(logintoken.class)) { logintoken logintoken = method.getannotation(logintoken.class); if (logintoken.required()) { // 执行认证 string tokenheader = request.getheader(jwtutils.token_header);// 从 http 请求头中取出 token if(tokenheader == null){ throw new runtimeexception("没有token"); } string token = tokenheader.replace(jwtutils.token_prefix, ""); if (token == null) { throw new runtimeexception("没有token"); } boolean b = jwtutils.verify(token); if (b == false) { throw new runtimeexception("token不存在或已失效,请重新获取token"); } return true; } } return false; } @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 { } }
8、配置拦截器
package com.example.jwtdemo.config; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.web.servlet.config.annotation.interceptorregistry; import org.springframework.web.servlet.config.annotation.webmvcconfigurer; @configuration public class interceptorconfig implements webmvcconfigurer { @override public void addinterceptors(interceptorregistry registry) { registry.addinterceptor(authenticationinterceptor()) .addpathpatterns("/**"); } @bean public authenticationinterceptor authenticationinterceptor() { return new authenticationinterceptor(); } }
9、新建一个测试的控制器
package com.example.jwtdemo.controller; import com.example.jwtdemo.common.jwtutils; import com.example.jwtdemo.config.logintoken; import com.example.jwtdemo.config.passtoken; import org.springframework.web.bind.annotation.*; @restcontroller public class democontroller { @passtoken @postmapping("gettoken") public string gettoken(@requestparam string username, @requestparam string password){ if(username.equals("admin") && password.equals("123456")){ string token = jwtutils.sign("admin"); return token; } return "用户名或密码错误"; } @logintoken @getmapping("getdata") public string getdata() { return "获取数据..."; } }
10、postman测试
(1)get请求:http://localhost:8080/getdata,返回如下
(2)get请求:http://localhost:8080/getdata,在token中随便输入字符串,返回如下
(3)post请求:http://localhost:8080/gettoken,并设置用户名和密码参数,返回如下
(4)get请求:http://localhost:8080/getdata,在token中输入上面token字符串,返回如下
推荐阅读
-
PHP如何使用JWT做Api接口身份认证的实现
-
Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理
-
SpringBoot集成Spring security JWT实现接口权限认证
-
Spring-Boot快速集成netty-socketio(socket服务实现,支持认证)
-
spring boot集成javacv + websocket实现实时视频推流回放(延时1-2秒)
-
Spring Boot 集成 swagger2 API
-
Spring Boot Security OAuth2 实现支持JWT令牌的授权服务器
-
Spring Boot+JWT快速实现简单的接口鉴权
-
Spring Cloud实战 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT实现微服务统一认证授权
-
Spring Cloud实战 | 最终篇:Spring Cloud Gateway+Spring Security OAuth2集成统一认证授权平台下实现注销使JWT失效方案