SpringBoot+JWT实战(附源码)
在阅览本文之前,咱们还应该对session、cookie、JWT有一个根本的了解。在本篇文章中小码仔不再对它们做出过多赘述,假如对这三者知道还不行清晰的小可爱能够先移步这儿:看完这篇 Session、Cookie、Token ! A,和面试官扯皮就没问4 k C ? J { :题了对其做根本的了解和知道。
假如你已对以上三者有了的根本概L ] : 0 1念和了解,但o ^ O + e t } ( =是关于JWT的运用还充溢疑问的话,那么本篇文章便是为你而写。本文咱们将运用S# $ bpringBoot集成JWT来完成一个简略的token验证,从而使咱们对JWT的运用有一个根本的了解。
SpringBoot集成JWT
首要咱们搭建好SpringBoot框架,SpringBoot环境准备就绪。接下来履行以下操作:
1.引入依靠
引入JWT依靠,由h t _ U Y b所以基于Java,所以需求的是java-jwt
。
<dependencya a *>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.5.0</version>
</dependency>
2.自界说注解
在这一步,咱们在annotation包下界说一个用户需求登录才干进行其他接口拜访等一系列操作的注解TokenRequired
。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceTokenRequired{
booleanrequired) 0 e()defaulttrue;
}
@Target
旨意为咱们自o T , Y界说注解@TokenRequired
的效果方针,因为咱们本次注解的效果方针为办法层级,因此运用 ElementType.METHOD
。
@Retention
旨意为咱们自界b m T ^ ^ 1 ^ 0 M说注; ) 9 Q # | .解 @TokenRequired
的保存方位,@TokenRequired
的保存方位被界说为RetentionPolic; 1 I r E ]y.RUNTIME
这种类型的注解将被JVM保存,他能在运H : n J行时被JVM或其他运用反射机制的代码所读取和运用。
3.界说实体类
在entity包中,咱们运用lomb– ? 5 ; y * K tok,简略自界说一个实体类User。$ 4 E n [ Y 1 L
@Data
@AllArgsConstructor
@$ l : ANoArgsConstructor
publicclu p : + D ` &as| 2 & | /sUser{
StringId;
Stringusername;
Stringpassword;
}
4.界说一个JWT东西类
在这一步,咱们在util包下j c P E ! t 5面创建一个JwtUtil东西类,用于生成token和校验token。
publicclassJwtUtil{
//过期时间S 4 N ]15分钟
privatq y ` UestaticfinallongEXPIRE_TIME=15*60*1000;
//生成签名,15分钟后过期
publi) 2 . dcstaticStringW 1 m z - {sign(Stringusernamb [ 3 3 | Z oe,StringuserId,Stringpasswo( S ?rd){
//过期时间
Datedatk X # J 0 P o He=newDh ` C 5 Y & qate(System.currentTimeMillis()+EXPIRE_TIME);
//运用用户暗码作C ) K ; 2为私钥进行} L $ ( N F I加密
Algoe T e L _ UrT / Z R 5 ithx q Nmalgorithm=Algorithm.HMAC256(password);
//设置头信息
HashMap<String,Object>header=newHashMap<>(2)( R 9;
header.put("typ","JWT");
header.put("alg","HS256");
//顺便uF / a Username和userID生成签名
returnJWT.create().withHeader^ / O , 2 t E ](header).withClaim("userId",userId)
.withClaim("username",username).) e 8 f O T s cwithExpiresAt(date).sign(algorG F Q D Hithm);
}
//校验token
publicstaticbooleanverb * D m _ity(S! - g X v { Z [tringtoken,Stringpassword){
try{
Algorithmalgorithm=Algo[ m w Z y p Xrithm.HMAC- ` f * X u J e256(password5 ! y);
JWTVerifierverifier=JWT.require(algorithm).build();
verifier.verify(token);
returntrue;
}caP N j s stch(IllegalArgumentExcepti0 . + U A ] 9one){
returnfalse;
}catch(JWTVerificationExceptione){
returnfalse;
}
}
}
5.事务校验并生成token
在service包下,咱们创建一个UserService,并界说一个login办法,{ j 7用于做登录接口的事务层数据校验,并调取JwtUtil中办法生成token。
@Service("UserService")
publicclassUserService{
@Autowired
UserMapo D W ]peruserMapper;
publicString X D b g ] 6 Elogin(Stringname,Stringpassword){
Stringtoken=null;
try{
//校验用户是否存在
Useruseh N _ ]r=userMapper.findByUser= y wname(name);
if(user==null){
ResultDTO.failure(newResultErrW z p [ Mor(UserError.EMP_IS_NULL_EXIT));
}else{
//查验用户暗码是否正确
if(!uss T & [ a # R 6 Wer.getPass] # X + $ ~ C W [wo+ z ! @rd().equals(password)){
ResultDTO.failure(newResultError(UserError.. ` OPASSWORD_OR_N3 { A NAME_IS_ERROR));
}elseX C ) o n v $ o{
//生成token,将userid、userName保存到toke& C _ B 0n里边
token=JwtUtil.sign(user.getUsername(B Z 9 [ ; S),user.getId(),user.getPassword())/ X J S I;
}
}
}catch(Exceptioq $ One){
e.printStackTrace();
}
returntoken;
}
}
Algorithm.HMAC256()
:运用HS256
生成token
,密钥则是用l i g K ! {户的暗码,仅有密钥的话能够保存在服务端。
withAudience()
存& g j | & E O } S入需求保存在token
的信息,这儿我把用户ID存入token
中。
6.界说阻拦器
接下8 p 8 { ~ I来咱们需求写一个阻拦器去获取token并验证token。
publicclasq G 0 T nsAuthentx l xicationInti U K . 6 4 ; oerceptorimplementsHandlerInterceptor{
@Autowired
UserServiceuserService;
@Override
publicbooleanpreHandle(HttpServletRequesthttpServle+ i 9 ; DtRequest,HttpServletResponsehttpServ5 9 7letResponse,Objectobject)throwsException{
//从http恳求头中取出token
Stringtoken( E h L 3 @ N $=httpServletRequesU l r % )t.getHeader("to7 6 =ken");
//假如不是映u R B E a n U ~射到办法直接经过
if(!(objecti{ i + e _nstanceofHandlerMethod)){
returntrue;
}
HandlerMethodhandlerMethod=(HandlerMethod)object;
Met% D E X O t / Bhodmethod=haf 9 ; # C p Z 3 *ndlerMethod.getMethod();
//查看有没有需求用户权限的注解
if(method.isAnnotationPresent(TokenRequired.class)){
TokenRequiredused [ t , # k 4 2 rLoginToken=method.gM 2 | J : X I detAnnotation(TokenRequiredA g u D , n k : 5.class);
if(userLoginToken.required()){
//履行认证
if(token==null){
thrownewRuntimeException("无to/ k G $ Y d cken,请从头登录");
}
//获取token中的useriH O 7 T M i 2d
StringuserId;
try{
userId=JWK , ; t ( % ^ zT.decode(token).getClaim("uz w k =serId").asString();
}L z ncatch(JWTDecodeExcepB b z # q Hti3 k / F 6 6onj){
thrownewRuntimeException([ u O K ^ 6 ) Z y"401");
}
Useruser=userService.findUserBye b y r } G kId(userId);
if(user==null){
thrownewRuntimeException("用户不存在,请从头登a P l + o K录");
}
//验证token
try{
if(!JwtUtil.verity(token,user.getPasswo{ 4 & Rrd())){
thrownewRuntimeException("无效的令牌");
}
}catch(JWTVerificati` * k e n E % 4 NonExceptione){
thrownewRuntimeException("401");
}
retuR P Q * e qrntrue;
}
}. U L _ K
returntrue;
}
@Override
publiz G * & TcvoI + G Z CidpostHandle(HttpServleZ s T .tReqd @ 4 O 8 & /uesthttpServlet- V _ * & v .Request,HttpServletResponsehttpServletResponse,Objecto,Mode! [ } R U = C NlAndViewmodelAndView)throwsException{
}
@Override
publ3 _ 9 6ic7 y c - u (voidafterCompletion(HttpServletRequesthttpServle@ V N (tRequest,HttpServletResponsehttpServletResponse,Objecto,Exceptione)throwsException{
}
}
AuthenticationInterceptor
阻拦器完成了HanN q # @dlerInterceptor
接口的三个办法:
-
boolean preHandle ():
预处理回调办法,完成处理器的预处理,第三个参数为响应的处理器,自界说Controller回来值,回来值为true会调用下一个阻拦器或处理器,或许接着履行postHand2 L T Q 7le()和afterCompletion();false表示流程中止,不会持g # E J续调用其他的阻拦器或处理器,中止履行。
-
void postHandle():
后处理回调办法,完成处理T P B F & 3 O 2器的后处理(DispatcherServlet进行视图回来烘托之行进行调用),此刻咱们能够J r = L 3 s k M经过modelAndView对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
-
void afterComX - X v C ^ +pletion():
整个恳求处理完毕回调办法,该办法也是需求当前对应的Interceptor的preHandle()的回来值为true时才会履行,也便是在DispatcherServlet烘托了对应的视图之后履行。用于进行资源清理。
该阻拦器的履行流程为:
-
从 http 恳求头中取出 token; -
查看有没有需求用户权限的注解,} | ( ^假如需求,查验token是否为空; -
假如token不为空,查询用户信息并校验token; -
校验经过,则进行事务拜访处理,校验失利则回来token失效信息。
7.装备阻拦器
在装备类上增加了注解@Configuration
,标明晰该类是一个装备类并且会将该类作为一个Spring1 f B Y r ) ~ f 1Bean增加到IOC容器内。
@Configuration
publicclassInterceptorCon9 ) rfigimplementsWebMvcConfigurer{
@Bean
publicAuthenticationInterceptorauthenticationIw ` m Z ! W w 8nterceptor(){
returnnewAuthenticationInterceptor();
}
@Override
publicvoidaddInters I N Q $ o 0ceptors(InterceptorRegistryregistry){
//将咱们上步界说的完成了HandlerInterceptorI J D O h接口的阻拦器实例V b jauthenticationInterceptor增加InterceptorRegistration中,并设置过滤规矩,所有恳求都要经过authenticG B h I 9 4 z PationIntZ _ ( qerceptor阻拦。
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**) ^ V p x @ A d L");
}
}
We! } B x | kbMvcCl , ^ ` U : & aonfigurer
接口是Spring内部的一种装备方式,采用JavaBean的方式来替代传统的xml装备文件来完成根本的装备需求。
InterceptorConfig
内的a_ d . { y jddInterceptor
需o 8 q W f g求一个完成HandlerInterceptor
接口的阻拦器实例,addPathPatterns
办法用于设置阻拦器的过滤途径规矩。
在addInterceptors
办法中,咱U V G ] s l们将第6步界说的完成了HandlerInterceptor
接口的阻拦器实例authenticatioh O O D ~ G p Rnp Z { l q 9 + H JInterceptor
,增加至In / ] p + j 6 Zte[ x nrceptorRegistration
中,并设置过滤途径。现在,咱们所有恳求都要经过authenticationInterceptor
的阻拦,阻拦器authenticationT @ { z 7Interceptor
经过pre| ; c u V 6 V | hHandle
办法的事务过滤,判别是否有@TokenRequired
来决定是否需求登录。
8.界说接口办法并增加注解
@RestController
@RequestMapping("us* Z K 4 5 f Z |er")
publiccl5 X 3 I C 1assUser5 i H D Y 6 G , 4Controller{
@AutowireT F wd
UserServiceuserService;
/**
*用户登录
*@paramuser
*@return
*/
@PostMapping(= 0 g 8 i k "/login")
publicRe} l q p U c 0 8sultDTOlogin(Useruser){r U D m - [ + T
Stringtoken=userServic4 i De.login(user.getUsername(),user.getPassword());
if(token==null){
returnResultDTO.failure(newResultErr^ { # m 9or(UserError.PASSWORD_OR_NAME_IS_ERROR));
}
Map<String,String>tokenMap=newHashM Q 7ap<>();
tokenMap.put("token",token);
returnResultDTO.success(tokenMap);
}
@TokenRequired
@GetMapping("/hF H s - Tello")C A w n ] !
publicStringgetMessage(){
return"你好哇@ Q 1,我是小码仔";
}
}
不加注解的话默许不验证,登录接口一般是不验证的。所以我在getMessage()
中加上了登录注解,说明该接口有必要登录获取token后,在恳求头中加上token并经过验{ c B 8 1 R $ –证才能够拜访。
恳求验证
我在代码中对getMessage()
增加了@Tokeo b DnRequired
注解,此刻拜访该办x K O 0 b D 7 ( )法时有必要要经过登录拿取h K 到token值,F ) P ] z K Y –并在恳求头中增– 8 & g s `加token才能够拜访。咱们现在做以下校验:
如上图所示,恳求4 m W ;成果显现:无token,请从头登录。
-
拜访登录接口,获取token,并在恳求头中增加token信息:
此刻,拜访成功。 -
15分钟后,token失效,咱们再次在恳求头中增加token信息拜访:
此刻token已失效,回6 x U U j B a F h来:无效的令牌。
总结
回忆一下本7 I K r i b E次JWT运用的根本事务判别流程:
-
用户拜访页面,前端恳求相关接口,经过阻拦器,阻拦器中从 htte M hp 恳求头中取出 too W H Y kenZ K 4 y d; -
查看该接口有没有@TokenRequired注解,假如没有,直接放行P s } } A p T;假Q | o c ^ 8如有,查验token是否为空; -
假如token为空,拜访失利;token不为空,则查询用户信息并校验{ y P _ & q ,token; -
校验经过,则进行事务拜访处理,校验失利则回来token失效信息。
不足之处:本次集成仅仅做一个简略的JWT运用介绍,没有完成token的过期改写机Z _ | v + Y ` V制,此种情况下用户每隔15分钟就需求从头登, w | A X录一次R b x s 0 – X ,假如在实践生产Z % { : ( H环境中运用,可能会被用户打死,因此实践开发中并不引荐。
关于token的改写机制,小码仔O I / N g C g w ^将在下篇文章中为大家解读并附上源码。
本次集成代码地址:https://github.com/bailele1995/springboot-jjwt.git
最后
最后,感谢各位的阅览。文章的目的是记载和共享,若文中呈现显着纰漏也欢迎指出,咱们一起在讨论中学习。不胜感激 !
假如你觉得本文对你有用,那就给个「赞」吧,你的鼓励和支持是我行进路上的动力~
欢迎关注我的微信公众号【小码仔】,咱们一起讨论代码与人生。

文章参阅:
htts i q l Aps://my.oschina.net/odeh 7 ` X A ! [ – $tteisgorgeous/blog/1920762
https://ww[ ! 7 7 8w.t 1 ` J 3 – 9zhihu.com/search?type=content&q=spring%20boot%20jwt
https://mp.weixin.qq.com/s/q4uj 6 4 } RpZNTul5Z5WSq2wHC9lg
https://mp.weixin.qq.com/s/Vh-75A7qNS s Y8lDo_0DQXCkzg
https://mp.weixin.qq.com/s/KlXc5hWEfgj-Q9cMab4 V ? 8 x |eOdA
httW N x R j Wps://juejin.im/post/5e055d9ef265da33997a42cc