持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的4天,点击查看活动详情
Cookie
为了解决 HTTP 无状态的问题,当浏览器访问某个web服务器时,web服务器会将一个键值对通过HTT响应头发送到浏览器,浏览器将该键值对缓存到本地中,用来保存客户端的信息。
优点:
- 解决了 HTTP 无状态的问题;
- 在客户端存储部分用户信息,每次访问都可以携带这部分的用户信息发送到服务器;
缺点:
- 解决HTTP无状态的问题,将额外的信息存储在客户端,但大小只有4kb,存储的大小有限;
- 在如今前后端分离开发的模式下,cookie不支持跨域是最大的缺点;
Session
session相当于服务端的cookie,可以将用户的信息存在服务端中,Tomcat中session的实现其实是基于HashMap的实现。
优点:
- 相对于cookie,将用户信息存储在服务端,会相对安全;
- 减少了网络传输的消耗,直接从服务端内存中获取信息,速度更快;
缺点:
- 分布式 session 问题💛
在如今分布式的互联网架构中,一台服务端已经支持不了大多数业务的发展,动不动就要多台台服务器。假设现在拥有三台服务器,部署相同的代码,某一次访问到了一台服务器,在该台服务器的session中存储了用户信息,而用户的第二次访问却访问到了另一台服务器,但这台服务器上却没有在session中记录用户信息,这时就要显示用户未登录吗?这就是分布式session的问题
解决方案:
使用独立一台服务器,用来记录session的信息,每次都去该服务器获取session即可
- 内存容量问题💛
如果使用了独立一台服务器记录session 信息,那么在用户量过多的情况下,一台服务器明显是撑不住的,这时就要加机器。加机器就意味着加钱,同时服务器过多也会给后来的系统的维护带来困难。
Token
token是一串在服务端生成的字符串,通过一定的加密算法,将用户的信息保存在该字符串中,客户端每次发送请求都会在请求头中带上该字符串进行鉴权,并且可以从该字符串中获取到用户信息。
优点:
- 相对于cookie,存储在客户端的字符串,没有长度的限制,并且支持跨域;
- 相对于session来说,它是存在于客户端的,减少服务器的存储压力,而且只要获得了该字符串,就可以向多台机器中进行用户信息的鉴权,不存在分布式session问题
- 请求流程
- token的使用(基于JWT的实现)
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
package com.blog.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
/**
* token 工具类
*
* @author Ezreal
*/
@Slf4j
public class TokenUtils {
// 一小时过期
private static final long TIME = 1000 * 60 * 60;
// 加密的密钥
private static final String SECRET_KEY = "ezreal";
/**
* 生成token
*
* @param account
* @return
*/
public static String create(String account) {
// 设置过期的时间
Date date = new Date(System.currentTimeMillis() + TIME);
// 算法
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
String token = JWT.create()
.withClaim("account", account)
.withExpiresAt(date)
.sign(algorithm);
log.info("生成token :" + token);
return token;
}
/**
* 验证token是否有效
*
* @param token
* @return
*/
public static boolean verify(String token) {
// 算法
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT verify = verifier.verify(token);
String string = verify.getClaim("account").toString();
System.out.println(string);
log.info("token 中的account为" + string);
return true;
} catch (Exception e) {
return false;
}
}
}
缺点:
安全问题,一旦该字符串被其他人获取,就相当于获取了该账号进行登录,保护 token 的存储是非常重要的。
跨域与同源
同源
协议 域名 端口三者相同,即称为同源
域名的组成:协议 + 子域名 + 主域名 + 端口号 + 请求资源的地址
协议:http://
子域名:www
主域名:baidu.com
端口号:80
请求资源的地址:index.html
同源的限制
- Ajax请求
- cookie
- 但是
<link/><script/><img/>允许跨域请求资源;
跨域
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
跨域解决方案
- 配置过滤器,添加响应头信息
package com.blog.filter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 跨域配置
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
//@WebFilter(filterName = "crossFilter", urlPatterns = "/*")
@Component
public class CrossFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
servletResponse.setHeader("Access-Control-Allow-Origin", servletRequest.getHeader("Origin"));
servletResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE,PATCH");
servletResponse.setHeader("Access-Control-Allow-Headers", "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,SessionToken,Cookie");
servletResponse.setHeader("Access-Control-Max-Age", "3600");
servletResponse.setHeader("Access-Control-Allow-Credentials", "true");
servletResponse.setHeader("Access-Control-Expose-Headers", "*");
//判断是否为可选择性批量操作的请求,设置状态码返回
if ("OPTIONS".equals(servletRequest.getMethod())) {
servletResponse.setStatus(HttpStatus.ACCEPTED.value());
return;
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
- 网关支持
# 跨域配置
location ^~ /api/ {
proxy_pass http://127.0.0.1:8080/api/;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers '*';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
- 配置@CrossOrigin 注解
@Controller
@CrossOrigin
public class Controller {
}
- 添加Web全局请求拦截器
@Configuration
public class WebMvcConfg implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
//当**Credentials为true时,**Origin不能为星号,需为具体的ip地址【如果接口不带cookie,ip无需设成具体ip】
.allowedOrigins("http://localhost:9527", "http://127.0.0.1:9527", "http://127.0.0.1:8082", "http://127.0.0.1:8083")
//是否允许证书 不再默认开启
.allowCredentials(true)
//设置允许的方法
.allowedMethods("*")
//跨域允许时间
.maxAge(3600);
}
}
- 返回新的CosFilter
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setMaxAge(3600L);
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}