目录
Gateway解决如何允许跨域
服务网关Gateway实现用户鉴权_什么是JWT
服务网关Gateway实现用户鉴权_JWT原理
服务网关Gateway实现用户鉴权_用户微服务
服务网关Gateway实现用户鉴权_JWT工具类
服务网关Gateway实现用户鉴权_用户服务实现JWT鉴权
CORS
1、如何允许跨域,一种解决方法就是目的域告诉请求者允许什么来源域来请求,那么浏览器就会知道 B域是否允许A域发起请求。
2、CORS("跨域资源共享"(Cross-origin resource sharing))就是这样一种解决手段。
CORS使得浏览器在向目的域发起请求之前先发起一个OPTIONS方式的请求到目的域获取目的域的信息,比如获取目的域允许什么域来请求的信息。
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowCredentials: true
allowedOriginPatterns: "*"
allowedMethods: "*"
allowedHeaders: "*"
add-to-simple-url-handler-mapping: true
实时效果反馈
1.当一个请求url的____之间任意一个与当前页面url不同即为跨域。
A 协议、域名
B 协议、端口
C 协议、域名、请求类型
D 协议、域名、端口
2.最常用解决跨域的手段是____。
A CORS
B COS
C COR
D 以上都是错误
什么是JWT
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。特别适用于分布式站点的单点登录 (SSO)场景。
传统的session认证
每次提到无状态的 JWT时相信都会看到另一种基于Session 的用户认证方案介绍,这里也不例外,Session 的认证流程通常会像这样:
缺点:
安全性:CSRF攻击因为基于cookie来进行用户识别, cookie如果被截获,用户就会很容易受 到跨站请求伪造的攻击。
扩展性:对于分布式应用,需要实现 session 数据共享
性能:每一个用户经过后端应用认证之后,后端应用都要在服务端做一次记录,以方便用户 下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开 销会明显增大,与REST风格不匹配。因为它在一个无状态协议里注入了状态。
JWT方式
优点:
1、无状态
2、适合移动端应用
3、单点登录友好
实时效果反馈
1.传统的session认证缺点的是__。
A 内存容量
B 网络安全
C 分布式应用session共享
D 以上都正确
2.下列属于JWT优点的是__。
A 无状态
B 适合移动端应用
C 单点登录友好
D 以上都正确
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2030年7月1日0点0分"
}
注意:
用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候会加上签名,服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
JWT的结构
注意: 它是一个很长的字符串,中间用点( . )分隔成三个部分。注 意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
JWT 的三个部分依次如下:
头部(header)
载荷(payload)
签证(signature)
Header
JSON对象,描述 JWT 的元数据。其中 alg 属性表示签名的算法 (algorithm),默认是 HMAC SHA256(写成 HS256);typ 属性 表示这个令牌(token)的类型(type),统一写为 JWT。
{
"alg": "HS256",
"typ": "JWT"
}
注意: 上面代码中, alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256); typ 属性表示这个令牌 (token)的类型(type),JWT 令牌统一写为 JWT 然后将头部进行Base64编码构成了第一部分,Base64是一种用64个字符来表示任意二进制数据的方法,Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。
Payload
内容又可以分为3中标准
1、标准中注册的声明
2、公共的声明
3、私有的声明
payload-标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
payload-公共的声明 :
公共的声明可以添加任何的信息。一般这里我们会存放一下用户的基本信息(非敏感信息)。
payload-私有的声明 :
私有声明是提供者和消费者所共同定义的声明。需要注意的是,不要存放敏感信息,不要存放敏感信息,不要存放敏感信息!!!
因为:这里也是base64编码,任何人获取到jwt之后都可以解码!!
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
注意: sub和iat是标准声明,分别代表所面向的用户和jwt签发时间。
1、sub:这个是发给一个账号是1234567890的用户(也许是ID)
2、name:名字叫John Doe
3、iat:签发时间是1516239022(2030/1/18 9:30:22)
Signature
这部分就是 JWT 防篡改的精髓,其值是对前两部分 base64UrlEncode 后使用指定算法签名生成,以默认 HS256 为例,指定一个密钥(secret),就会按照如下公式生成:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret,
)
注意: 算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"( . )分隔,就可以返回给用户。
JWT 的使用方式
流程: 客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息 Authorization 字段里面。
实时效果反馈
1.客户端收到服务器返回的 JWT把数据保存到____。
A load
B session
C loadStorage
D 以上都是错误
2. JWT签证默认算法__。
A HS256
B Base64
C HS384
D SR384
创建cloud-auth-user6500工程
引入POM依赖
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.projectlombok
lombok
1.18.22
org.springframework.boot
spring-boot-starter-actuator
引入JWT依赖
com.alibaba
fastjson
1.2.79
com.auth0
java-jwt
3.7.0
创建JWT工具类JWTUtils
public class JWTUtil {
// 秘钥
public static final String SECRET_KEY = "erbadagang-123456";
// token过期时间
public static final long TOKEN_EXPIRE_TIME = 5 * 60 * 1000;
// 签发人
private static final String ISSUER = "issuer";
// 用户名
private static final String USER_NAME = "username";
}
生成签名方法
public static String token(String username) {
Date now = new Date();
//SECRET_KEY是用来加密数据签名秘钥
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
String token = JWT.create()
// 签发人
.withIssuer(ISSUER)
// 签发时间
.withIssuedAt(now)
// 过期时间
.withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRE_TIME))
// 保存权限标记
.withClaim(USER_NAME,username)
.sign(algorithm);
log.info("jwt generated user={}",username);
return token;
}
验证签名
public static boolean verify(String token)
{
try {
//SECRET_KEY是用来加密数据签名秘钥
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
//如果校验有问题会抛出异常。
verifier.verify(token);
return true;
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
测试JWT
public static void main(String[] args) {
// 生成Token
String token = JWTUtil.token("itbaizhan");
System.out.println(token);
//验证Token
boolean verify = JWTUtil.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3N1ZXIiLCJleHAiOjE2NDUwNjMyNzYsImlhdCI6MTY0NTA2Mjk3NiwidXNlcm5hbWUiOiJpdGJhaXpoYW4ifQ.uKKVEMCTW0-e_bPHLyb-JY8CwRU7ciU8vP5B78lDY3s");
System.out.println(verify);
}
编写LoginController类
@RequestMapping("user")
@RestController
@Slf4j
public class LoginController { }
编写统一返回实体类
/**
* AuthResult 作用 : 统一返回实体类
*/
@Data
@NoArgsConstructor
@Builder
public class AuthResult {
// 返回状态码
private int code;
// 返回描述信息
private String msg;
// 返回Token签名
private String token;
public AuthResult(int i, String s) {
this.code = i;
this.msg = s;
}
public AuthResult(int i, String success, String token) {
this.code = i;
this.msg = success;
this.token = token;
}
}
编写用户登录接口
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @return
*/
@PostMapping("/login")
public AuthResult login(String username,String password) {
// TODO 模拟数据库验证用户名密码
if ("admin".equals(username) && "admin".equals(password)) {
//生成token
String token = JWTUtil.token();
return new AuthResult(0, "success", token, refreshToken);
} else {
return new AuthResult(1001,"username or password error");
}
}
编写验证令牌接口
/**
* 验证令牌
* @param token 令牌
* @return
*/
@GetMapping("/verify")
public AuthResult verify(String token) {
// 验证令牌
boolean success = JWTUtil.verify(token);
if (success){
return AuthResult.builder()
.code(200).msg("Token未失效").build();
}else {
return AuthResult.builder()
.code(500).msg("Token失效").build();
}
}
YML文件编写
eureka:
client:
# 表示是否将自己注册到Eureka Server
register-with-eureka: true
# 示是否从Eureka Server获取注册的服务信息
fetch-registry: true
# Eureka Server地址
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: user-service
prefer-ip-address: true
spring:
application:
# 设置应用名词
name: user-service
server:
port: 6510
Postman测试
测试登录