JavaWeb前后端分离架构
前后端分离已成为互联网项目开发的业界标准使用方式,通过 nginx+tomcat的方式有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如:浏览器,车载终端,安卓,IOS 等等)打下坚实的基础。这个步骤是系统架构从猿进化成人的必经之路。
核心思想是前端 html 页面通过 ajax 调用后端的 restuful api 接口并使用 json数据进行交互。
前后分离的优势
- 可以实现真正的前后端解耦,前端服务器使用 nginx/tomcat。前端/WEB服务器放的是 css,js,图片等等一系列静态资源,前端服务器负责控制页面引用,跳转,路由.
- 发现 bug,可以快速定位是谁的问题,不会出现互相踢皮球的现象。页面逻辑,跳转错误,浏览器兼容性问题,脚本错误,页面样式等问题,全部由前端工程师来负责。接口数据出错,数据没有提交成功,应答超时等问题,全部由后端工程师来解决。
- 减少后端服务器的负载压力。除了接口以外的其他所有 http 请求全部转移到前端服务器上。
- 即使后端服务暂时超时或者宕机了,前端页面也会正常访问,只不过数据刷不出来而已。
- 也许你也需要有微信相关的轻应用,那样你的接口完全可以共用,如果也有app 相关的服务,那么只要通过一些代码重构,也可以大量复用接口,提升效率。(多端应用)
- 页面显示的东西再多也不怕,因为是异步加载。
- nginx 支持页面热部署,不用重启服务器,前端升级更无缝。
- 增加代码的维护性&易读性(前后端混在一起的代码读起来相当费劲)。
- 提升开发效率,因为可以前后端并行开发,而不是像以前的强依赖。
- 在 nginx 中部署证书,外网使用 https 访问,并且只开放 443 和 80 端口,其他端口一律关闭(防止黑客端口扫描),内网使用 http,性能和安全都有保障。
- 前端大量的组件代码得以复用,组件化,提升开发效率
在本机中部署项目
前端
- 在Vue-cli项目中输入命令npm run build 打包
- 将dist包中的内容移至nginx/html下
- 启动nginx
后端
- 将springboot项目打jar包
- cmd中输入命令java -jar springboot8080.jar启动服务
- 启动放置图片的服务tomcat:apache-tomcat-9.0.43(img)\bin\startup.bat
访问 localhost+nginx端口
关于验证
Cookie & Session
Session主要作用就是在服务端记录用户状态和信息,保存在服务器,安全性更高,tomcat默认有效期30min
Cookie保存在客户端浏览器,可存储一些不敏感的信息
认证流程
Cookie+Session
用户成功登录,服务器会存储一个Session对象里边能放用户信息,并将SessionID发给客户端存在Cookie中,客户端之后发送的请求都会携带SessionID,服务端就能拿到用户信息
缺点
- Session保存在服务端,服务器挂了session就没了;
- Session过多占用服务器资源;
- 移动端没有cookie;
- 分布式多机器,只能固定访问一台,扩展性低;
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的认证流程如下:
- 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
- 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
- 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
- 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
- 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
- 验证通过后,后端解析出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