开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情
每日英语:
Discipline is the bridge between goals and accomplishment.
自律是目标和成就之间的桥梁。 -吉姆·罗恩
令牌颁发
用户登录获取令牌,我们在mall-user-service中实现令牌颁发。我们需要将Dao、Service、Controller全部创建完成,然后执行登录方法实现。
1)Dao
在mall-user-service中创建Dao:
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
2)Service
接口:在mall-user-service中创建接口com.xz.mall.user.service.UserInfoService代码如下:
public interface UserInfoService extends IService<UserInfo> {
}
实现类:在mall-user-service创建com.xz.mall.user.service.impl.UserInfoServiceImpl代码如下:
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
}
3)Controller
在mall-user-service中创建com.xz.mall.user.controller.UserInfoController代码如下:
@RestController
@RequestMapping(value = "/user/info")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
/****
* 登录
*/
@PostMapping(value = "/login")
public RespResult<String> login(@RequestParam String username,@RequestParam String pwd){
//登录
UserInfo userInfo = userInfoService.getById(username);
if(userInfo!=null){
//匹配密码是否一致
if(userInfo.getPassword().equals(pwd)){
//封装用户信息实现加密
Map<String,Object> dataMap = new HashMap<String,Object>();
dataMap.put("username",userInfo.getUsername());
dataMap.put("name",userInfo.getName());
dataMap.put("roles",userInfo.getRoles());
//创建令牌
String token = JwtToken.createToken(dataMap);
return RespResult.ok(token);
}
//账号密码不匹配
return RespResult.error("账号或者密码错误");
}
return RespResult.error("账号不存在");
}
}
令牌安全
我们前面说过,令牌数据不安全,其实除了令牌数据不安全之外,令牌还存在被盗用的风险,例如:
1:张三登录获得令牌 zzz
2:李四盗取了张三的令牌 zzz,并用令牌zzz直接访问后台
上面例子执行,后台只要能识别令牌,是不会拒绝zzz令牌的,这时候就存在盗用风险。
令牌盗用该如何解决?
如果令牌被盗,我们可以通过IP识别令牌是否安全,如上图:
1:每次生成令牌的时候,把用户的IP作为令牌的一部分进行MD5加密,并将密文存入到令牌中
2:用户每次访问API接口的时候,都先获取客户端IP,再将IP进行MD5加密,并和令牌中的IP密文比对
3:如果密文一致,则证明IP没有发生变化,如果密文不一致,则证明IP发生变化,提示重新登录
这种操作在很多大厂中都有应用,我们平时登录QQ、微信的时候他们都会提示设备终端发生变化,其实和上图操作是一个道理。
令牌封装
在mall-user-service中修改com.xz.mall.user.controller.UserInfoController添加IP封装,代码如下:
令牌安全校验
我们在mall-api-gateway中创建com.xz.mall.api.permission.AuthorizationInterceptor用来实现鉴权,代码如下:
public class AuthorizationInterceptor {
/***
* 令牌解析
*/
public static Map<String, Object> jwtVerify(String token,String clientIp){
try {
//token解析
Map<String, Object> resultMap = JwtToken.parseToken(token);
//令牌中的IP
String jwtip = resultMap.get("ip").toString();
//IP校验
clientIp = MD5.md5(clientIp);
if(clientIp.equals(jwtip)){
return resultMap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
在微服务网关过滤器com.xz.mall.api.filter.ApiFilter中调用上面方法,同时进行优化:
@Configuration
public class ApiFilter implements GlobalFilter, Ordered {
@Autowired
private HotQueue hotQueue;
/***
* 执行拦截处理 http://localhost:9001/mall/seckill/order?id&num
* JWT
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
//获取uri
String uri = request.getURI().getPath();
if(uri.equals("/mall/user/info/login")){
//放行
return chain.filter(exchange);
}
//客户端IP
String ip = IPUtil.getIp(request);
//用户令牌
String token = request.getHeaders().getFirst("authorization");
//令牌校验
Map<String, Object> resultMap = AuthorizationInterceptor.jwtVerify(token, ip);
if(resultMap==null){
endProcess(exchange,401,"no token");
}
if(uri.equals("/seckill/order")){
//秒杀过滤
seckillFilter(exchange, request, resultMap.get("username").toString());
}
//NOT_HOT 直接由后端服务处理
return chain.filter(exchange);
}
/***
* 秒杀过滤
* @param exchange
* @param request
* @param username
*/
private void seckillFilter(ServerWebExchange exchange, ServerHttpRequest request, String username) {
//商品ID
String id = request.getQueryParams().getFirst("id");
//数量
Integer num =Integer.valueOf( request.getQueryParams().getFirst("num") );
//排队结果
int result = hotQueue.hotToQueue(username, id, num);
//QUEUE_ING、HAS_QUEUE
if(result==HotQueue.QUEUE_ING || result==HotQueue.HAS_QUEUE){
endProcess(exchange,result,"hot");
}
}
/***
* 结束程序
* @param exchange
* @param code
* @param message
*/
public void endProcess(ServerWebExchange exchange,Integer code,String message){
Map<String,Object> resultMap = new HashMap<String,Object>();
resultMap.put("code",code);
resultMap.put("message",message);
exchange.getResponse().setStatusCode(HttpStatus.OK);
exchange.getResponse().setComplete();
exchange.getResponse().getHeaders().add("message",JSON.toJSONString(resultMap));
}
@Override
public int getOrder() {
return 0;
}
}
我们把mall-cart和mall-user服务配置到gateway中:
spring:
cloud:
gateway:
routes:
#秒杀
- id: seckill_route
uri: lb://mall-seckill
predicates:
- Path=/mall/seckill/order/**
filters:
- StripPrefix=1
#购物车
- id: cart_route
uri: lb://mall-cart
predicates:
- Path=/mall/cart/**
filters:
- StripPrefix=1
#用户服务
- id: user_route
uri: lb://mall-user
predicates:
- Path=/mall/address/**,/mall/user/info/**
filters:
- StripPrefix=1
我们可以在不同IP上登录生成令牌,在其他IP上用该令牌,效果如下:
总结
本篇主要介绍了一下用户登录获取令牌,实现令牌颁发,还有令牌封装、令牌安全校验的设计,下一篇主要介绍一下鉴权实现。