手机版

前后端如何实现登录代币拦截校验详解

时间:2021-08-30 来源:互联网 编辑:宝哥软件园 浏览:

一、场景与环境

最近需要写一下前后端分离下的登录解决方案,目前大多数都采用请求头携带代币的形式

1、我是名小白网工作者,每天都为自己的将来担心不已。第一次记录日常开发中的过程,如有表达不当,还请一笑而过;

2、本实例开发环境前端采用有角的框架,后端采用跳羚框架;

3、实现的目的如下:

答:前端实现登录操作(无注册功能);

b、后端接收到登录信息,生成有效期限令牌(后端算法生成的一段秘钥),作为结果返回给前端;

c 、前端在此后的每次请求,都会携带代币与后端校验;

d、在代币有效时间内前端的请求响应都会成功,后端实时的更新代币有效时间(暂无实现),如果代币失效则返回登录页。

二、后端实现逻辑

注:部分代码参考网上各个大神的资料

整个服务端项目结构如下(登录代币拦截只是在此工程下的一部分,文章结尾会贴上工程地址):

1、新增访问令牌类模型

在模型文件下新增AccessToken.java,此模型类保存校验代币的信息:

/** * @param access_token字段;* @param token_type标记类型字段;* @param expires_in令牌有效期字段;*/public类访问令牌{私有String access _ token私有字符串标记类型;private long expires _ input blic String GetACCESS _ token(){ return access _ token;} public void setAccess _ token(String access _ token){ this。access _ token=access _ token} public String getToken _ type(){ return token _ type;} public void setToken _ type(String token _ type){ this。token _ type=令牌_ type;} public long getExpires _ in(){ return expires _ in;} public void setExpires _ in(long expires _ in){ this。expires _ in=expires _ in}}2、新增观众类模型

@ConfigurationProperties(前缀='接受')公共类接受{私有字符串客户端id;私有字符串base64密码私有字符串名称;private int expiresecondpublic String get client id(){返回客户端id;} public void set client id(String client id){ this。客户端id=客户端id;} public String getbase64 secret(){ return base64 secret;} public void setbase64 secret(String base64 secret){ this。base64秘密=base64秘密;} public String getName(){ return name;} public void setName(String name){ this。name=name} public int getexpiresecond(){ return expiresecond;} public void setexpiresecond(int expiresecond){ this。expiresecond=expiresecond}}@ConfigurationProperties(前缀="受众")获取配置文件的信息(application.properties),如下:

服务器。端口=8888弹簧。个人资料。active=devserver。servlet。context-path=/电影观众。客户id=098 F6 BCD 4621d 373 cade 4 e 832627 B4 F6观众。base64 secret=mdk 4 ZJ zi2 q 0 njixzdm3m 2 nhz gu 0 ztgzmjyy 2 i0 zjy=account。name=xxxaccount。expiresecond=1800配置文件定义了端口号、根路径和观众相关字段的信息,(观众也是根据网上资料命名的),观众的功能主要在第一次登录时,生成有效令牌,然后将代币的信息存入上述访问令牌类模型中,方便登录成功后校验前端携带的代币信息是否正确。

3、生成以智威汤逊广告公司包的CreateTokenUtils工具类

下面对这个工具类的生成、功能进行说明:

答:首先在pom.xml文件中引用依赖(这和前端在package.json安装新公共管理包性质相似)

依赖项groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId 0。6 .0版/版本/依赖项b,然后再uitls文件夹下新增工具类CreateTokenUtils,代码如下:

公共类CreateTokenUtils {私有静态记录器记录器=记录器工厂。getlogger(CreateTokenUtils。类);/** * * @param请求* @返回s;* @抛出异常*/public static ReturnModel checkJWT(httprsvletrequest请求,String base64Secret)抛出异常{布尔b=nullstring auth=request。getheader('授权');if(((auth!=null)(auth。length()4)){ String HeAdtr=auth。子串(0,3).toLowerCase();if(HeAdtr。比较(' MSO '==0){ auth=auth。子串(4,auth。length());伐木工。信息('声明s : ' parseJWT(auth,base64 secret));声明声明=parseJWT(auth,base64 secret);b=索赔==null?false : true } } if(b==false){ logger。错误(' getuserinfobyrequest : ' auth);返回新的ReturnModel(-1,b);}返回新的ReturnModel(0,b);}公共静态声明解析器(String jsonWebToken,String base64 security){尝试{声明=jwts。解析器().setSigningKey(数据类型转换器。parsebase64二进制(base64安全性)).parseClaimsJws(jsonWebToken).getBody();退货索赔;}捕获(例外情况){ 0返回null} }公共静态字符串createJWT(字符串名称、字符串受众、字符串颁发者、长TTLMillis、字符串base64安全性){签名算法签名算法=签名算法.HS256long现在毫=系统。当前timemillis();现在日期=新日期(现在毫秒);byte[]API key secret bytes=datatype converter。parsebase64二进制(base64安全);密钥签名密钥=新的secretkey规范(apikey secretbytes,signaturealgorithm。getjcaname());JwtBuilder builder=Jwts.builder().setHeaderParam('典型值,' JWT ' .声明(“唯一名称”,名称)。设置发卡行(发卡行)。集合受众(观众)。签名用(签名算法,签名密钥);if(TTL毫=0){ long expMillis=now毫TTL毫;日期exp=新日期(ex pm illis);builder.setExpiration ).setNotBefore(现在);}返回生成器。compact();}}此工具类有三个静态方法:

checkJWT——此方法在后端拦截器中使用,检测前端发来的请求是否带有代币值

createJWT——此方法在登陆接口中调用,首次登陆生成代币值

parseJWT——此方法在checkJWT中调用,解析代币值,将智威汤逊广告公司类型的代币值分解成观众模块

可以在parseJWT方法中打断点,查看要求对象,发现其字段存储的值与观众对象值一一对应。

注:索赔对象直接会将代币的有效期进行判断是否过期,所以不需要再另写相关时间比对逻辑,前端的带来的时间与后台的配置文件观众的观众。解释秒=1800个索赔对象会直接解析

4、拦截器的实现HTTPBasicAuthorizeHandler类的实现

在类型处理程序文件夹中新建HTTPBasicAuthorizeHandler类,代码如下:

@ WebFilter(筛选器名称=' basicFilter ',urlPatterns='/*)公共类HTTPBasicAuthorizeHandler实现过滤器{私有静态记录器=记录器工厂。getlogger(httpbascauthorizhandler。类);私有静态最终集字符串ALLOPED _ PATH=collections。unmodifiebleset(新的Hashset(数组。aslist('/person/exsit '));@自动连线私人观众;@覆盖公共void init(过滤器配置过滤器配置)引发ServletException { logger。info(' filter is init ');} @ Override public void doFilter(servlet请求servlet请求,ServletResponse servletResponse,FilterChain filterChain)引发IOException,ServletException { logger。信息(“过滤器已启动”);请尝试{记录器。信息('观众: '帐户。getbase64 secret());httpersvletrequest请求=(httpersvletrequest)servlet请求;httpersvletresponse=(httpersvletresponse)servlet响应;字符串路径=request.getRequestURI().子字符串(请求。getcontextpath().长度()).replaceAll('[/] $ ',' ');logger.info('url: '路径);布尔ALlowedPATH=ALLOPED _ PATH。包含(路径);if(allowedPath){筛选器链。dofilter(servlet请求,servlet响应);} else { return model return model=createtokenutils。checkjwt((httprsvletrequest)servlet请求,帐户。getbase64 secret());if(返回模型。getcode()==0){筛选器链。dofilter(servlet请求,servlet响应);} else {//响应。setcharacter encoding(' UTF-8 ');//响应。setcontenttype(' application/JSON;charset=utf-8 ');//响应。setstatus(HttpServletResponse .SC _ UNAUTHORIZED);//返回模型RM=新的返回模型();//response.getWriter().打印(RM);} } } catch(异常e){ e . print stack trace();} } @ Override public void destroy(){ logger。信息('筛选器被破坏');}}此类继承过滤器类,所以重写的三个方法init、doFitler、destory、重点拦截的功能在doFitler方法中:

答:前端发来请求都会到这个方法,那么显而易见,第一登陆请求肯定不能拦截,因为它不带有代币值,所以剔除登录拦截这种情况:

私有静态最终集字符串ALLOPED _ PATH=collections。unmodifiebleset(新的Hashset(数组。aslist('/person/exsit '));这里面的我的登录接口路径是"/人/存在",所以在将前端请求路径分解:

字符串路径=request.getRequestURI().子字符串(请求。getcontextpath().长度()).replaceAll('[/] $ ',' ');两者进行如下比对:

布尔ALlowedPATH=ALLOPED _ PATH。包含(路径);根据允许路径的值进行判断是否拦截;

b、拦截的时候调用上述工具类的checkJWT方法,判断代币是否有效:

返回模型返回模型=createtokenutils。checkjwt((HttpServletrequest)servlet请求,帐户。getbase64 secret());ReturnModel是我定义的返回类型结构,在模型文件下;

c 、如果代币无效,处理代码注释了:

原因前端有角的实现的拦截器和后端会冲突,导致前端代码异常,后面会详细说明。

d、配置拦截器有两种方法(这里只介绍一种):

直接在拦截类上添加注释的方法,urlPatterns是你过滤的路径,还需在服务启动的地方配置

注:这里面过滤的路径不包括配置文件的根路径,比如说前端访问接口路径"/电影/人/存在",这里面的电影是根路径,在配置文件中配置,如果你想拦截这个路径,则urlPatterns="人/存在"即可。

5、登录类的实现

在控制器文件夹中新建人员控制器类,代码如下

/** *由海南省居住建筑节能设计标准于2018年四月23日创建*/@ rest controller @请求映射(“/person”)公共类个人控制器{私有最终静态Logger=Logger工厂。getlogger(个人控制器。类);@ auto wired private PersonBll PersonBll;@自动连线私人观众;/** * @content:根据编号对应的person * @ param id=1;* @返回模型*/@请求映射(值='/exsit ',方法=RequestMethod .POST)公共返回模型exsit(@RequestParam(值='userName ')字符串userName,@请求参数(值=' PassPort ')String PassPort){ String MD5 PassPort=MD5 utils。getmd5(PassPort);字符串id=personbll。getpersonexist(用户名,MD5密码);if(id==null | | id . length()0){ 0返回new ReturnModel(-1,null);}else { MapString,Object map=new HashMap();person person=personbll。GetPeer(id);map.put('person ',person);字符串访问令牌=CreateTokenUtils .createJWT(userName,accessor . getclient id(),访问器。getname(),访问器。getexpiresecond()* 1000,访问器。getbase64 secret());AccessToken accessTokenEntity=new AccessToken();访问令牌实体。setaccess _ token(访问令牌);accesstokenentity。setexpires _ in(帐户。getexpiresecond());accesstokenentity。settoken _ type('承载');map.put('accessToken ',AccessTokeEntity);返回新的ReturnModel(0,映射);} }/* * * @内容:列表* @ param null* @返回模型*/@请求映射(值='/list ',方法=RequestMethod .GET)公共返回模型列表(){ listener list=personbll。selectall();if(list . size()==0){ 0返回new ReturnModel(-1,null);}else {返回新的ReturnModel(0,列表);} } @RequestMapping(值='/item ',方法=RequestMethod .GET)公共返回模型getItem(@ request param(value=' id ')String id){ Person Person=Person bll。get person(id);如果(人!=null){ 0返回新的ReturnModel(0,人);}else {返回新的ReturnModel(-1,)无此用户');} }}前端调用这个类的接口路径:/电影/人物/存在

首先它会查询数据库

字符串id=personbll。getpersonexist(用户名,MD5密码);如果查询存在,创建访问令牌

字符串访问令牌=CreateTokenUtils .createJWT(userName,accessor . getclient id(),访问器。getname(),访问器。getexpiresecond()* 1000,访问器。getbase64 secret());最后整合返回到前端模型

AccessToken accessTokenEntity=new AccessToken();访问令牌实体。setaccess _ token(访问令牌);accesstokenentity。setexpires _ in(帐户。getexpiresecond());accesstokenentity。settoken _ type('承载');map.put('accessToken ',AccessTokeEntity);返回新的ReturnModel(0,映射);这个控制器类中还有两个接口供前端登陆成功后调用。

以上都是服务端的实现逻辑,接下来说明前端的实现逻辑,我本身是前端小码农,后端只是大多是不会的,如有错误,请一笑而过哈~_~哈

三、前端实现逻辑

前端使用有角的框架,目录如下

上述应用文件下普通存一些共同组建(分页、弹框)、组件存一些整体布局框架、页面是各个页面组件服务是请求接口聚集地,共享是表单自定义校验;所以这里面都有相关的angular2表单校验、http请求、分页、有角度的动画等各种实现逻辑。

1、前端超文本传送协议(超文本传输协议的缩写)请求(确切的说客户端请求)

所有的请求都在服务文件夹service.service.ts文件中,代码如下:

从“@棱角分明/核心”导入{内射};从" @angular/common/http "导入{ HttpClient,Httpaders };从“rxjs/天文台”导入{天文台};导入”rxjs/add/operator/map”;导入" rxjs/add/observable/forkJoin ";@可注射()导出类ServiCeServiCe { movies :字符串;httpOptions:Object构造函数(公共http : Http客户端){这。电影='/movies ';这个。httpoptions={ header RS : new httpaders({ ' Content-Type ' : ' application/x-www-form-URL编码;charset=UTF-8 ',}),} } /**登录模块开始*/loginMovies(body){ const URL=this。电影/人物/存在;const param=' userName=' body。userName ' PassPort='正文。PassPort返回this.http.post(url,param,this。HttpOptions);} /**登录模块结束*///首页;getpersonatem(param){ const URL=this。电影/人物/物品;返回this.http.get(url,{ params 3360 param });} //个人中心getPersonList(){ const URL=this。电影/“人物/名单”;返回这个。http。获取(网址);/**首页模块结束*/}上述有三个请求与后端人员控制器类中三个接口方法一一对应,这里面的请求方式官网有,这里不做赘述,this.httpOptions是设置请求头。然后再app.modules.ts中添加到提供,所谓的依赖注入,这样就可以在各个页面调用业务层方法了

提供商:[服务服务,Httpinterceptorproviders]Httpinterceptorproviders是前端拦截器,前端每次请求结果都会出现成功或者错误,所以在拦截器中统一处理返回结果使代码更简洁。

2、前端拦截器的实现

在应用文件在新建拦截服务文件,代码如下:

从“@棱角分明/核心”导入{内射};从" @angular/common/http "导入{ HttpEvent、HttpInterceptor、HttpHandler、HttpRequest、Httpreresponse };从“rxjs/天文台”导入{天文台};从“rxjs/观测站/误差观测站”导入{错误观察站};从“rxjs/运算符”导入{合并地图};从" @angular/router "导入{路由器};@可注射()导出类拦截服务实现了httppinterceptor {构造函数(私有router:Router),{ } authorization : string=authreq : anyiintercept(req : Httprequestany,next : Httphandler): observableHttpeventany { this。授权=' MSO '本地存储。getitem(' AccessToken ');如果(请求。网址。index of('/person/ex sit ')===-1){ this。authreq=req。克隆({ URL :请求。网址,报头RS :请求。标题。设置('授权',这个。授权)});} else { this。AuthReq=req。克隆({ URL :请求。URL,});}返回next.handle(this.authReq).pipe(合并映射)((事件: any)={ if(Httpresponse事件的事件实例。body===null){返回这个。handledata(事件);}返回天文台。创建(观察者=观察者。下一个(事件));}));}私有handleData(事件: Httpreresponse any):可观察到ny {//业务处理:一些通用操作开关(事件。status){ case 200: if(Httpresponse的事件实例){ const body : any=event。身体;if(body===null){ this。返回ginout();} }休息第401: //未登录状态码这个。返回ginout();打破;案例404:案例500:中断;默认值:返回ErrorObservable.create(事件);} } ginout(){ if(本地存储)的私有back。getitem(' accessToken ')!==null | |本地存储。getitem(' person ')!==null){本地存储。移除项目(' AccessToken ');本地存储。删除项目(“人”);} if(本地存储。getitem(' accessToken ')==null本地存储。getitem(' person ')==null){ this。路由器。navigatebyurl('/log in ');} }}拦截器的实现官网也详细说明了,但是拦截器有几大坑:

答:如果用的是angular2,你请求是采用的是从" @angular/http "导入{ Http }包http,那么拦截器无效,你可能需要另一种写法了,angular4、5、6都是采用从" @angular/common/http "导入{ HttpClient,HttpHeaders }包下客户端和请求头HttpHeaders

b、拦截器返回结果的方法中:

返回next.handle(this.authReq)。pipe(merge map)((event : any)={ if(event instance of Httpresponse event . body===null){ return this . handledata(event);}返回Observatory . create(observer=observer . next(event));}));中断点查看此方法,一旦请求将循环两次,第一次事件:{type:0}将第二次返回对象,截图如下:

第一次

第二次

但是如果在我的后端拦截器令牌无效的情况下处理代码(也就是我注释的代码,我注释代码的关键功能是返回401,可以回头看),这个逻辑只循环一次,所以我会把令牌无效的代码注释返回给后端代码,前端拦截器第二次返回的事件结果存在event.body===null在后端代码注释的情况下,所以可以根据这个条件来判断令牌是否有效;

C.拦截器使用rjs。如果在rjs中使用Observable.forkJoin()方法进行并发请求,那么很抱歉,它似乎是无效的。如果你有办法解决这两个矛盾,请告诉我。

d、它还应该消除对着陆的拦截,这取决于代码。

3.登录效果

以上逻辑就是实现过程。我们来看看整体效果:

在登录逻辑中,我使用本地存储来存储令牌值:

点击登录会先进入前端拦截器,然后直接跳转到else

然后到后端服务拦截器

过滤登录界面,直接跳转到登录界面,创建令牌值并返回

观察返回的地图值

最后,回到前面的界面

上面返回的结果对应于后端。成功登录后,请求其他页面将携带令牌值

以上是关于前端分离的登录验证,但是还有一个未完成的步骤,就是令牌会在有效期内进行更新,时间画出来后会进行补充。上述代码的后端使用了idea编辑器,后端服务构建会涉及到很多配置。

上面实现的代码的github地址如下:github.com/yuelinghuny…(本地下载)

请称赞我。当我第一次写记录文件时,我会继续写下去。我会坚信它越来越好。谢谢你。

总结:

以上就是本文的全部内容。希望本文的内容对大家的学习或工作有一定的参考价值。有问题可以留言交流。谢谢你的支持。

版权声明:前后端如何实现登录代币拦截校验详解是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。