身份认证-鉴权功能

93 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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令牌的,这时候就存在盗用风险。

令牌盗用该如何解决?

1612161750008.png

如果令牌被盗,我们可以通过IP识别令牌是否安全,如上图:

1:每次生成令牌的时候,把用户的IP作为令牌的一部分进行MD5加密,并将密文存入到令牌中
2:用户每次访问API接口的时候,都先获取客户端IP,再将IP进行MD5加密,并和令牌中的IP密文比对
3:如果密文一致,则证明IP没有发生变化,如果密文不一致,则证明IP发生变化,提示重新登录

这种操作在很多大厂中都有应用,我们平时登录QQ、微信的时候他们都会提示设备终端发生变化,其实和上图操作是一个道理。

令牌封装

mall-user-service中修改com.xz.mall.user.controller.UserInfoController添加IP封装,代码如下:

1612165355424.png

令牌安全校验

我们在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-cartmall-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上用该令牌,效果如下:

1612168991412.png

总结

本篇主要介绍了一下用户登录获取令牌,实现令牌颁发,还有令牌封装、令牌安全校验的设计,下一篇主要介绍一下鉴权实现。