赞
踩
比方在一个商城系统中(这个商城为微服务架构),比如现在有这么这么几个服务:
eureka服务注册中心(端口10000)
用户服务(端口10001),
购物车服务(端口10002),
订单服务(端口10003),
zuul网关(端口9000)
其中购物车服务和订单服务需要在登录后才能访问,那么我们如何判断用户是否登录了呢?
我们可能会想到Session。Session是存储在服务器端,现在用户访问用户访问登录并在用户访问中存储了Session,那么我们需要考虑的是在其他服务我们能获取到这个Session吗?答案是不能,因为它们不是同一个服务,如果要访问其他服务需要自己实现Session共享!
这时候我们可以使用token来解决这个问题。在微服务架构中实现token的标准方案是JWT
JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;它是分布式服务权限控制的标准解决方案!
Token官网:https://jwt.io
普通token:32位UUID
JWT生成的Token
如图所示JWT生成的token包含了三部分数据:
- Header:头部,通常头部有两部分信息:
- 声明类型type,这里是JWT(type=jwt)
- 加密算法,自定义的(使用rs256/base64/hs256)
我们会对头部进行base64加密(可解密),得到第一部分数据
- Payload:载荷,就是有效数据,一般包含下面信息
- 用户身份信息userid,username(注意:这里因为采用base64加密是可以解密的,因此不要存放敏感信息)
- 注册声明:如token的签发时间,过期时间,签发人等
这部分也会采用base64加密,得到第二部分数据
- Signature:base64加密,签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的密钥(secret,盐。不要泄露,最好声明周期跟换),通过加密算法生成,用于验证整个数据完整和可靠
总结
1.JWT生成的token是一个有规则的
2.它有三部分组成:Header.payload.signature,每部分都是通过base64加密而成
3.JWT每个部分都是可以解密的
项目架构分析
在该微服务架构中一个有四个微服务,分别是:
1.auth-service:身份验证服务,该微服务主要用来对用户的身份进行验证以及生成token
2.eureka-server:服务注册中心
3.shopping_cart-service:购物车服务,处理购物车相关的业务
4.zuul-service:网关,用来实现间权,动态路由,负载均衡
common:相当于一个工具包,用来存放常被复用的类,例如:JWT工具类JWTUtil.java,接口响应对象Result.java接口响应对象,接口响应状态管理工具类StatusCode.java
在启动好个个微服务后,个个微服务会将自己注册到eureka当中,并将自己的地址信息交给eureka管理。在不同服务接口的调用不再是直接去访问对应访问中的接口,而是通过zuul网关的动态路由来访问访问中的接口,访问方式如下所举例:
localhost:9000/api/auth-service/auth/login
localhost:9000:网关服务的地址
/api:网关的前缀路径
auth-service:服务的名称
auth:该服务业务层的窄路径
login:对应的是业务层中的接口名
在网关中还会对请求进行鉴权。(在访问需要登录后的接口)
网关中集成了负载均衡,我们在配置文件中进行配置即可
实现的需求
发起请求,请求后端接口,在网关中对请求进行鉴权,(实际上就是在网关中编写了一个拦截器拦截请求),判断请求的uri,如果请求的是用户身份验证服务中的登录接口,直接放行,在该接口中对用户提交的登录信息进行验证,如果身份信息正确颁发jwt凭证,生成token,并将token响应到客户端存储在localStorage中,(客户端每次请求都会携带上这个token)如果失败响应错误信息。`
如果请求的uri不是指定被放行接口的uri,将会拦截该请求,通过HttpServletRequest获取头部信息中的token,判断token是否为空,如果不为空对token进行解析,解析成功放行,负责响应信息提示用户先登录再进行访问。
主要:我这里简单的模拟该需求,并没有使用到关系型数据MySQL
<properties> <jjwt.version>0.7.0</jjwt.version> <joda-time.version>2.9.6</joda-time.version> </properties> <dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jjwt.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/joda-time/joda-time --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>${joda-time.version}</version> </dependency> </dependencies>
这个类主要是用来生成token和解析token
package com.jn.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.joda.time.DateTime; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.security.Key; /** * @Description: token工具类 */ public class JWTUtil { /** * 获取token中的参数 * * @param token *Z */ public static Claims parseToken(String token, String key) { if ("".equals(token)) { return null; } try { return Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(key)) .parseClaimsJws(token).getBody(); } catch (Exception ex) { return null; } } /** * 生成token * * @param userId * @return */ public static String createToken(Integer userId,String username,String key, int expireMinutes) { //TODO SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //生成签名密钥 byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(key); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //添加构成JWT的参数 JwtBuilder builder = Jwts.builder() .setHeaderParam("type", "JWT") .setSubject(userId.toString()) .claim("userId", userId) // 设置载荷信息 .claim("username",username) .claim("age",23) .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())// 设置超时时间 .signWith(signatureAlgorithm, signingKey); //生成JWT return builder.compact(); } }
package com.jn.filter; import com.jn.util.JWTUtil; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import io.jsonwebtoken.Claims; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * @Author ScholarTang * @Date 2020/3/15 10:36 PM * @Desc 权限校验(过滤器) */ @Component public class JWTFilter extends ZuulFilter { @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); String uri = request.getRequestURI(); if (uri.contains("/login")) { System.out.println("无需拦截"); return null; } String token = request.getHeader("authorization"); if (token != null) { Claims claims = JWTUtil.parseToken(token, "user"); if (claims != null) { return null; } } currentContext.setSendZuulResponse(false); currentContext.setResponseStatusCode(401); currentContext.setResponseBody("{'flag':'false','msg':'请登录'}"); currentContext.getResponse().setContentType("text/html;charset=utf-8"); return null; } }
package com.jn.controller; import com.jn.domain.User; import com.jn.util.JWTUtil; import com.jn.vo.Result; import com.jn.vo.ResultUtil; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author ScholarTang * @Date 2020/3/15 10:36 AM * @Desc 处理用户登录业务 */ @RestController @RequestMapping("/auth") public class LoginController { //正确的用户信息 private final User USER_DATA = new User(1,"123","123"); /** * 模拟登录的方法 * @param user * @return */ @PostMapping("/login") public ResponseEntity<Result> login (@RequestBody User user) { //简单的校验用户名和密码 if (user.getAccount().equals(USER_DATA.getAccount())) { if (user.getPassword().equals(USER_DATA.getPassword())) { //生成token String token = JWTUtil.createToken(USER_DATA.getUid(), USER_DATA.getAccount(), "user", 30); return ResponseEntity.ok(ResultUtil.SUCCESSFUL(token)); } } return ResponseEntity.ok(ResultUtil.FAILURE()); } }
这里我简单处理,只是为了模拟
(shopping_cart-service/com.jn.controller.ShoppingCartController.java)package com.jn.controller; import com.jn.vo.Result; import com.jn.vo.ResultUtil; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author ScholarTang * @Date 2020/3/15 11:04 AM * @Desc 处理购物车相关的业务 */ @RestController @RequestMapping("/shoppingCart") public class ShoppingCartController { /** * 模拟获取所有购物车中的商品列表 * @return */ @GetMapping("/findAll") public ResponseEntity<Result> findAllShoppingCart(){ return ResponseEntity.ok(ResultUtil.SUCCESSFUL("获取商品列表信息成功")); } }
分析
网关的拦截器中拦截了该请求(该请求的uri并不是制定被放行的uri),判断了该请求是否携带token,或者说它携带的token在拦截器中无法解析(解析方式是我定义的,该token使用我的解析方式无法解析),这时向客户端响应对应的提示信息
1.登录
2.访问shopping_cart-service服务中的接口
结果
测试结果为预想结果,OK!
https://gitee.com/Tang1314521/jwt-demo.git
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。