SpringBoot解決跨域?qū)е聅essionId不一致的實(shí)現(xiàn)方式
SpringBoot解決跨域?qū)е聅essionId不一致
在用谷歌的kaptcha做驗(yàn)證碼登錄校驗(yàn),將后端發(fā)布到阿里云,前端是本地啟動(dòng),用谷歌瀏覽器(版本85)訪問驗(yàn)證碼遇到了如下問題(360瀏覽器、microsoft edge未重現(xiàn))


可以定位到是瀏覽器兼容問題
代碼是這樣的:
后端先用HttpServletRequest request的getSession().setAttribute將驗(yàn)證碼存進(jìn)session,請(qǐng)求登錄的時(shí)候再用request.getSession().getAttribute來判斷,然后發(fā)現(xiàn)請(qǐng)求驗(yàn)證碼的sessionId跟請(qǐng)求登錄的sessionId不一致,導(dǎo)致提示驗(yàn)證碼一直失效。
如下為獲取驗(yàn)證碼的接口
@ApiOperation(value = "獲取驗(yàn)證碼", notes = "此接口用于獲取驗(yàn)證碼")
@GetMapping("captcha.jpg")
public void captcha(HttpServletResponse response, HttpServletRequest request) throws ServletException, IOException {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
// 生成文字驗(yàn)證碼
String text = producer.createText();
// 生成圖片驗(yàn)證碼
BufferedImage image = producer.createImage(text);
// 保存到驗(yàn)證碼到 session
System.out.println("=============================");
request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, text);
System.out.println("生成文字驗(yàn)證碼:" + text);
System.out.println("獲取驗(yàn)證碼 session:" + request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY));
System.out.println("獲取驗(yàn)證碼 request.getSession().getId():" + request.getSession().getId());
System.out.println("=============================");
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "jpg", out);
IOUtils.closeQuietly(out);
}登錄的部分接口
@ApiOperation(value = "系統(tǒng)登錄", notes = "此接口用于系統(tǒng)登錄")
@PostMapping(value = "/login")
public ApiResponses login(@RequestBody LoginParam loginPARAM, HttpServletRequest request) {
String username = loginPARAM.getUsername();
String password = loginPARAM.getPassword();
String captcha = loginPARAM.getCaptcha();
System.out.println("=============================");
System.out.println("系統(tǒng)登錄時(shí) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY):" + request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY));
System.out.println("系統(tǒng)登錄時(shí) request.getSession().getId():" + request.getSession().getId());
。
。
.
}這種方式請(qǐng)求驗(yàn)證碼的時(shí)候會(huì)帶cookie給前端,如下所示,JSESSIONID就是后端的request.getSession().getId(),登錄的時(shí)候如果設(shè)置了跨域,前端會(huì)將JSESSIONID返回給后端,后端會(huì)進(jìn)行判斷。但是現(xiàn)在的問題就是兩次的sessionId不一致。所以還是要檢查是否設(shè)置對(duì)了跨域

檢查后端設(shè)置的跨域
這是我的跨域配置類,需要注意的是
當(dāng)allowCredentials為true時(shí)
allowedOrigins盡量不要設(shè)置為 *
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 允許跨域訪問的路徑
registry.addMapping("/**")
// 允許跨域訪問的源
.allowedOrigins("http://服務(wù)器Ip:9528","http://服務(wù)器Ip:9001")
// 允許請(qǐng)求方法
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
// 預(yù)檢間隔時(shí)間
.maxAge(168000)
// 允許頭部設(shè)置
.allowedHeaders("*")
// 是否發(fā)送cookie
.allowCredentials(true);
}
}前端設(shè)置的跨域
前端設(shè)置跨域主要為:axios.defaults.withCredentials = true,然后此項(xiàng)目前端如下
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})尋思著,這樣配置也沒問題吧。
經(jīng)過度娘的助攻,終于找出了問題的源頭
新版的chrome,加強(qiáng)了防止CSRF攻擊,需要設(shè)置Cookie的SameSite屬性
SameSite的值可以填3個(gè):Strict,Lax,None.
缺省的值為Lax,而且當(dāng)你設(shè)置其為空時(shí),在新的Chrome中還是會(huì)給予默認(rèn)值Lax.
3個(gè)模式的介紹
- Strict:嚴(yán)格模式
- Lax:寬松模式
- None:可以在第三方環(huán)境中發(fā)送cookie
在這種模式下,必須同時(shí)啟用Secure才行
似乎看到了黎明的曙光,上后端代碼
@Configuration
public class SpringSessionConfig {
// 最新的chrome,設(shè)置null會(huì)默認(rèn)成lax 但是如果設(shè)置samesite為NONE,又需要設(shè)置secure。https支持secure,http不行
@Bean
public CookieSerializer httpSessionIdResolver() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setUseHttpOnlyCookie(false);
cookieSerializer.setSameSite("None");
cookieSerializer.setCookiePath("/");
cookieSerializer.setUseSecureCookie(true);
return cookieSerializer;
}
}然后將后端繼續(xù)發(fā)布到阿里云,然而的然而 還是翻車了。。。
再次尋求百度,然后發(fā)現(xiàn)要滿足https +SameSite("None") +SecureCookie(true)
三者條件才能在高版本的谷歌瀏覽器訪問
但是阿里云是http,那怎么辦呢
還有一種解決方法
棄用通過session校驗(yàn),可以引入redis來做判斷
上代碼:
@Autowired
private RedisTemplate redisTemplate;
@ApiOperation(value = "獲取驗(yàn)證碼", notes = "此接口用于獲取驗(yàn)證碼")
@GetMapping("captcha.jpg")
public void captcha(HttpServletResponse response, HttpServletRequest request) throws ServletException, IOException {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
// 生成文字驗(yàn)證碼
String text = producer.createText();
// 生成圖片驗(yàn)證碼
BufferedImage image = producer.createImage(text);
// 保存到驗(yàn)證碼到 redis 設(shè)置1分鐘過期
redisTemplate.opsForValue().set(Constants.KAPTCHA_SESSION_KEY,text,1, TimeUnit.MINUTES);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "jpg", out);
IOUtils.closeQuietly(out);
}
@ApiOperation(value = "系統(tǒng)登錄", notes = "此接口用于系統(tǒng)登錄")
@PostMapping(value = "/login")
public ApiResponses login(@RequestBody LoginParam loginPARAM, HttpServletRequest request) {
String username = loginPARAM.getUsername();
String password = loginPARAM.getPassword();
String captcha = loginPARAM.getCaptcha();
Object kaptcha = redisTemplate.opsForValue().get(Constants.KAPTCHA_SESSION_KEY);
。
。
。
}這樣就可以解決啦
問題調(diào)整
用如上方法寫驗(yàn)證碼會(huì)有一種問題,就是當(dāng)多個(gè)用戶同時(shí)請(qǐng)求獲取驗(yàn)證碼,其中先獲取驗(yàn)證碼的人就會(huì)失效。然后做了如下改進(jìn)
我棄用了谷歌的kaptcha,重寫了驗(yàn)證碼。給redis set值的時(shí)候同時(shí)加上一個(gè)token,登錄的時(shí)候需要返回token來驗(yàn)證
續(xù)上部分代碼
/**
* 生成驗(yàn)證碼
*
* @return
*/
public CaptchaDTO getCaptcha() {
//1.在內(nèi)存中創(chuàng)建一張圖片
BufferedImage bi = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
// 畫布顏色數(shù)組
Color[] colors = new Color[]{Color.BLUE, Color.CYAN, Color.GRAY, Color.GREEN, Color.ORANGE, Color.RED, Color.BLACK};
//2.得到圖片
Graphics g = bi.getGraphics();
//3.設(shè)置圖片的背影色
setBackGround(g, WIDTH, HEIGHT);
//4.設(shè)置圖片的邊框
//setBorder(g,width,height);
//5.在圖片上畫干擾線
drawRandomLine(g, colors, WIDTH, HEIGHT);
String random = drawRandomNum((Graphics2D) g, colors);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ImageIO.write(bi, "jpg", outputStream);
} catch (IOException e) {
e.printStackTrace();
}
// 對(duì)字節(jié)數(shù)組Base64編碼
BASE64Encoder encoder = new BASE64Encoder();
String imageCode = encoder.encode(outputStream.toByteArray()).replaceAll("\r|\n", "");
String token = JwtTokenUtils.generateCheckCode(random);
CaptchaDTO captchaDTO = new CaptchaDTO();
captchaDTO.setCodeToken(token);
captchaDTO.setImageCode(imageCode);
// 保存到驗(yàn)證碼到 redis 設(shè)置1分鐘過期
redisTemplate.opsForValue().set(Constants.KAPTCHA_SESSION_KEY + token, random, 1, TimeUnit.MINUTES);
return captchaDTO;
}// 登錄部分代碼
String username = loginPARAM.getUsername();
String password = loginPARAM.getPassword();
String imageCode = loginPARAM.getImageCode();
String codeToken = loginPARAM.getCodeToken();
// 校驗(yàn)驗(yàn)證碼
String code = (String) redisTemplate.opsForValue().get(Constants.KAPTCHA_SESSION_KEY + codeToken);
if (StringUtils.isBlank(code)) {
ApiAssert.failure(ErrorCodeEnum.KAPTCHA_NOT_FOUND);
}
// 清除token,防止重用
redisTemplate.delete(Constants.KAPTCHA_SESSION_KEY + codeToken);
if (!imageCode.equalsIgnoreCase(code)) {
ApiAssert.failure(ErrorCodeEnum.KAPTCHA_ERROR);
}總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot中使用MyBatis-Plus詳細(xì)步驟
MyBatis-Plus是MyBatis的增強(qiáng)工具,簡(jiǎn)化了MyBatis的使用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2025-01-01
JAVA發(fā)送HTTP請(qǐng)求的多種方式詳細(xì)總結(jié)
目前做項(xiàng)目中有一個(gè)需求是這樣的,需要通過Java發(fā)送url請(qǐng)求,查看該url是否有效,這時(shí)我們可以通過獲取狀態(tài)碼來判斷,下面這篇文章主要給大家介紹了關(guān)于JAVA發(fā)送HTTP請(qǐng)求的多種方式總結(jié)的相關(guān)資料,需要的朋友可以參考下2023-01-01
使用OkHttp3以Multipart/Form-Data方式上傳文件方式
在開發(fā)過程中,使用multipart/form-data進(jìn)行圖片上傳是一個(gè)常見的需求,本文通過一個(gè)實(shí)際案例,講解了如何在項(xiàng)目中添加OkHttp3庫(kù)依賴,并使用multipart/form-data進(jìn)行圖片上傳,首先,介紹了ContentType的概念和常見類型2024-10-10
基于Log4j2阻塞業(yè)務(wù)線程引發(fā)的思考
這篇文章主要介紹了基于Log4j2阻塞業(yè)務(wù)線程引發(fā)的思考,基于很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
MyBatis控制臺(tái)顯示SQL語句的方法實(shí)現(xiàn)
這篇文章主要介紹了MyBatis控制臺(tái)顯示SQL語句的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
MyBatis入門之增刪改查+數(shù)據(jù)庫(kù)字段和實(shí)體字段不一致問題處理方法
這篇文章主要介紹了MyBatis入門之增刪改查+數(shù)據(jù)庫(kù)字段和實(shí)體字段不一致問題處理方法,需要的朋友可以參考下2017-05-05
Maven中引入 springboot 相關(guān)依賴的方式(最新推薦)
這篇文章主要介紹了Maven中引入 springboot 相關(guān)依賴的方式(最新推薦),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2025-04-04

