赞
踩
1、Session认证
① 用户向服务器发送用户名和密码
② 服务器验证通过后,在当前对话(session)里面保存相关数据,如用户角色,登陆时间等
③ 服务器向用户返回一个session_id,写入用户的Cookie
④ 用户随后的每一次请求,都会通过Cookie,将session_id传回服务器
⑤ 服务器收到session_id,找到前期保存的数据,由此得知用户的身份
认证流程:
Session认证的方式扩展性不好,如果是服务器集群,或者是跨域的服务导向架构,就要求session数据共享,以便每台服务器都能够读取session,针对这问题有两种解决方案:
① session数据持久化,写入数据库或别的持久层。优点是架构清晰,但工程量大
② 服务器不再保存session数据,所有数据都保存在客户端,每次请求都发回服务器。Token就是其中一个代表
2、Token认证
Token是在服务端产生的一串字符串,是客户端访问资源接口(API)时所需要的资源凭证
① 客户端使用用户名和密码请求登陆,服务端收到请求,去验证用户名和密码
② 验证成功后,服务端会签发一个token并把这个token发送给客户端
③ 客户端收到token后,存储起来,比如放在cookie或者localStorage里头
④ 客户端每次向服务端请求资源时需要带上这个token
⑤ 服务端收到请求后去验证这个token,成功则返回请求数据
实现方式:JWT(JSON Web Token)认证
原理:
① 用户发送用户名和密码后,服务器认证并生成JWT令牌(JSON对象),将其发回给客户端
(为了防止用户篡改数据,服务器在生成这个对象的时候会加上签名)
② 客户端将JWT令牌存储在本地以便后续使用
③ 当客户端向另一个域名服务器发送请求时,将JWT令牌作为请求头(放在Authorization字段里头)或请求参数发送
④ 服务器收到请求后,检查JWT令牌的有效性,并进行身份验证和授权
⑤ 若令牌有效则返回请求的数据,否则返回未授权的错误信息
JWT由三个部分组成:
1、Header(头部):
- {
- "alg": "HS256", // 令牌类型
- "typ": "JWT" //加密算法
- }
2、Payload(负载):
- {
- "iss": "example.com",
- "sub": "1234567890",
- "aud": ["foo", "bar"],
- "exp": 1648696800,
- "nbf": 1648693200, // 2022年4月29日10:20:00
- "iat": 1648694700,
- "jti": "abcdef123456"
- }
- // 1、iss(issuer): 表示JWT签发者的名称,通常是一个字符串或URL。
- // 2、sub(subject): 表示JWT的主题,即客户端的唯一标识符,通常是一个用户ID。
- // 3、aud(audience): 表示JWT的预期接收者,即对该JWT有效的接收方,可以是单个字符串或一个字符串数组。
- // 4、exp(expiration time): 表示JWT的过期时间,用Unix时间戳表示。
- // 5、nbf(not before): 表示JWT的生效时间,用Unix时间戳表示。
- // 6、iat(issued at): 表示JWT的签发时间,用Unix时间戳表示。
- // 7、jti(JWT ID): 表示JWT的唯一标识符,通常用于避免重放攻击。
3、Signature(签名):由Header、Payload和一个密钥(secret,存储在服务器端,对外不可见)进行签名生成。
- HMACSHA256(
- base64UrlEncode(header) + "." +
- base64UrlEncode(payload),
- secret)
将Header、Payload和Signature通过'.'连接在一起形成JWT令牌,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
代码实现:
① 引入依赖
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
② 添加JWT配置
- # JWT相关配置
- jwt.secret=your-secret
- jwt.expiration=3600
③ 创建JWT工具类:用于生成和解析JWT
- @Component
- public class JwtUtils {
-
- // 秘钥
- @Value("${jwt.secret}")
- private String secret;
-
- // 过期时间,单位秒
- @Value("${jwt.expiration}")
- private Long expiration;
-
- // 生成JWT
- public String generateToken(Long userId) {
- SecretKey key = Keys.hmacShaKeyFor(secret.getBytes()); // 创建密钥
- Date now = new Date();
- Date expireTime = new Date(now.getTime() + expiration * 1000);
- return Jwts.builder()
- .setIssuer("issuer") // 设置JWT的签发者
- .setAudience("audience") // 设置JWT的接收方
- .setSubject(userId.toString()) // 设置JWT的主题
- .setIssuedAt(now) // 设置JWT的签发时间
- .setExpiration(expireTime) // 设置JWT的过期时间
- .signWith(key, SignatureAlgorithm.HS256) // 用HS256算法和密钥key签名JWT
- .compact(); // 生成JWT字符串
- }
-
- // 解析JWT
- public Claims parseToken(String token) {
- SecretKey key = Keys.hmacShaKeyFor(secret.getBytes()); // 创建密钥
- return Jwts.parserBuilder()
- .setSigningKey(key) // 设置用于解析JWT的密钥
- .build()
- .parseClaimsJws(token) // 解析JWT,获取Jws<Claims>实例
- .getBody();
- }
- }
④ 配置拦截器:用于验证请求中的JWT是否有效
- @Component
- public class JwtInterceptor implements HandlerInterceptor {
-
- @Autowired
- private JwtUtils jwtUtils;
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- // 从请求头中获取token
- String token = request.getHeader("Authorization");
- // 判断token是否存在并以Bearer开头
- if (token != null && token.startsWith("Bearer ")) {
- token = token.substring(7); // 去掉token前缀
- Claims claims = jwtUtils.parseToken(token); // 解析JWT
- if (claims != null) {
- Long userId = Long.valueOf(claims.getSubject()); // 获取JWT中的用户id
- // 将用户信息存储到request中,方便后续操作
- request.setAttribute("userId", userId);
- return true;
- }
- }
- // 解析失败,返回401未授权状态码
- response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
- return false; // 拦截请求
- }
- }
⑤ 使用JWT
- @RestController
- @RequestMapping("/api")
- public class UserController {
-
- @Autowired
- private UserService userService;
-
- @PostMapping("/login")
- public ResponseEntity<?> login(@RequestBody UserLoginDto userLoginDto) {
- // 用户登录逻辑
-
- // 生成 JWT token
- String token = Jwts.builder()
- .setSubject(user.getUsername()) // 主题
- .claim("roles", user.getRoles())
- .setIssuedAt(new Date()) // 签发时间
- .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
- .signWith(SignatureAlgorithm.HS512, SECRET_KEY) // 使用算法和密钥对token进行签名。
- .compact(); // 将JWT token生成为字符串
-
- // 返回 token 给客户端
- return ResponseEntity.ok(new AuthResponse(token));
- }
-
-
- @GetMapping("/users")
- @PreAuthorize("hasAuthority('ROLE_ADMIN')") // 拥有 ROLE_ADMIN 权限的用户才能访问该方法
- public ResponseEntity<?> getUsers(@RequestHeader("Authorization") String authorizationHeader) {
- String token = authorizationHeader.substring(7); // 去掉 "Bearer" 前缀
-
- // 验证 token 是否有效
- Claims claims = Jwts.parser() // 获取一个JwtParser对象
- .setSigningKey(SECRET_KEY) // 设置JWT的签名密钥,用于校验JWT的合法性
- .parseClaimsJws(token) // 将其转化为Jws对象
- .getBody(); // 获取Jws对象中的payload信息
-
- // 获取用户列表逻辑
- }
-
-
- @GetMapping("/users/{id}")
- public ResponseEntity<?> getUserById(@PathVariable Long id) {
- // 根据用户ID获取用户信息逻辑
- }
-
- // 省略其他接口...
- }
Session和Token认证的区别:
Session和Token都是常用的用户认证方式,它们的作用都是为了验证用户身份和授权访问资源,但是它们的实现方式有所不同。
Session是一种服务器端认证方式,通常通过在服务器端保存用户的登录信息(较安全),以便在后续的请求中进行验证。当用户进行登录操作时,服务器会创建一个Session,并给这个Session分配一个唯一的标识符(Session ID),然后将这个Session ID发送给客户端保存。客户端在后续的请求中需要携带这个Session ID,服务器端根据这个Session ID来查找对应的Session,从而验证用户的身份。
Token是一种无状态认证方式,通常通过在客户端保存用户的登录信息(不安全),以便在后续的请求中进行验证。当用户进行登录操作时,服务器会生成一个Token,并将这个Token发送给客户端保存。客户端在后续的请求中需要携带这个Token,服务器端通过验证这个Token来确定用户的身份。
优缺点:
Session需要在服务器端保存用户的登录信息,因此需要占用服务器的资源,并且需要在分布式系统中进行Session共享和Session失效管理(工程量大)。
Token是无状态的,不需要在服务器端保存用户的登录信息,因此具有良好的可扩展性,并且可以很方便地实现分布式系统中的认证和授权。用解析token的计算时间换取session的存储空间,从而减轻服务器压力,减少频繁查询数据库
Session的安全性比较高,因为Session的内容保存在服务器端,客户端无法直接修改Session的内容
Token的安全性相对较低,因为Token的内容保存在客户端,客户端可以通过一些手段来篡改Token的内容。
Session和Token的应用场景:
一般来说,使用 session 可能更适合传统的 Web 应用,因为它通常需要用户在浏览器中持续地与应用交互,而且涉及到敏感数据的处理。例如,在电子商务网站中,用户需要登录才能访问个人购物车和订单等敏感信息,此时可以使用 session 来验证用户身份,并在服务器端存储相关的用户信息和状态。
而在 API (应用程序编程接口)设计和单页面应用中,使用 token 可能更加常见。由于 API 和单页面应用的特性,客户端可以直接与 API 或后端服务通信,而不需要经过浏览器的中间层。此时,使用 token 可以避免一些 session 的问题,如跨域和服务器负载均衡等。同时,token 也更容易在不同服务之间进行传递和共享,比如使用 OAuth2 等协议来实现单点登录和授权等功能。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。