JavaWeb前后端分离架构

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

核心思想是前端 html 页面通过 ajax 调用后端的 restuful api 接口并使用 json数据进行交互。


前后分离的优势

  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验证流程

通俗地说,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坐标

<!-- jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.2</version>
</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");
    }
}

————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_52163230/article/details/122510846

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值