JavaWeb前后端分离架构

目录

  • JavaWeb前后端分离架构
      • 前后分离的优势
  • 在本机中部署项目
  • 关于验证
      • Cookie & Session
      • Token & JWT
      • token验证流程
      • JWT的构成
  • JWT搭建使用

JavaWeb前后端分离架构

前后端分离已成为互联网项目开发的业界标准使用方式,通过 nginx+tomcat的方式有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如:浏览器,车载终端,安卓,IOS 等等)打下坚实的基础。这个步骤是系统架构从猿进化成人的必经之路。

核心思想是前端 html 页面通过 ajax 调用后端的 restuful api 接口并使用 json数据进行交互。
JavaWeb前后端分离架构_第1张图片

前后分离的优势

  1. 可以实现真正的前后端解耦,前端服务器使用 nginx/tomcat。前端/WEB服务器放的是 css,js,图片等等一系列静态资源,前端服务器负责控制页面引用,跳转,路由.
  2. 发现 bug,可以快速定位是谁的问题,不会出现互相踢皮球的现象。页面逻辑,跳转错误,浏览器兼容性问题,脚本错误,页面样式等问题,全部由前端工程师来负责。接口数据出错,数据没有提交成功,应答超时等问题,全部由后端工程师来解决。
  3. 减少后端服务器的负载压力。除了接口以外的其他所有 http 请求全部转移到前端服务器上。
  4. 即使后端服务暂时超时或者宕机了,前端页面也会正常访问,只不过数据刷不出来而已。
  5. 也许你也需要有微信相关的轻应用,那样你的接口完全可以共用,如果也有app 相关的服务,那么只要通过一些代码重构,也可以大量复用接口,提升效率。(多端应用)
  6. 页面显示的东西再多也不怕,因为是异步加载。
  7. nginx 支持页面热部署,不用重启服务器,前端升级更无缝。
  8. 增加代码的维护性&易读性(前后端混在一起的代码读起来相当费劲)。
  9. 提升开发效率,因为可以前后端并行开发,而不是像以前的强依赖。
  10. 在 nginx 中部署证书,外网使用 https 访问,并且只开放 443 和 80 端口,其他端口一律关闭(防止黑客端口扫描),内网使用 http,性能和安全都有保障。
  11. 前端大量的组件代码得以复用,组件化,提升开发效率

在本机中部署项目

前端

  1. 在Vue-cli项目中输入命令npm run build 打包
  2. 将dist包中的内容移至nginx/html下
  3. 启动nginx

后端

  1. 将springboot项目打jar包
  2. cmd中输入命令java -jar springboot8080.jar启动服务
  3. 启动放置图片的服务tomcat:apache-tomcat-9.0.43(img)\bin\startup.bat

访问 localhost+nginx端口

关于验证

Cookie & Session

Session主要作用就是在服务端记录用户状态和信息,保存在服务器,安全性更高,tomcat默认有效期30min

Cookie保存在客户端浏览器,可存储一些不敏感的信息

认证流程

Cookie+Session

用户成功登录,服务器会存储一个Session对象里边能放用户信息,并将SessionID发给客户端存在Cookie中,客户端之后发送的请求都会携带SessionID,服务端就能拿到用户信息

缺点

  1. Session保存在服务端,服务器挂了session就没了;
  2. Session过多占用服务器资源;
  3. 移动端没有cookie;
  4. 分布式多机器,只能固定访问一台,扩展性低;

Cookie 和 Session 的区别?

  • 作用范围不同,Cookie 保存在客户端,Session 保存在服务器端。
  • 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能;Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
  • 隐私策略不同,Cookie 存储在客户端,容易被窃取;Session 存储在服务端,安全性好一些。
  • 存储大小不同, 单个 Cookie 保存的数据不能超过 4K;对于 Session 来说存储没有上限

Token & JWT

为解决传统的Cookie+Session认证的不便,JWT(Json web Token),他可以在服务端不用保存Session,只用在客户端保存服务端返回的Token就可以,扩展性提高

JWT本质就是一段签名的JSON格式的数据。由于带有数字签名,所以这些信息是可信的。

优点

  • 简洁:JWT Token数据量小,传输速度也很快
  • 自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库
  • 跨语言:因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
  • 扩展性:不需要在服务端保存会话信息,特别适用于分布式微服务

token验证流程

JavaWeb前后端分离架构_第2张图片

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
  2. 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
  3. 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
  4. 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
  5. 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
  6. 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

JWT的构成

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidHlwZSI6MCwiZXhwIjoxNjQxMjY1NjY5LCJhY2NvdW50IjoiYWRtaW4ifQ.d8V0-IHMVFi5OhmlhFK5SMcOZN2nteLjrWyjubTaYbo

三部分 标头(Header)、有效载荷(Payload,用户的信息)和签名(Signature)

在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串

第一部分 标头 header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC HS256
{
  "alg": "HS256",
  "typ": "JWT"
}

将这段json进行base64转码后就成为token的第一部分

第二部分 有效荷载 payload

{
  "sub": "1234567890",
  "name": "Helen",
  "admin": true
}

存放用户的个人信息,只是经过base64转码,但不加密,所有不要存放隐私信息,JWT只是适合在网络中传输一些非敏感的信息

第三部分 签名 signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64转码后的header和base64转码后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分

JWT搭建使用

  1. 导入jar坐标

    
    <dependency>
        <groupId>com.auth0groupId>
        <artifactId>java-jwtartifactId>
        <version>3.8.2version>
    dependency>
    
  2. 创建JWTUtil类

    /**
     * JWT常用方法类
     *
     * @author Deevan
     */
    public class JwtUtil {
        /**
         * jwt生成token
         */
        public static String token(Integer id, String account, Integer type) {
            String token = "";
            try {
                //过期时间 为1970.1.1 0:0:0 至 过期时间  当前的毫秒值 + 有效时间
                Date expireDate = new Date(System.currentTimeMillis() + 300 * 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是否有效
         */
        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部分数据,按需使用
         */
        public static DecodedJWT getTokenInfo(String token) {
            return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
        }
    }
    
  3. 登录验证时生成token并返回给前端

    @RequestMapping("/login")
        public CommonResult<Admin> login(@RequestBody Admin admin) {
            CommonResult<Admin> commonResult = null;
            System.out.println(admin);
            try {
                Admin adminBack = loginService.loginCheck(admin);
                if (adminBack != null) {
                    String token = JwtUtil.token(adminBack.getId(), adminBack.getAccount(), adminBack.getType());
                    adminBack.setToken(token);
                    commonResult = new CommonResult<>(200, "登录成功", adminBack);
                } else {
                    commonResult = new CommonResult<>(201, "密码错误", null);
                }
    
            } catch (Exception e) {
                e.printStackTrace();
                commonResult = new CommonResult<>(500, "服务器忙", null);
            }
            return commonResult;
        }
    
  4. 前端接收token并存入sessionStorage中

    login() {
        var _this = this; //存储vue对象
        this.$http.post("/login/login", this.form).then(function(res) {
            //密码错误
            if (res.data.code === 201) {
                _this.$message({
                    message: res.data.msg,
                    type: 'warning'
                });
                return;
            }
            window.sessionStorage.setItem("account", res.data.data.account)
            window.sessionStorage.setItem("token", res.data.data.token)
            //路由跳转
            _this.$router.push("/main");
        })
    }
    
  5. 前端请求拦截中为请求头中加入token,使得每一次请求都带有token字段

    //axios 请求拦截
    axios.interceptors.request.use(config => {
    	//为请求头对象,添加 Token 验证的 token 字段
    	config.headers.token = window.sessionStorage.getItem('token');
    	return config;
    })
    
  6. 路由导航守卫中验证token

    //路由导航守卫,在每次发生组件路由的时候,会自动出发
    rout.beforeEach((to, from, next) => {
    	//如果用户访问的登录页, 直接放行
    	if (to.path == '/login') {
    		return next();
    	} else {
    		//验证token,拦截没有token的路由
    		var token = window.sessionStorage.getItem("token");
    		if (token == null) {
    			return next("/login");
    		} else {
    			next();
    		}
    	}
    })
    
  7. 后端拦截器中进行"是否登录"验证

    创建LoginInterceptor拦截器

    /**
     * 判断是否登录拦截器
     *
     * @author Deevan
     */
    public class LoginInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String token = request.getHeader("token");
            //验证请求中的token 是否有问题
            boolean res = JwtUtil.verify(token);
            if (!res) {
                response.getWriter().print(401);
            }
            return res;
        }
    }
    

    配置此拦截器

    /**
     * 拦截器配置类
     *
     * @author Deevan
     */
    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //拿到自己注册的拦截器
            InterceptorRegistration inter = registry.addInterceptor(new LoginInterceptor());
            //拦截的地址
            inter.addPathPatterns("/**");
            //放行的地址
            inter.excludePathPatterns("/api/login/login");
        }
    }
    

你可能感兴趣的:(框架,架构,前端,java)