Springboot前后端分離項(xiàng)目配置跨域?qū)崿F(xiàn)過(guò)程解析
項(xiàng)目登錄流程如下
用戶進(jìn)入前端登錄界面,輸入賬號(hào)密碼等,輸入完成之后前端發(fā)送請(qǐng)求到后端(攔截器不會(huì)攔截登錄請(qǐng)求),后端驗(yàn)證賬號(hào)密碼等成功之后生成Token并存儲(chǔ)到數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)中包含該Token過(guò)期時(shí)間,然后返回生成的Token到前端。
前端收到Token,表示登錄成功,把這個(gè)Token存儲(chǔ)本地。然后跳轉(zhuǎn)到用戶中心頁(yè)面,用戶中心頁(yè)面在ajax的請(qǐng)求頭中帶上Token,跟隨請(qǐng)求用戶數(shù)據(jù)接口一起帶到后端。
后端通過(guò)攔截器攔截到這個(gè)請(qǐng)求,去判斷這個(gè)Token是否有效,有效就放過(guò)去做他該做的事情,無(wú)效就拋出異常。
跨域配置
先說(shuō)一下這個(gè)前后分離的項(xiàng)目,已經(jīng)配置過(guò)跨域這些問(wèn)題。我這里后端WebMvcConfig配置的方式如下:
import com.zdyl.devicemanagement.interceptor.AccessInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Resource private WebServerConfig webServerConfig; @Bean public AccessInterceptor getAccessInterceptor() { return new AccessInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { List<String> excludeUrl = new ArrayList<>(); excludeUrl.add("/error"); excludeUrl.add("/v1/zdyl/downloadFile"); excludeUrl.add("/v1/zdyl/lcoStation/qrcode/**"); excludeUrl.add("/devicemanagement/images/**/*"); excludeUrl.add("/upgrade/**"); excludeUrl.add("/v1/zdyl/login/**"); excludeUrl.add("/NewsImage/**"); excludeUrl.add("/v1/zdyl/equipment/alarm/toExcel/test"); excludeUrl.add("/v1/zdyl/deviceMonitoring/get/alarm/toExcel/**"); registry.addInterceptor(getAccessInterceptor()).addPathPatterns("/**") .excludePathPatterns(excludeUrl); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { List<String> locations = new ArrayList<String>(); locations.add("classpath:/META-INF/resources/"); locations.add("classpath:/resources/"); locations.add("classpath:/public/"); locations.add("file:" + webServerConfig.getUploadFileLocation()); locations.add("file:" + webServerConfig.getPicpath()); locations.add("file:" + webServerConfig.getProjectsource()); String[] myArray = new String[locations.size()]; registry.addResourceHandler("/**").addResourceLocations(locations.toArray(myArray)); } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedHeaders("*") .allowCredentials(true) .allowedOrigins("*") .allowedMethods("POST", "GET", "DELETE", "PUT", "OPTIONS") .maxAge(3600); } }
前端每次發(fā)送請(qǐng)求也都有在ajax里面設(shè)置xhrFields:{withCredentials: true}屬性。
攔截器代碼
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.zdyl.devicemanagement.common.exception.RRException; import com.zdyl.devicemanagement.common.utils.AccountNumber; import com.zdyl.devicemanagement.common.utils.RedisSavePrefix; import com.zdyl.devicemanagement.common.utils.RedisUtils; import com.zdyl.devicemanagement.common.utils.SystemConstants; import com.zdyl.devicemanagement.entity.LcoUsers; import com.zdyl.devicemanagement.entity.Login; import com.zdyl.devicemanagement.service.LcoUsersService; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; @Slf4j public class AccessInterceptor extends HandlerInterceptorAdapter { @Resource private RedisUtils redisUtils; @Resource private LcoUsersService lcoUsersService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("------------------------AccessInterceptor-------------------------"); if (request.getMethod().equals(RequestMethod.OPTIONS.name())) { return super.preHandle(request, response, handler); } //獲取請(qǐng)求token,如果token不存在,直接返回401 String token = getRequestToken(request); String loginId = getRequestloginId(request); if (StringUtils.isEmpty(token)) { throw new RRException("token為空", 401); } if (StringUtils.isEmpty(loginId)) { throw new RRException("loginId為空", 401); } Object users = redisUtils.getObject(redisUtils.getKey(RedisSavePrefix.Login, loginId), AccountNumber.loginDataBase); if (users == null) { throw new RRException("用戶尚未登錄", 401); } Login loginUser = JSONObject.parseObject(JSON.toJSONString(users), Login.class); if (!loginUser.getToken().equals(token)) { throw new RRException("token不匹配", 401); } Date loginTime = loginUser.getLoginTime(); long exitTime = loginTime.getTime() / 1000 + 7200; long time = new Date().getTime(); long nowTime = new Date().getTime() / 1000; if (nowTime > exitTime) { throw new RRException("token已過(guò)期!", 401); } QueryWrapper<LcoUsers> lcoUsersQueryWrapper = new QueryWrapper<>(); lcoUsersQueryWrapper.eq("phone", loginUser.getLoginID()); LcoUsers lcoUsers = lcoUsersService.getOne(lcoUsersQueryWrapper); request.setAttribute(SystemConstants.CURRENTUSER, lcoUsers); return super.preHandle(request, response, handler); } /** * 獲取請(qǐng)求的token */ private String getRequestToken(HttpServletRequest httpRequest) { //從header中獲取token String host = httpRequest.getHeader("token"); //如果header中不存在token,則從參數(shù)中獲取token if (StringUtils.isEmpty(host)) { host = httpRequest.getParameter("token"); } // if (StringUtils.isEmpty(host)) { // Cookie[] cks = httpRequest.getCookies(); // for (Cookie cookie : cks) { // if (cookie.getName().equals("yzjjwt")) { // host = cookie.getValue(); // return host; // } // } // } return host; } /** * 獲取請(qǐng)求的loginId */ private String getRequestloginId(HttpServletRequest httpRequest) { //從header中獲取token String loginId = httpRequest.getHeader("loginId"); //如果header中不存在token,則從參數(shù)中獲取token if (StringUtils.isEmpty(loginId)) { loginId = httpRequest.getParameter("loginId"); } // if (StringUtils.isEmpty(loginId)) { // Cookie[] cks = httpRequest.getCookies(); // for (Cookie cookie : cks) { // if (cookie.getName().equals("yzjjwt")) { // loginId = cookie.getValue(); // return loginId; // } // } // } return loginId; } /** * 對(duì)跨域提供支持 */ protected boolean addCors(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); // 跨域時(shí)會(huì)首先發(fā)送一個(gè)option請(qǐng)求,這里我們給option請(qǐng)求直接返回正常狀態(tài) if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { httpServletResponse.setStatus(HttpStatus.OK.value()); return false; } return super.preHandle(request, response); } }
自定義異常RRException代碼
/** * 自定義異常 */ public class RRException extends RuntimeException { private static final long serialVersionUID = 1L; private String message; private String code = "INVALID"; private int status = 0; public RRException(String msg) { super(msg); this.message = msg; } public RRException(String msg, Throwable e) { super(msg, e); this.message = msg; } public RRException(String msg, String code) { super(msg); this.message = msg; this.code = code; } public RRException(String msg, int status) { super(msg); this.message = msg; this.status = status; } public RRException(String msg, String code, Throwable e) { super(msg, e); this.message = msg; this.code = code; } public String getMsg() { return message; } public void setMsg(String msg) { this.message = msg; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)單鏈表倒轉(zhuǎn)的方法
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)單鏈表倒轉(zhuǎn)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05詳細(xì)總結(jié)Java堆棧內(nèi)存、堆外內(nèi)存、零拷貝淺析與代碼實(shí)現(xiàn)
零拷貝,這是個(gè)耳熟能詳?shù)拿~,是開(kāi)發(fā)崗面試中經(jīng)常提及的問(wèn)題.最近在回顧Netty的基礎(chǔ)原理,還是把NIO中關(guān)于堆外內(nèi)存的知識(shí)點(diǎn)過(guò)了一遍,這里就針對(duì)堆棧內(nèi)存 堆外內(nèi)存和零拷貝這幾個(gè)概念以及相關(guān)知識(shí)做一下記錄,需要的朋友可以參考下2021-05-05java多線程編程之使用runnable接口創(chuàng)建線程
實(shí)現(xiàn)Runnable接口的類必須使用Thread類的實(shí)例才能創(chuàng)建線程,通過(guò)Runnable接口創(chuàng)建線程分為以下兩步2014-01-01如何為?Spring?Boot?項(xiàng)目配置?Logback?日志
由于?Spring?Boot?的默認(rèn)日志框架選用的?Logback,再加上?Log4j2?之前爆過(guò)嚴(yán)重的漏洞,所以我們這次就只關(guān)注?Logback,本文重點(diǎn)給大家介紹如何為?Spring?Boot?項(xiàng)目配置?Logback?日志,感興趣的朋友跟隨小編一起看看吧2024-07-07Java ArrayList.toArray(T[]) 方法的參數(shù)類型是 T 而不是 E的原因分析
這篇文章主要介紹了Java ArrayList.toArray(T[]) 方法的參數(shù)類型是 T 而不是 E的原因分析的相關(guān)資料,需要的朋友可以參考下2016-04-04在spring中使用自定義注解注冊(cè)監(jiān)聽(tīng)器的方法
本篇文章主要介紹了在spring中使用自定義注解注冊(cè)監(jiān)聽(tīng)器的方法,本文就是在分析監(jiān)聽(tīng)器回調(diào)原理的基礎(chǔ)上,在spring環(huán)境中使用自定義的注解實(shí)現(xiàn)一個(gè)監(jiān)聽(tīng)器。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01