一、需求

整个微服务项目有两个服务,一个用户鉴权服务和业务处理服务。基本需求是用户通过鉴权服务进行用户认证操作并返回令牌Token,后续通过token访问业务服务。

二、项目基本结构

- gateway
- service
  - service-user
  - service-business

service-user是用户鉴权服务,主要采用SpringSecurity实现用户鉴权。service-business则是一些业务操作。
gateway主要用于服务分发

三、鉴权服务

用户鉴权服务实现的主要思路是用户登录验证成功后生成token,并将其存放在redis中。
SpringSecurity的相关使用就不多赘述,网上有很多教程
用户认证成功后调用的方法

    //2 认证成功调用的方法
    @Override
    protected void successfulAuthentication(HttpServletRequest request, 
                                            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
        System.out.println("认证成功...");
        //认证成功,得到认证成功之后用户信息
        SecurityUser user = (SecurityUser)authResult.getPrincipal();
        //把用户名称和用户权限列表放到redis
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getNickName(),user.getPermissionValueList());
        ResponseUtil.out(response, Result.ok(userService.loginSuccess(user,request,tokenManager)));
    }

userService.java

@Override
    public User loginSuccess(SecurityUser user, HttpServletRequest request, TokenManager tokenManager) {
        //根据用户名生成token
        String token = tokenManager.createToken(user.getCurrentUserInfo().getNickName());
        System.out.println("认证成功后生成的用户token:"+token);
        // 记录用户登录日志
        User userInfo = user.getCurrentUserInfo();
        String ipAddr = NetworkUtil.getIpAddr(request);
        userLoginRecordService.userLoginRecord(userInfo.getId(),ipAddr);
        //返回token
        userInfo.setToken(token);
        //清空密码
        userInfo.setPassword(null);
        //设置权限列表
        userInfo.setPermissionList(user.getPermissionValueList());
        return userInfo;
    }

在登录成功的方法里可以进行一系列操作(比如记录用户登录日志),将用户信息及生成的token返回给前端

四、网关

前端请求头中带上鉴权成功返回的token访问网关

AuthGlobalFilter.java

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private TokenManager tokenManager = new TokenManager();
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("==="+path);

        //登录和注册请求直接放行
        if(antPathMatcher.match("/api/user/login", path) ||
                antPathMatcher.match("/api/user/register", path)) {
            return chain.filter(exchange);
        }

        //api接口,异步请求,校验用户必须登录
        if(antPathMatcher.match("/api/**", path)) {
            String userName = this.getUserName(request);
            System.out.println("获取登录用户信息:"+userName);
            if(StringUtils.isEmpty(userName)) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response, ResultCodeEnum.LOGIN_AUTH);
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * api接口鉴权失败返回数据
     * @param response
     * @return
     */
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        Result result = Result.build(null, resultCodeEnum);
        byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 获取当前登录用户信息
     * @param request
     * @return
     */
    private String getUserName(ServerHttpRequest request) {
        String token = "";
        List<String> tokenList = request.getHeaders().get("token");
        if(null != tokenList) {
            token = tokenList.get(0);
        }
        System.out.println("获取到的token:"+token);
        if(!StringUtils.isEmpty(token)) {
            // 通过获取到的token获取用户名并检查redis中是否保留登录信息
            // TODO
            // 如果redis中不存在用户登录信息则直接返回null
            return tokenManager.getUserInfoFromToken(token);
        }
        return null;
    }
}

网关通过解析token获取用户名,然后通过用户名查询redis获取用户权限,这样就能进行下一步操作了。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐