真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

前后端如何實現(xiàn)登錄token攔截校驗詳解

一、場景與環(huán)境

創(chuàng)新互聯(lián)是一家專注于網(wǎng)站制作、網(wǎng)站設(shè)計與策劃設(shè)計,讓胡路網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)十載,網(wǎng)設(shè)計領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:讓胡路等地區(qū)。讓胡路做網(wǎng)站價格咨詢:13518219792

最近需要寫一下前后端分離下的登錄解決方案,目前大多數(shù)都采用請求頭攜帶 Token 的形式

1、我是名小白web工作者,每天都為自己的將來擔(dān)心不已。第一次記錄日常開發(fā)中的過程,如有表達(dá)不當(dāng),還請一笑而過;

2、本實例開發(fā)環(huán)境前端采用 angular框架,后端采用 springboot框架;

3、實現(xiàn)的目的如下:

  a、前端實現(xiàn)登錄操作(無注冊功能);

  b、后端接收到登錄信息,生成有效期限token(后端算法生成的一段秘鑰),作為結(jié)果返回給前端;

  c、前端在此后的每次請求,都會攜帶token與后端校驗;

  d、在token有效時間內(nèi)前端的請求響應(yīng)都會成功,后端實時的更新token有效時間(暫無實現(xiàn)),如果token失效則返回登錄頁。

二、后端實現(xiàn)邏輯

注:部分代碼參考網(wǎng)上各個大神的資料

整個服務(wù)端項目結(jié)構(gòu)如下(登錄token攔截只是在此工程下的一部分,文章結(jié)尾會貼上工程地址):

前后端如何實現(xiàn)登錄token攔截校驗詳解

1、新增AccessToken 類 model

  在model文件下新增AccessToken.java,此model 類保存校驗token的信息:

/**
 * @param access_token token字段;
 * @param token_type token類型字段;
 * @param expires_in token 有效期字段;
 */
public class AccessToken {
 private String access_token;
 private String token_type;
 private long expires_in;

 public 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 = token_type;
 }

 public long getExpires_in() {
  return expires_in;
 }

 public void setExpires_in(long expires_in) {
  this.expires_in = expires_in;
 }
}

2、新增Audience 類 model

@ConfigurationProperties(prefix = "audience")
public class Audience {
 private String clientId;
 private String base64Secret;
 private String name;
 private int expiresSecond;

 public String getClientId() {
  return clientId;
 }

 public void setClientId(String clientId) {
  this.clientId = clientId;
 }

 public String getBase64Secret() {
  return base64Secret;
 }

 public void setBase64Secret(String base64Secret) {
  this.base64Secret = base64Secret;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public int getExpiresSecond() {
  return expiresSecond;
 }

 public void setExpiresSecond(int expiresSecond) {
  this.expiresSecond = expiresSecond;
 }
}

@ConfigurationProperties(prefix = "audience")獲取配置文件的信息(application.properties),如下:

server.port=8888
spring.profiles.active=dev
server.servlet.context-path=/movies

audience.clientId=098f6bcd4621d373cade4e832627b4f6
audience.base64Secret=MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY=
audience.name=xxx
audience.expiresSecond=1800

配置文件定義了端口號、根路徑和audience相關(guān)字段的信息,(audience也是根據(jù)網(wǎng)上資料命名的),audience的功能主要在第一次登錄時,生成有效token,然后將token的信息存入上述AccessToken類model中,方便登錄成功后校驗前端攜帶的token信息是否正確。

3、生成以jwt包的CreateTokenUtils 工具類

  下面對這個工具類的生成、功能進(jìn)行說明:

  a、首先在pom.xml文件中引用依賴(這和前端在package.json安裝npm包性質(zhì)相似)

 
  io.jsonwebtoken
  jjwt
  0.6.0
 

  b、然后再uitls文件夾下新增工具類CreateTokenUtils,代碼如下 :

public class CreateTokenUtils {
 private static Logger logger = LoggerFactory.getLogger(CreateTokenUtils.class);

 /**
  *
  * @param request
  * @return s;
  * @throws Exception
  */
 public static ReturnModel checkJWT(HttpServletRequest request,String base64Secret)throws Exception{
  Boolean b = null;
  String auth = request.getHeader("Authorization");
  if((auth != null) && (auth.length() > 4)){
   String HeadStr = auth.substring(0,3).toLowerCase();
   if(HeadStr.compareTo("mso") == 0){
    auth = auth.substring(4,auth.length());
    logger.info("claims:"+parseJWT(auth,base64Secret));
    Claims claims = parseJWT(auth,base64Secret);
    b = claims==null?false:true;
   }
  }
  if(b == false){
   logger.error("getUserInfoByRequest:"+ auth);
   return new ReturnModel(-1,b);
  }
  return new ReturnModel(0,b);
 }

 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;
  }
 }
 public static String createJWT(String name,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());

  JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
    .claim("unique_name", name)
    .setIssuer(issuer)
    .setAudience(audience)
    .signWith(signatureAlgorithm, signingKey);
  if (TTLMillis >= 0) {
   long expMillis = nowMillis + TTLMillis;
   Date exp = new Date(expMillis);
   builder.setExpiration(exp).setNotBefore(now);
  }
  return builder.compact();
 }
}

此工具類有三個 靜態(tài)方法:

 checkJWT—— 此方法在后端攔截器中使用,檢測前端發(fā)來的請求是否帶有token值

 createJWT——此方法在登陸接口中調(diào)用,首次登陸生成token值

 parseJWT——此方法在checkJWT中調(diào)用,解析token值,將jwt類型的token值分解成audience模塊

 可以在parseJWT方法中打斷點,查看Claims 對象,發(fā)現(xiàn)其字段存儲的值與audience對象值一一對應(yīng)。

注:Claims對象直接會將token的有效期進(jìn)行判斷是否過期,所以不需要再另寫相關(guān)時間比對邏輯,前端的帶來的時間與后臺的配置文件audience的audience.expiresSecond=1800 Claims對象會直接解析

4、攔截器的實現(xiàn)HTTPBasicAuthorizeHandler類的實現(xiàn)

在typesHandlers文件夾中新建HTTPBasicAuthorizeHandler類,代碼如下:

@WebFilter(filterName = "basicFilter",urlPatterns = "/*")
public class HTTPBasicAuthorizeHandler implements Filter {
 private static Logger logger = LoggerFactory.getLogger(HTTPBasicAuthorizeHandler.class);
 private static final Set ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("/person/exsit")));
 @Autowired
 private Audience audience;
 @Override
 public void init(FilterConfig filterConfig) throws ServletException {
  logger.info("filter is init");
 }
 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  logger.info("filter is start");
  try {
   logger.info("audience:"+audience.getBase64Secret());
   HttpServletRequest request = (HttpServletRequest) servletRequest;
   HttpServletResponse response = (HttpServletResponse) servletResponse;
   String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");
   logger.info("url:"+path);
   Boolean allowedPath = ALLOWED_PATHS.contains(path);
   if(allowedPath){
    filterChain.doFilter(servletRequest,servletResponse);
   }else {
    ReturnModel returnModel = CreateTokenUtils.checkJWT((HttpServletRequest)servletRequest,audience.getBase64Secret());
    if(returnModel.getCode() == 0){
     filterChain.doFilter(servletRequest,servletResponse);
    }else {
     // response.setCharacterEncoding("UTF-8");
//     response.setContentType("application/json; charset=utf-8");
//     response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
//     ReturnModel rm = new ReturnModel();
//     response.getWriter().print(rm);
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 @Override
 public void destroy() {
  logger.info("filter is destroy");
 }
}

此類繼承Filter類,所以重寫的三個方法init、doFitler、destory,重點攔截的功能在doFitler方法中:

 a、前端發(fā)來請求都會到這個方法,那么顯而易見,第一登陸請求肯定不能攔截,因為它不帶有token值,所以剔除登錄攔截這種情況:

private static final Set ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("/person/exsit")));

這里面的我的登錄接口路徑是“/person/exsit”,所以在將前端請求路徑分解:

String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");

兩者進(jìn)行如下比對:

Boolean allowedPath = ALLOWED_PATHS.contains(path);

根據(jù)allowedPath 的值進(jìn)行判斷是否攔截;

 b、攔截的時候調(diào)用上述工具類的checkJWT方法,判斷token是否有效:

ReturnModel returnModel = CreateTokenUtils.checkJWT((HttpServletRequest)servletRequest,audience.getBase64Secret());

ReturnModel 是我定義的返回類型結(jié)構(gòu),在model文件下;

 c、如果token無效,處理代碼注釋了:

前后端如何實現(xiàn)登錄token攔截校驗詳解

原因前端angular實現(xiàn)的攔截器和后端會沖突,導(dǎo)致前端代碼異常,后面會詳細(xì)說明。

 d、配置攔截器有兩種方法(這里只介紹一種):

前后端如何實現(xiàn)登錄token攔截校驗詳解

直接在攔截類上添加注釋的方法,urlPatterns是你過濾的路徑,還需在服務(wù)啟動的地方配置

前后端如何實現(xiàn)登錄token攔截校驗詳解

注:這里面過濾的路徑不包括配置文件的根路徑,比如說前端訪問接口路徑“/movies/people/exist”,這里面的movies是根路徑,在配置文件中配置,如果你想攔截這個路徑,則urlPatterns=”/people/exist“即可。

5、登錄類的實現(xiàn)

在controller文件夾中新建PersonController類,代碼如下

/**
 * Created by jdj on 2018/4/23.
 */
@RestController
@RequestMapping("/person")
public class PersonController {
 private final static Logger logger = LoggerFactory.getLogger(PersonController.class);
 @Autowired
 private PersonBll personBll;
 @Autowired
 private Audience audience;
 /**
  * @content:根據(jù)id對應(yīng)的person
  * @param id=1;
  * @return returnModel
  */
 @RequestMapping(value = "/exsit",method = RequestMethod.POST)
 public ReturnModel exsit(
   @RequestParam(value = "userName") String userName,
   @RequestParam(value = "passWord") String passWord
 ){
  String md5PassWord = Md5Utils.getMD5(passWord);
  String id = personBll.getPersonExist(userName,md5PassWord);
  if(id == null||id.length()<0){
   return new ReturnModel(-1,null);
  }else {
   Map map = new HashMap<>();
   Person person = personBll.getPerson(id);
   map.put("person",person);
   String accessToken = CreateTokenUtils
     .createJWT(userName,audience.getClientId(), audience.getName(),audience.getExpiresSecond() * 1000, audience.getBase64Secret());
   AccessToken accessTokenEntity = new AccessToken();
   accessTokenEntity.setAccess_token(accessToken);
   accessTokenEntity.setExpires_in(audience.getExpiresSecond());
   accessTokenEntity.setToken_type("bearer");
   map.put("accessToken",accessTokenEntity);
   return new ReturnModel(0,map);
  }
 }
 /**
  * @content:list
  * @param null;
  * @return returnModel
  */
 @RequestMapping(value = "/list",method = RequestMethod.GET)
 public ReturnModel list(){
  List list = personBll.selectAll();
  if(list.size()==0){
   return new ReturnModel(-1,null);
  }else {
   return new ReturnModel(0,list);
  }
 }

 @RequestMapping(value = "/item",method = RequestMethod.GET)
 public ReturnModel getItem(
   @RequestParam(value = "id") String id
 ){
  Person person = personBll.getPerson(id);
  if(person != null){
   return new ReturnModel(0,person);
  }else {
   return new ReturnModel(-1,"無此用戶");
  }
 }
}

前端調(diào)用這個類的接口路徑:“/movies/people/exist”

首先它會查詢數(shù)據(jù)庫

 String id = personBll.getPersonExist(userName,md5PassWord);

如果查詢存在,創(chuàng)建accessToken

 String accessToken = CreateTokenUtils
 .createJWT(userName,audience.getClientId(), audience.getName(),audience.getExpiresSecond() * 1000, audience.getBase64Secret());

最后整合返回到前端model

AccessToken accessTokenEntity = new AccessToken();
   accessTokenEntity.setAccess_token(accessToken);
   accessTokenEntity.setExpires_in(audience.getExpiresSecond());
   accessTokenEntity.setToken_type("bearer");
   map.put("accessToken",accessTokenEntity);
   return new ReturnModel(0,map);

這個controller類中還有兩個接口供前端登陸成功后調(diào)用。

以上都是服務(wù)端的實現(xiàn)邏輯,接下來說明前端的實現(xiàn)邏輯,我本身是前端小碼農(nóng),后端只是大多是不會的,如有錯誤,請一笑而過哈~_~哈

三、前端實現(xiàn)邏輯

前端使用angular框架,目錄如下

前后端如何實現(xiàn)登錄token攔截校驗詳解

上述app文件下common 存一些共同組建(分頁、彈框)、component存一些整體布局框架、
page是各個頁面組件,service是請求接口聚集地,shared是表單自定義校驗;所以這里面都有相關(guān)的angular2+表單校驗、http請求、分頁、angular動畫等各種實現(xiàn)邏輯。

1、前端http請求(確切的說httpClient請求)

所有的請求都在service文件夾service.service.ts文件中,代碼如下:

import { Injectable } from '@angular/core';
import { HttpClient,HttpHeaders } from "@angular/common/http";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/forkJoin';

@Injectable()
export class ServiceService {
 movies:string;
 httpOptions:Object;
 constructor(public http:HttpClient) {
 this.movies = "/movies";
 this.httpOptions = {
  headers:new HttpHeaders({
  'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8',
  }),
 }
 }
 /**登錄模塊開始*/
 loginMovies(body){
 const url = this.movies+"/person/exsit";
 const param = 'userName='+body.userName+"&passWord="+body.password;
 return this.http.post(url,param,this.httpOptions);
 }
 /**登錄模塊結(jié)束*/
 //首頁;
 getPersonItem(param){
 const url = this.movies+"/person/item";
 return this.http.get(url,{params:param});
 }
 //個人中心
 getPersonList(){
 const url = this.movies+"/person/list";
 return this.http.get(url);
 /**首頁模塊結(jié)束 */
}

上述有三個請求與后端personController類中三個接口方法一一對應(yīng),這里面的請求方式官網(wǎng)有,這里不做贅述,this.httpOptions是設(shè)置請求頭。然后再app.modules.ts中添加到provides,所謂的依賴注入,這樣就可以在各個頁面調(diào)用servcie方法了

 providers: [ServiceService,httpInterceptorProviders]

httpInterceptorProviders 是前端攔截器,前端每次請求結(jié)果都會出現(xiàn)成功或者錯誤,所以在攔截器中統(tǒng)一處理返回結(jié)果使代碼更簡潔。

2、前端攔截器的實現(xiàn)

在app文件在新建InterceptorService.ts文件,代碼如下:

import { Injectable } from '@angular/core';
import { HttpEvent,HttpInterceptor,HttpHandler,HttpRequest,HttpResponse} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
import { mergeMap } from 'rxjs/operators';
import {Router} from '@angular/router';

@Injectable()
export class InterceptorService implements HttpInterceptor{
 constructor(
  private router:Router,
 ){ }
 authorization:string = "";
 authReq:any;
 intercept(req:HttpRequest,next:HttpHandler):Observable>{
  this.authorization = "mso " + localStorage.getItem("accessToken");
  
  if (req.url.indexOf('/person/exsit') === -1) {
   this.authReq = req.clone({
    url:req.url,
    headers:req.headers.set("Authorization",this.authorization)
   });
  }else{
   this.authReq = req.clone({
    url:req.url,
   });
  }
  return next.handle(this.authReq).pipe(mergeMap((event:any) => {
   if(event instanceof HttpResponse && event.body === null){
    return this.handleData(event);
   }
   return Observable.create(observer => observer.next(event));
  }));
 }
 private handleData(event: HttpResponse): Observable {
  // 業(yè)務(wù)處理:一些通用操作
  switch (event.status) {
   case 200:
   if (event instanceof HttpResponse) {
    const body: any = event.body;
    if (body === null) {
     this.backForLoginOut();
    }
   }
   break;
   case 401: // 未登錄狀態(tài)碼
   this.backForLoginOut();
   break;
   case 404:
   case 500:
   break;
   default:
   return ErrorObservable.create(event);
  }
 }
 private backForLoginOut(){
  if(localStorage.getItem("accessToken") !== null || localStorage.getItem("person")!== null){
   localStorage.removeItem("accessToken");
   localStorage.removeItem("person");
  }
   if(localStorage.getItem("accessToken") === null && localStorage.getItem("person") === null){
   this.router.navigateByUrl('/login');
  }
 }
}

攔截器的實現(xiàn)官網(wǎng)也詳細(xì)說明了,但是攔截器有幾大坑:

  a、如果用的是angular2,你請求是采用的是import { Http } from "@angular/http"包http,那么攔截器無效,你可能需要另一種寫法了,angular4、5、6都是采用import { HttpClient,HttpHeaders } from "@angular/common/http"包下HttpClient和請求頭HttpHeaders ;

  b、攔截器返回結(jié)果的方法中:

return next.handle(this.authReq).pipe(mergeMap((event:any) => {
   if(event instanceof HttpResponse && event.body === null){
    return this.handleData(event);
   }
   return Observable.create(observer => observer.next(event));
  }));

打斷點查看這個方法一次請求會循環(huán)兩次,第一次event:{type:0} ,第二次才會返回對象,截圖如下:

第一次

前后端如何實現(xiàn)登錄token攔截校驗詳解

第二次

前后端如何實現(xiàn)登錄token攔截校驗詳解

但是如果以我上述后端攔截器token無效的情況處理代碼(就是我注釋的那段代碼,我注釋的代碼重點的作用是返回401,可以回看),這個邏輯只循環(huán)一次,所以我將后端代碼返回token無效的代碼注釋,前端攔截器在后端代碼注釋的情況下第二次返回的event結(jié)果體存在event.body=== null ,以這個條件進(jìn)行token是否有效判斷;

  c、攔截器使用rxjs,如果你在頁面請求中使用rxjs中Observable.forkJoin()方法進(jìn)行并發(fā)請求,那么不好意思,好像無效,如果你有辦法解決這兩個不沖突,請告訴我哈。

  d、這里面也要剔除登陸的攔截,具體看代碼。

3、登錄效果

以上的邏輯都是實現(xiàn)過程,下面來看下整體的效果:

登陸邏輯中我用的是localStorage存儲token值的:

前后端如何實現(xiàn)登錄token攔截校驗詳解

點擊登錄會先到前端攔截器,然后直接跳到else

前后端如何實現(xiàn)登錄token攔截校驗詳解

前后端如何實現(xiàn)登錄token攔截校驗詳解

接著到后端服務(wù)攔截器

前后端如何實現(xiàn)登錄token攔截校驗詳解

過濾登陸接口,直接跳到登陸接口,創(chuàng)建token值并返回

前后端如何實現(xiàn)登錄token攔截校驗詳解

觀察返回的map值

前后端如何實現(xiàn)登錄token攔截校驗詳解

最后返回前端界面

前后端如何實現(xiàn)登錄token攔截校驗詳解

上面的返回結(jié)果與后端對應(yīng),登錄成功后,再請求其他頁面會攜帶token值

前后端如何實現(xiàn)登錄token攔截校驗詳解

以上就是關(guān)于前后端分離登錄校驗,還有一步?jīng)]有完成,就是token更新時間有效期,等抽時間再補(bǔ)充,上述代碼后端用idea編輯器,后端服務(wù)搭建會涉及到很多配置。

上面實現(xiàn)的代碼github地址如下:github.com/yuelinghuny… (本地下載)

麻煩各位給我點個贊,第一次寫記錄文檔,我會堅持寫下去,會堅信越來越好,謝謝。

總結(jié):

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對創(chuàng)新互聯(lián)的支持。


當(dāng)前題目:前后端如何實現(xiàn)登錄token攔截校驗詳解
文章地址:http://weahome.cn/article/pjsceh.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部