前后端分离学习笔记 ---[跨域问题,JWT,路由守卫,Axios设置请求拦截和响应拦截]
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前后端分离学习笔记 ---[跨域问题,JWT,路由守卫,Axios设置请求拦截和响应拦截]相关的知识,希望对你有一定的参考价值。
文章目录
跨域问题
跨域是指从一个域名的网页去请求另一个域名的资源。
浏览器对 javascript 具有默认的安全限制,同源处理策略;
同源 : 协议,域名,端口都相同;
只要 协议,域名,端口中任何一个是不同的,就是跨域
;
如果一个网页可以随意地访问另外一个网站的资源,那么就有可能在客户完全不知情的情况下出现安全问题。
但是在某些情况下时需要跨域的,这次学习前后端项目分离时,前后端服务器的端口不一样,这就需要跨域进行请求和响应数据信息;或者说在同一项目中需要访问子域系统模块,就需要跨域访问;
那么当然也可以不跨域完成,即在一个打开的窗口中内嵌窗口进行链接请求访问.
后端配置解决跨域
跨域资源共享(Cross-originResource Sharing)
- 实现跨站访问控制,可安全地进行跨站数据传输;
- 服务器端对于 CORS 的支持,需要设置 Access-Control-Allow-Origin;要在后台中加上响应头来允许域请求.
- 当浏览器检测到了配置,才允许 Ajax 进行跨域的访问;
(1) 在类或方法上使用注解
@CrossOrigin("...配置Ip和端口")
;仅达到局部配置跨域的效果[仅在使用了注解的地方生效];
比如在上次搭建的案例中就用了这个注解;前后端分离学习笔记(1) —[Vue基础]
(2)手工设置响应头-----使用
HttpServletResponse
对象来添加响应头
(Access-Control-Allow-Origin)
来授权原始域
Origin 的值也可以设置为"*" ,表示全部放行
例如
@RequestMapping("/test")
@ResponseBody
public String index(HttpServletResponse response)
response.addHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5277/");
return "Hello World";
(3)使用配置类完成全局跨域设置—>返回一个新的 CorsFilter Bean,并添加映射路径和具体的 CORS
配置信息
配置类
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 java.util.Collections;
@Configuration
public class CorsConfig
@Bean
public CorsFilter corsFilter()
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1,允许任何来源
corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
//2,允许任何请求头
corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);
//3,允许任何方法
corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);
//4,允许凭证
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source
= new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
Json web token
传统的基于session访问鉴权机制
在之前的学习中,后端标识信息的处理一般都放在了后端的session中;
根据 http 协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为 cookie,以便下次请求时发送给我们的应用.
那么当出现不同的客户端,不同的用户时,独立的服务器已无法承载更多的用户,而这时候基于 session 认证就出现问题了,服务器可能会受到恶意的信息交互.
- 扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,限制了负载均衡器的能力 , 限制了应用的扩展能力。
- CSRF (跨站请求伪造):因为是基于 cookie 来进行用户识别的, cookie 如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于 token 的访问鉴权机制
它是无状态的,不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于 token 认证机制的应用不需要去考虑用户在哪一台服务器登录了,便于应用扩展.
- 用户使用账号和密码发出 post 请求;
- 服务器使用私钥创建一个 jwt;
- 服务器返回此 jwt给浏览器;
- 浏览器将该 jwt 串在请求头中像服务器发送请求;
- 服务器验证该 jwt;
- 返回响应的资源到浏览器。
Json web token
JWT
是为在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准((RFC 7519);定义的一种简洁的,自包含的方法用于通信双方之间以 JSON 对象的形式安全的传递信息。
由于是经过签名的,所以通信信息是安全的;JWT 可使用 HMAC 算法/ RSA 的公私秘钥对进行签名.
- 只要一次登录后;在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证,访问不同的域明也没有问题.
- 信息交换在通信的双方之间使用 JWT 对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。
特点
- 简洁(Compact): 可以通过 URL,POST 参数或者在 HTTP header 发送,数据量小,当然传输速度也很快.
- 它具有自包含(Self-contained)性:负载中包含了所有用户所需要的信息,避免了多次查询数据库(但是建议不要将重要信息存入其中[比如密码,私密信息等]);
- 由于 Token 在传递时会以 JSON 加密的形式发到倒客户端,所以 JWT 是跨语言的,原则上任何 web 形式都支持。
- 无需在服务端保存会话信息,因为只要系统登录后,它每次接受请求时都会去验证请求头中的
token令牌
是否符合设定的签名规则,较适用于分布式微服务。
JWT结构
由三部分构成
头部(header)
完整的头部例如:
'typ': 'JWT',
'alg': 'HS256'
然后将头部进行 base64 转码,构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
10010101 01010101
100101 010101 01010
载荷(payload, 保存用户的信息[例如用户Id,账号])
签证(signature).
jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:
• header (base64 后的)
• payload (base64 后的)
• secret
具体的搭建引入
在项目的pom.xml
中引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
配置类设置
package com.xiaozhi.backserver.startspringboot.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author by @CSDN 小智RE0
* @date 2021-12-28
*/
public class JWTUtil
/**
* jwt生成token
* @param id 管理员Id
* @param account 管理员账号
* @param type 管理员类型
* @return
*/
public static String token (Integer id, String account,Integer type)
String token = "";
try
//过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() + 1800*1000);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带id,账号信息,生成签名
token = JWT.create()
.withHeader(header)
.withClaim("id",id)
.withClaim("account",account)
.withClaim("type",type)
.withExpiresAt(expireDate)
.sign(algorithm);
catch (Exception e)
e.printStackTrace();
return null;
return token;
//验证Token是否还有效; true有效,false失效;
public static boolean verify(String token)
try
//验签
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
catch (Exception e) //当传过来的token如果有问题,抛出异常
return false;
/**
* 获得token 中playload部分数据,按需使用
* @param token
* @return
*/
public static DecodedJWT getTokenInfo(String token)
return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
后端配置拦截
自定义登录拦截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 登录拦截器
*
* */
public class LoginInterceptor implements HandlerInterceptor
//预处理-->进入到控制器之前
//返回false 不进入处理器,返回true,就进入拦截器中执行
//在控制器之前走这个,决定是否走这个控制器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
//在请求头中取到token令牌;
String token = request.getHeader("token");
//验证token;
boolean b = JWTUtil.verify(token);
//若令牌不符合则响应信息为 401;
if(!b)
response.getWriter().print(401);
return b;
配置
import com.xiaozhi.backserver.startspringboot.util.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer
public void addInterceptors(InterceptorRegistry registry)
InterceptorRegistration inter = registry.addInterceptor(new LoginInterceptor());
inter.addPathPatterns("/**");
//放行地址;登录请求访问路径
inter.excludePathPatterns("/api/login/login");
路由守卫
全局配置
在路由配置的index.js中配置路由守卫,若在当前浏览器的session中没有找到token令牌,则强制将回退到登录页面;
//to-将要访问的页面地址,from-从哪个页面访问的,next-放行函数
rout.beforeEach((to, from, next) =>
//如果用户访问的登录页,直接放行;
if (to.path == '/login')
return next();
else
//若没有令牌,则推到登录页面;
var token = window.sessionStorage.getItem("token");
if (token == null)
return next("/login");
else
next();
)
Axios设置前端请求拦截和响应拦截
注意这里的401响应拦截,和后端设置的401登录请求拦截正好形成一个前后闭环;
//导入axios;
import axios from 'axios';
//设置访问后台服务器地址
axios.defaults.baseURL = "http://127.0.0.1:5277/api/";
//将 axios 挂载;
Vue.prototype.$http = axios;
//全局请求拦截和响应拦截配置;
//axios 请求拦截
axios.interceptors.request.use(config =>
//为请求头对象,添加 Token 验证的 token 字段;
config.headers.token = window.sessionStorage.getItem('token');
return config;
)
// 添加响应拦截器
axios.interceptors.response.use((res) =>
//后端拦截器拦截状态码;
if (res.data == 401)
//清除session信息;
window.sessionStorage.clear();
router.replace("/login");
//服务器异常状态码拦截;
if(res.data.code == 500)
this.$message(message:resp.data.msg,type: 'warning');
return;
return res;
);
基础整合一下登录案例
后端这里的话,和之前的差不多,服务层,数据访问层这里就不放了
/**
* @author by @CSDN 小智RE0
*/
@RestController
@RequestMapping(value = "/api/login")
public class LoginController
CommonResult commonResult;
@Autowired
LoginService loginService;
//跨域接收;
//@CrossOrigin("http://localhost:8080/")
@RequestMapping(value = "/login")
public CommonResult login(@RequestBody Admin admin)
try
System.out.println(admin);
Admin admin1 = loginService.loginUser(admin);
if(admin1!=null)
String token = JWTUtil.token(admin1.getId(), admin1.getAccount(),admin1.getType());
admin1.setToken(token);
commonResult = new CommonResult(200,"正确",admin1);
else
commonResult = new CommonResult(201,"账号或密码错误",null);
catch (Exception e)
e.printStackTrace();
commonResult = new CommonResult(500,"服务器错误",null);
return commonResult;
需要注意的是,数据响应时,注意分级
响应时同样还是用一个公共类进行封装,数据存储在属性data中;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author by @CSDN 小智RE0
* @date 2021-12-28 16:02
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult
private Integer code;
private String msg;
private Object data;
在取得响应的数据时需要写两级data,第一级的data是axios数据请求响应时的数据域,第二级才是公共类中的属性data数据;
前端登录组件Login.vue
<template>
<div class="login_container">
<!-- 登录盒子-->
<div class="login_box">
<!-- 头像盒子-->
<div class="img_box">
<img src="../assets/logo.png" />
</div>
<div style="margin-top: 100px; padding-right: 30px;">
<!-- 登录表单-->
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="账号">
<el-input v-model="form.account"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="form.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="login()">登录</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default
data:function()
return
form:
account:"",
password:""
,
methods:
login()
//注意这里定义 _this, this是vue对象;
var _this = this;
//console.log(this.form);
this.$http.post("login/login",this.form).then(function(resp)
//回调函数;
console.log(resp);
//警告消息弹框;
if(resp.data.code == 201)
_this.$message(message:resp.data.msg,type: 'warning');
Python全栈100天学习笔记Day48 前后端分离开发入门