跨域&JWT-13

跨域
	跨域解决方案
JWT

跨域

目前主流的项目架构,以前是传统的MVC项目,现在主流是前后端分离的项目模式

前端和后端是分离开来的,都是单独的项目,前端可以交给nginx、apache、nodejs,后端交给tomcat去管理

用户先访问前端页面,前端页面再发送请求给后端,后端响应对应的数据给前端,前端再负责渲染数据

后端只负责业务逻辑的处理,最后响应的只是数据,不再负责页面的渲染。

因为涉及到两台服务器了,两个服务器的IP和端口至少有一个不一样。协议、IP、端口只要有一个不一样了,就会出现跨域的问题,这个ajax就无法正常的访问了,会报错

Access to XMLHttpRequest at 'http://localhost:8080/userManager/address/getChildren?parentId=0' from origin 'http://localhost:9999' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

出现这个错误的原因主要是因为浏览器的同源策略限制导致的

同源策略/SOP(same origin policy) 是一种约定,是浏览器最核心也是最基本的安全功能,如果少了同源策略,则浏览器的正常功能都可能会受影响。web安全就是基于同源策略的,浏览器只是同源策略的一种实现,假设缺少了,可能遭受 XSS,CSRF 等攻击。

什么叫不同源:只要协议、主机、端口有一个不一样就是不同源,浏览器上只要不同源ajax就会报错

如何解决跨域的问题?

跨域解决方案
1. JSONP解决(重点)
2. document.domain + iframe解决
3. location.hash + iframe
4. window.name + iframe 
5. postMessage跨域
6. 跨域资源共享(CORS) (重点)
7. nginx代理跨域
8. nodejs中间件代理跨域
9. websocket 跨域

JSONP跨域

jsonp的原理就是利用了script标签不受同源策略影响,通过script标签的src属性,发送一个带有callback参数的get请求,服务器收到参数并将数据拼凑到callback函数中,响应给客户端,客户端将callback函数解释执行,从而前端拿到后端的数据

原生js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>跨域请求演示1(原生js实现)</title>
</head>
<body>

<form action="/user/register1" method="post">

    <span>用户名</span><input type="text" name="username"><span></span><br>
    <span>密码</span><input type="text" name="password"><span></span><br>

    <input type="submit" value="注册">
</form>
<script>

    var element = document.querySelector("input[name='username']");
    let espan =document.querySelector("input[name=username] + span");

    //跨域请求解决方法(原生js)

    function callBack(str){
        // var data =JSON.parse(str);
        // let data = str; //不用进行转换了

                    console.log(str);

                    if (str.code==0){
                        element.style.color = "red";
                    } else if(str.code==1){
                        element.style.color = "green";

                    }
                    espan.innerHTML=str.msg;
    }


    element.onblur = function () {


        let htmlElement = document.createElement("script");

        document.body.appendChild(htmlElement);

        //参数要传入一个回调的方法名   src  相当于是一个get请求  不受同源策略的影响
        htmlElement.src="http://localhost:8080/user/register?username="+element.value+"&method=callBack";
        //需要结合后台一起解决同源问题


    }


</script>

</body>
</html>

=



    public void register(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String username = request.getParameter("username");
        //跨域解决方案1
        response.setContentType("application/javascript;charset=utf-8");
        //同源方法
        String method = request.getParameter("method");

        Result result = new Result();

        if ("lijijun".equals(username)){
            result.setCode(0);
            result.setMsg("用户已存在");

        }else{
            result.setCode(1);
            result.setMsg("√");
        }

        String content = JSON.toJSONString(result);
        System.out.println(content);

        PrintWriter writer = response.getWriter();

        //同源策略解决 返回的是一个方法的调用
        writer.write(method+"("+content+")");


    }

原生js实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>三级联动</title>
</head>
<body>
<div id="content">
    <select name="" onchange="getChildren('shi',this.value)" id="sheng"></select>
    <select name="" onchange="getChildren('xian',this.value)" id="shi"></select>
    <select name="" id="xian"></select>
</div>
<script>
    function getChildren(selectId,parentId){
        let script = document.createElement("script");
        script.src = "http://localhost:8080/userManager/address/getChildren2?parentId=" + parentId + "&callback=showAddress&selectId=" + selectId;
        document.head.appendChild(script);//才会发送请求出去

    }

    function showAddress(selectId,result){
        // let result = JSON.parse(str);
        var select = document.getElementById(selectId);
        if(result.code == 0){//查询成功
            select.innerHTML = "";
            for (let i = 0;i < result.data.length;i++){
                select.innerHTML += "<option value='"+result.data[i].id+"'>"+result.data[i].name+"</option>";
            }
            if(selectId == "sheng"){
                getChildren("shi",select.value);
            }else if(selectId == "shi"){
                getChildren("xian",select.value);
            }

        }else{
            alert(result.msg);
        }
    }

    getChildren("sheng",0);
</script>
</body>
</html>

后台

 private void getChildren2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String parentId = request.getParameter("parentId");
        String callback = request.getParameter("callback");
        String selectId = request.getParameter("selectId");
        List<Address> addresses = addressService.selectChildren(Integer.parseInt(parentId));
        Result<List<Address>> result = new Result<>();
        if(addresses != null && !addresses.isEmpty()){
            result.setCode(0);
            result.setData(addresses);
        }else{
            result.setCode(1);
            result.setMsg("查询失败");
        }
        response.setContentType("application/javascript;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(callback + "('" + selectId + "'," + JSON.toJSONString(result) + ")");

    }

jquery的jsonp实现

==

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>跨域请求演示1(jquery的jsonp实现)</title>
</head>
<body>




<form action="/user/register1" method="post">

    <span>用户名</span><input type="text" name="username"><span></span><br>
    <span>密码</span><input type="text" name="password"><span></span><br>

    <input type="submit" value="注册">

</form>

<script src="../js/jquery-3.6.0.js"></script>
<script>

    $("input[name='username']").blur(function () {

        let value = this.value;

        if (""==value)
            return;
        $.ajax({
            url:"http://localhost:8080/user/register",
            type:"get",
            data:{username:value},
            dataType:"jsonp",
            success:function (result) { //如果服务器返回的是json格式的字符串 jq 会自动帮我们转换成json对象

                let title =$("input[name='username'] + span");

                if (result.code==0){
                    title.css("color","red");

                }else if(result.code==1){
                    title.css("color","black");
                }

                title.text(result.msg);


            },
            error:function () {

            },
            complete:function () {

            }


        })

    });


</script>

</body>
</html>

=

    public void register(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String username = request.getParameter("username");

        response.setContentType("application/javascript;charset=utf-8");
        String method = request.getParameter("callback");//默认的回调函数名

        Result result = new Result();

        if ("lijijun".equals(username)){
            result.setCode(0);
            result.setMsg("用户已存在");

        }else{
            result.setCode(1);
            result.setMsg("√");
        }

        String content = JSON.toJSONString(result);
        System.out.println(content);

        PrintWriter writer = response.getWriter();
        //同源策略解决 返回的是一个方法的调用
        writer.write(method+"("+content+")");


    }

====================================


    function getChildren(selectId,parentId){
        $.ajax({
            url:"http://localhost:8080/userManager/address/getChildren3",
            type:"get",//jsonp的type只能是get
            data:{parentId:parentId},
            dataType:"jsonp",//这里设置成jsonp
            success:function (result){
                if(result.code == 0){//查询成功
                    var select = document.getElementById(selectId);
                    select.innerHTML = "";
                    for (let i = 0;i < result.data.length;i++){
                        select.innerHTML += "<option value='"+result.data[i].id+"'>"+result.data[i].name+"</option>";
                    }
                    if(selectId == "sheng"){
                        getChildren("shi",select.value);
                    }else if(selectId == "shi"){
                        getChildren("xian",select.value);
                    }

                }else{
                    alert(result.msg);
                }
            }
        })

    }

后台

private void getChildren3(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String parentId = request.getParameter("parentId");
        String callback = request.getParameter("callback");
        List<Address> addresses = addressService.selectChildren(Integer.parseInt(parentId));
        Result<List<Address>> result = new Result<>();
        if(addresses != null && !addresses.isEmpty()){
            result.setCode(0);
            result.setData(addresses);
        }else{
            result.setCode(1);
            result.setMsg("查询失败");
        }
        response.setContentType("application/javascript;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(callback + "("  + JSON.toJSONString(result) + ")");

    }

jsonp的实现是需要后台的配合,因为script的src发送请求是get请求,所以限制的jsonp的使用,jsonp只支持get请求,如果要在一些复杂情况下jsonp的使用情况就局限性很强。

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-开头的
1. Access-Control-Allow-Origin :必须的
	他的值可以是一个*,表示接收任意域名的请求,也可以是具体的值 http://localhost 表示只接受 来自这个地方的请求
2. Access-Control-Allow-Credentials : 可选的
	他的值是一个boolean类型,如果为true,表示客户端允许发送cookie,默认情况下Cookie是不会包含在跨域的请求中。也就是如果不想携带cookie不设置这个头即可。
3. Access-Control-Expose-Headers : 可选的
	CORS 请求是: XMLHttpRequest 对象的 getResponseHeader 方法默认只能获取6个响应头:Cache-Control、Content-language、Content-Type、Expirse、Last-Modified、Pragma 。如果想让客户端获取更多的响应头就必须在 Access-Control-Expose-Headers 的值中进行指定。然后就可以在getResponseHeader中获取到。

实现CORS 一般只需要在服务器配置一个过滤器即可,因为主流的浏览器都是支持的。CORS一般会发送两次请求过来,第一次发送OPTIONS 请求,获取对应的响应头,第二次才会发送执行任务的请求。

=

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>跨域请求演示(CORS)</title>
</head>
<body>

<form action="/user/register1" method="post">

    <span>用户名</span><input type="text" name="username"><span></span><br>
    <span>密码</span><input type="text" name="password"><span></span><br>

    <input type="submit" value="注册">
</form>

<script src="../js/jquery-3.6.0.js"></script>
<script>

    $("input[name='username']").blur(function () {

        let value = this.value;

        if (""==value)
            return;
        $.ajax({
            url:"http://localhost:8080/user/register",
            type:"get",
            data:{username:value},
            dataType:"json",
            success:function (result) { //如果服务器返回的是json格式的字符串 jq 会自动帮我们转换成json对象

                let title =$("input[name='username'] + span");

                if (result.code==0){
                    title.css("color","red");

                }else if(result.code==1){
                    title.css("color","black");
                }

                title.text(result.msg);


            },
            error:function () {

            },
            complete:function () {

            }
        })

    });


</script>

</body>
</html>

=

过滤器

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(filterName = "CORSFilter",urlPatterns = "/*")
public class CORSFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {



        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        //设置响应头
        //设置哪些地方的请求可以跨域 * 表示任意请求  多个值可以用,隔开
        //"http://localhost:9999,http://localhost:8888"
        // 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
        response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
        System.out.println("Origin:"+request.getHeader("Origin"));
        //是否允许携带cookie
        // 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
        response.setHeader("Access-Control-Allow-Credentials","true"); // Credentials资格证书
        // 允许的请求方式
        response.setHeader("Access-Control-Allow-Methods","GET,POST,HEAD,OPTIONS");
        //允许的请求头
        // 提示OPTIONS预检时,后端需要设置的两个常用自定义头
        response.setHeader("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type,Token,Accept, Connection, User-Agent, Cookie");
       
        //允许浏览器获取的响应头
        response.setHeader("Access-Control-Expose-Headers","Token");
        // 超时时间
        response.setHeader("Access-Control-Max-Age","362880");
        if("OPTIONS".equalsIgnoreCase(request.getMethod())){
            return;
        }


        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }

}

=

    public void register(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        String username = request.getParameter("username");
        String method = request.getParameter("method");


        Result result = new Result();

        if ("lijijun".equals(username)){
            result.setCode(0);
            result.setMsg("用户已存在");

        }else{
            result.setCode(1);
            result.setMsg("√");
        }

        String content = JSON.toJSONString(result);
        System.out.println(content);

        PrintWriter writer = response.getWriter();
        writer.write(content);

    }

=======================

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
public class CORSFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //设置响应头
        //设置哪些地方的请求可以跨域 * 表示任意请求  多个值可以用,隔开
        //"http://localhost:9999,http://localhost:8888"
        response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
        //是否允许携带cookie
        response.setHeader("Access-Control-Allow-Credentials","true");
        // 允许的请求方式
        response.setHeader("Access-Control-Allow-Methods","GET,POST,HEAD,OPTIONS");
        //允许的请求头
        response.setHeader("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type,Token,Accept, Connection, User-Agent, Cookie");
        //允许浏览器获取的响应头
        response.setHeader("Access-Control-Expose-Headers","Token");
        // 超时时间
        response.setHeader("Access-Control-Max-Age","362880");
        if("OPTIONS".equalsIgnoreCase(request.getMethod())){
            return;
        }
        chain.doFilter(request, response);
    }

    public void init(FilterConfig config) throws ServletException {

    }

}

扩展

http://ruanyifeng.com/blog/2016/04/cors.html

JWT

JWT的全称是json web token 。是为了在网络应用环境中传递声明而执行的一种基于json的开发标准(RFC7519).。这个token被设置的紧凑而安全。特别适用于分布式单点登录(SSO)场景。JWT的声明一般是在身份提供者【就是统一进行登录校验的一套系统】和服务器提供者【就是各个不同的系统平台】之间传递被认证的用户身份信息。以便于从资源服务器获取资源。该token可以直接用于被认证和加密。

流程上:

1. 用户适用用户名和密码登录服务器
2. 服务器根据用户名和密码验证身份
3. 验证成功以后,服务器使用jwt**产生一个token发送给客户端**
4. 客户端将token保存下载,在以后的**每一次请求中将token带上**
5. 服务器可以使用一个过滤器验证token是否有效,如果有效则继续执行,无效则去登录
6. 这个token必须在每次请求的时候传递给服务器,发送给服务器的时候可以保存在请求头中,服务器一般需要支持CORS策略

JWT 是由三段信息构成的,将三段信息由.连接在一起就构成了JWT的字符串:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImM2OTI3NzAxLTNmNmMtNDA2NS1hOGFiLWNiOWUwMzUzOTViMiIsImlhdCI6MTYxODQ2OTMxMiwiZXhwIjoxNjE4NDcyOTEyfQ.mo3xwud0tH9Tjx2UQUXZohPUBgZhbGsjmG6yUGcrODU

JWT的三段构成:

第一部分:header

jwt的header部分承载了两部分信息:
	声明类型:JWT
	加密算法:HS256
	完整头部信息如下:
	{"typ":"JWT","alg":"HS256"}
	将这一部分头部信息使用Base64加密后得到
	eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

第二部分: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.utils;

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();
    }
}

jwt扩展

https://blog.csdn.net/Top_L398/article/details/109361680
https://blog.csdn.net/weixin_45070175/article/details/118559272
JSON Web Token(JSON Web令牌)

是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密〈使用HNAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。
————————————————
版权声明:本文为CSDN博主「一支有理想的月月鸟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Top_L398/article/details/109361680
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员zhi路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值