文章目录
跨域&&JWT
1. 跨域
跨域介绍:
目前的项目一般都是前后端分离的,这时就会造成两台服务器去完成对应的功能,因为有了两台服务器,所以就会导致ip、端口等的不一致,这时就会造成一个服务器不能访问另外一个服务器,就会造成跨域问题
产生原因:
现在的浏览器的同源策略限制导致的,同源策略/SOP(same origin policy) 是一种约定,是浏览器最核心也是最基本的安全功能,如果少了同源策略,则浏览器的正常功能都可能会受影响。web安全就是基于同源策略的,浏览器只是同源策略的一种实现,假设缺少了,可能遭受 XSS,CSRF 等攻击。
什么叫不同源:只要协议、主机、端口有一个不一样就是不同源,浏览器上只要不同源ajax就会报错
1.1 跨域解决
1.1.1 JSONP跨域
1.1.1.1 原生实现:
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<form action="">
<span>账号</span><input type="text" name="username"><span></span><br>
<span>密码</span><input type="password" name="password"><br>
<input type="submit" value="注册">
</form>
<script src="../js/jquery-3.6.0.js"></script>
<script>
//使用jsonp完成 注意只能用与get请求
//设置回调函数
function callBack(data) {
var element = document.querySelector("input[name='username'] + span");
if (data.code){
element.style.color = "green";
}else {
element.style.color = "red";
}
element.innerHTML = data.msg;
}
$("input[name='username']").blur(function () {
//用户名
let value = this.value;
//获取一个script标签
var htmlScriptElement = document.createElement("script");
//设置script标签的地址 将参数给传进去以及要调用的回调函数
htmlScriptElement.src = "http:/localhost/ajax/user/selectUsername?username="+ value + "&method=callBack";
//将script标签添加到body里面
document.body.appendChild(htmlScriptElement);
});
</script>
</body>
</html>
后台主要逻辑代码(doget 不放上来了):
//检查用户名
public void selectUsername(HttpServletRequest request,HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
String method = request.getParameter("method");
//response.setContentType("application/json;charset=utf-8");
response.setContentType("application/javascript;charset=utf-8");
PrintWriter writer = response.getWriter();
Result result = new Result();
if ("zhangsan".equals(username)){
result.setCode(1);
result.setMsg("账号不可用");
} else{
result.setCode(0);
result.setMsg("√");
}
String s = JSON.toJSONString(result);
//response.getWriter().write(s);
//返回的数据就是调用这个方法
writer.write( method + "("+s+")");
}
1.1.1.2 jQuery实现:
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<form action="">
<span>账号</span><input type="text" name="username"><span></span><br>
<span>密码</span><input type="password" name="password"><br>
<input type="submit" value="注册">
</form>
<script src="../js/jquery-3.6.0.js"></script>
<script>
//使用jq完成 不常用格式
$("input[name='username']").blur(function () {
//用户名
let value = this.value;
$.ajax({
url:"http:/localhost/ajax/user/selectUsername",//请求路径
type:"get",//请求方式 默认get
data:{username: value},//请求参数
dataType:"jsonp",//响应数据类型
headers:{a:"a",b:"b"},//请求头,是可以传参数的
timeout:5000,//超时时间
success:function (result) {//里面的参数可以是很多,
//响应200的回调,具体操作
let span = $("input[name='username']+span");
if (result.code == 0){
span.css("color","green");
}else {
span.css("color","red");
}
},
error:function (){
//响应失败的回调,超时,非200,datatype和响应的数据类型不一致
},
compile:function () {
//无论成功还是失败都要执行
}
})
});
</script>
</body>
</html>
后端:
//检查用户名
public void selectUsername(HttpServletRequest request,HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
String method = request.getParameter("callBack");//jquery实现的时候就是callback
// String method = request.getParameter("method");//原生实现的时候就是method
//response.setContentType("application/json;charset=utf-8");
response.setContentType("application/javascript;charset=utf-8");
PrintWriter writer = response.getWriter();
Result result = new Result();
if ("zhangsan".equals(username)){
result.setCode(1);
result.setMsg("账号不可用");
} else{
result.setCode(0);
result.setMsg("√");
}
String s = JSON.toJSONString(result);
//response.getWriter().write(s);
//返回的数据就是调用这个方法
writer.write( method + "("+s+")");
}
注意:
- 使用jsonp的时候,后端返回的一定是一个方法调用
- jsonp的实现是需要后台的配合,因为script的src发送请求是get请求,所以限制的jsonp的使用,jsonp只支持get请求,如果要在一些复杂情况下jsonp的使用情况就局限性很强。
1.1.2 CORS跨域资源共享
CORS 介绍:
-
CORS 跨域资源共享,CORS 是一个W3C的标准,全称是跨域资源共享(CROSS-origin resources sharing)
-
它允许浏览器向跨域的服务器发送一个XMLHttpRequest请求,从而克服了ajax只能同源使用的限制。CORS需要浏览器和服务器同时支持才可以。目前主流的浏览器都是支持的,除了IE10以下的版本。
浏览器将CORS的跨域请求分为简单请求和非简单请求
只要满足如下的两个条件就是简单请求
1. 请求方式是 GET\POST\HEAD
2. 请求头是 Accept、Accept-language、Content-language、Content-Type(值仅限于:application/x-www-form-urldecoded、multipart/form-data、text-plain)
只要不满足如上的两个条件都是非简单请求,浏览器针对两种请求的处理方式是不一样的
请求头介绍:
GET /cors HTTP/1.1
Origin: http://localhost
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0…
上面的请求头中:Origin 表示这个请求从哪个源(协议+主机+端口)来的,服务器根据这个源决定是否同意这个请求。
CORS的响应都是以Access-Control-开头的
- Access-Control-Allow-Origin :必须的
他的值可以是一个*,表示接收任意域名的请求,也可以是具体的值 http://localhost 表示只接受 来自这个地方的请求 - Access-Control-Allow-Credentials : 可选的
他的值是一个boolean类型,如果为true,表示客户端允许发送cookie,默认情况下Cookie是不会包含在跨域的请求中。也就是如果不想携带cookie不设置这个头即可。 - Access-Control-Expose-Headers : 可选的
CORS 请求是: XMLHttpRequest 对象的 getResponseHeader 方法默认只能获取6个响应头:Cache-Control、Content-language、Content-Type、Expirse、Last-Modified、Pragma 。如果想让客户端获取更多的响应头就必须在 Access-Control-Expose-Headers 的值中进行指定。然后就可以在getResponseHeader中获取到。
1.1.2.1 CORS实现
实现CORS 一般只需要在服务器配置一个过滤器即可,因为主流的浏览器都是支持的。CORS一般会发送两次请求过来,第一次发送OPTIONS 请求,获取对应的响应头,第二次才会发送执行任务的请求。
过滤器:
package com.xiaowang.filter; /**
* @Author:小王吖
* @Date:2023/1/14
*/
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter( "/*")
public class CORSFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request1 = (HttpServletRequest) request;
HttpServletResponse response1 = (HttpServletResponse) response;
//获取要的方法
String method = request1.getMethod();
//设置响应头
//设置哪些地方的请求可以跨域 * 表示任意请求 多个值可以用,隔开
//"http://localhost:9999,http://localhost:8888"
response1.setHeader("Access-Control-Allow-Origin",request1.getHeader("Origin"));
//是否允许携带cookie
response1.setHeader("Access-Control-Allow-Credentials","true");
// 允许的请求方式
response1.setHeader("Access-Control-Allow-Methods","GET,POST,HEAD,OPTIONS");
//允许的请求头
response1.setHeader("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type,Token,Accept, Connection, User-Agent, Cookie");
//允许浏览器获取的响应头
response1.setHeader("Access-Control-Expose-Headers","Token");
// 超时时间
response1.setHeader("Access-Control-Max-Age","362880");
if ("OPTIONS".equalsIgnoreCase(request1.getMethod())){
return;
}
chain.doFilter(request1,response1);
}
}
前端(普通的ajax就行了):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<form action="">
<span>账号</span><input type="text" name="username"><span></span><br>
<span>密码</span><input type="password" name="password"><br>
<input type="submit" value="注册">
</form>
<script src="../js/jquery-3.6.0.js"></script>
<script>
//设置回调函数
function callBack(data) {
var element = document.querySelector("input[name='username'] + span");
if (data.code==0){
element.style.color = "green";
}else {
element.style.color = "red";
}
element.innerHTML = data.msg;
}
//使用jq完成 不常用格式
$("input[name='username']").blur(function () {
//用户名
let value = this.value;
$.ajax({
url:"http://localhost/ajax/user/selectUsername",//请求路径
type:"get",//请求方式 默认get
data:{username: value},//请求参数
dataType:"json",//响应数据类型
headers:{a:"a",b:"b"},//请求头,是可以传参数的
timeout:5000,//超时时间
success:function (result) {//里面的参数可以是很多,
console.log("success");
//响应200的回调,具体操作
let span = $("input[name='username']+span");
if (result.code == 0){
span.css("color","green");
}else {
// span.text(result.msg);
span.css("color","red");
}
span.text(result.msg);
},
error:function (){
console.log("error");
//响应失败的回调,超时,非200,datatype和响应的数据类型不一致
},
compile:function () {
//无论成功还是失败都要执行
}
})
});
</script>
</body>
</html>
后端(注意使用的是cors所以不需要将其写成jsonp的形式哦):
//检查用户名
public void selectUsername(HttpServletRequest request,HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
//String method = request.getParameter("callBack");//jquery实现的时候就是callback
String method = request.getParameter("method");//原生实现的时候就是method
response.setContentType("application/json;charset=utf-8");
//response.setContentType("application/javascript;charset=utf-8");
PrintWriter writer = response.getWriter();
Result result = new Result();
if ("zhangsan".equals(username)){
result.setCode(1);
result.setMsg("账号不可用");
} else{
result.setCode(0);
result.setMsg("√");
}
String s = JSON.toJSONString(result);
//response.getWriter().write(s);
//返回的数据就是调用这个方法
writer.write(s);
}
1.1.3 两种区别
jsonp只有一次请求,cors是两次请求(CORS一般会发送两次请求过来,第一次发送OPTIONS 请求,获取对应的响应头,第二次才会发送执行任务的请求。)
2. JWT介绍
JWT介绍:
-
WT的全称是json web token 。是一种令牌,。是为了在网络应用环境中传递声明(token令牌)而执行的一种基于json的开发标准(RFC7519).。
-
这个token被设置的紧凑而安全。特别适用于分布式单点登录(SSO)场景。
-
JWT的声明一般是在身份提供者和服务器提供者之间传递被认证的用户身份信息。以便于从资源服务器获取资源。该token可以直接用于被认证和加密。
单点登录:有很多项目,但是只用登录一次,相当于,QQ号授权后,可以登录很多应用。
流程上来看:
- 用户用用户名和密码登录服务器
- 服务器根据用户名和密码验证身份
- 验证成功以后,服务器使用jwt产生一个token发送给客户端
- 客户端将token保存下载,在以后的每一次请求中将token带上
- 服务器可以使用一个过滤器验证token是否有效,如果有效则继续执行,无效则去登录
- 这个token必须在每次请求的时候传递给服务器,发送给服务器的时候可以保存在请求头中,服务器一般需要支持CORS策略
JWT结构:
JWT 是由三段信息构成的,将三段信息用 . (点)连接在一次就构成了JWT的字符串:、
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImM2OTI3NzAxLTNmNmMtNDA2NS1hOGFiLWNiOWUwMzUzOTViMiIsImlhdCI6MTYxODQ2OTMxMiwiZXhwIjoxNjE4NDcyOTEyfQ.mo3xwud0tH9Tjx2UQUXZohPUBgZhbGsjmG6yUGcrODU
JWT的三段构成:
第一部分:header
jwt的header部分承载了两部分信息:
声明类型:JWT(如何产生的)
加密算法:HS256
完整头部信息如下:
{"typ":"JWT","alg":"HS256"}
将这一部分头部信息使用Base64加密后得到
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
base64加密是对称的,可以通过base64解密
第二部分:Payload(载荷)
载荷就是存放有效信息的地方,这个信息中包含了三部分
公有的声明
公有的声明可以添加任何信息进去,一般添加用户相关的信息或者业务相关的信息,不建议添加敏感信息,因为这部分用户可以在客户端解密
私有的声明
是提供者和消费者共同定义的声明,一般也不建议存放敏感信息,也可以解密的
注册的声明 (建议但不强制)
iss: jwt的签发者
sub: jwt面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个时间一定要大于签发时间
iat: jwt的签发时间
nbf: 定义在什么时间之前,jwt是不可用的
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
例:
定义一个载荷
{"sub":"1234567890","name":"John Doe","admin":true,"jti":"c6927701-3f6c-4065-a8ab-cb9e035395b2","iat":1618469312,"exp":1618472912}
使用Base64加密后如下
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImM2OTI3NzAxLTNmNmMtNDA2NS1hOGFiLWNiOWUwMzUzOTViMiIsImlhdCI6MTYxODQ2OTMxMiwiZXhwIjoxNjE4NDcyOTEyfQ
第三部分:签证Signing
这部分的信息是由三部分组成
header(加密后的)
payload(加密后的)
secret
将上面的三部分使用.连接成字符串,然后将这个字符串使用header中定义的加密算法进行加盐加密,得到第三部分
加密方式如下:
HS256(Base64(header).Base64(payload),secret)
最后得到 mo3xwud0tH9Tjx2UQUXZohPUBgZhbGsjmG6yUGcrODU
需要注意的是secret 是保存在服务器的,jwt的生成也是在服务器生成的,所以secret可以看做进行jwt的签发的盐值。所以secret算是服务器的私钥,在任何情况下都不能将私钥暴露出去。一旦客户端知道了这个私钥,那就意味着客户端可以自我签发jwt了。
使用jwt
添加依赖 :
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
工具类使用,一般的JWT都会包含以下方法:
package com.xiaowang.utils;
/**
* @Author:小王吖
* @Date:2023/1/29
*/
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class JWTUtil {
private static final long EXP = -24 * 60 * 60 * 1000;//过期时间
/**
* 生成token
* @param username 账号信息
* @param secret 秘钥
* @return 生成的token
*/
public static String sign(String username,String secret){
Date date = new Date(System.currentTimeMillis() + EXP);
Algorithm algorithm = Algorithm.HMAC256(secret);
//附带username信息的token
return JWT.create().withClaim("username",username).withExpiresAt(date).sign(algorithm);
}
/**
* 从token中获取账号
* @param token
* @return
*/
public static String getUsername(String token){
try {
DecodedJWT decodedJWT = JWT.decode(token);
return decodedJWT.getClaim("username").asString();
}catch (JWTDecodeException e){
return null;
}
}
/**
* 校验token是否正确
* @param token
* @param username
* @param secret
* @return
*/
public static boolean verify(String token,String username,String secret){
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim("username", username).build();
DecodedJWT verify = jwtVerifier.verify(token);
return true;
} catch (Exception e){
return false;
}
}
/**
* 检查token是否过期
* @param token
* @return
*/
public static boolean isExpires(String token){
return System.currentTimeMillis() > JWT.decode(token).getExpiresAt().getTime();
}
}
在使用的时候,可以直接通过类名去调用某一个具体要用的方法即可,这样也可以完成相对应的工作