尚品汇总结五:商品详情模块(面试专用)

一、登录业务介绍

早期单一服务器,用户认证

缺点:单点性能压力,无法扩展

WEB应用集群,session共享模式

Tomcat广播session

分布式,SSO(single sign on)模式

业务流程图

二、认证中心模块

数据库表:user_info!密码应该是加密的!

在设计密码加密方式时 一般是使用MD5+盐的方式进行加密和解密

1.登录业务流程

 密码:要求用户在注册的时候 多种组合.

 要求  大写 小写字母+数字+特殊字符  必须超过8位.

2、业务实现

Web-all工程

package com.atguigu.gmall.all.controller;

/**

 * <p>

 * 用户认证接口

 * </p>

 *

 */

@Controller

@RequestMapping

public class PassportController {

    @GetMapping("login.html")

    public String login(HttpServletRequest request) {

        String originUrl = request.getParameter("originUrl");

        request.setAttribute("originUrl",originUrl);

        return "login";

    }

}

认证中心模块service-user

package com.atguigu.gmall.user.controller;

  /**

 * <p>

 * 用户认证接口

 * </p>

 */

  @RestController

@RequestMapping("/api/user/passport")

  public class PassportController {

    @Autowired

    private UserService userService;

    @Autowired

    private RedisTemplate redisTemplate;

    @PostMapping("login")

    public Result login(@RequestBody UserInfo userInfo, HttpServletRequest request, HttpServletResponse response) {

        System.out.println("进入控制器!");

        UserInfo info = userService.login(userInfo);

        if (info != null) {

            String token = UUID.randomUUID().toString().replaceAll("-", "");

            HashMap<String, Object> map = new HashMap<>();

            map.put("name", info.getName());

            map.put("nickName", info.getNickName());

            map.put("token", token);

            redisTemplate.opsForValue().set(RedisConst.USER_LOGIN_KEY_PREFIX + token, info.getId().toString(), RedisConst.USERKEY_TIMEOUT, TimeUnit.SECONDS);

            return Result.ok(map);

        } else {

            return Result.fail().message("用户名或密码错误");

        }

    }

package com.atguigu.gmall.user.service.impl;

  @Service

  public class UserServiceImpl implements UserService {

    // 调用mapper 层

    @Autowired

    private UserInfoMapper userInfoMapper;

    @Override

    public UserInfo login(UserInfo userInfo) {

        // select * from userInfo where userName = ? and passwd = ?

        // 注意密码是加密:

        String passwd = userInfo.getPasswd(); //123

        // 将passwd 进行加密

        String newPasswd = DigestUtils.md5DigestAsHex(passwd.getBytes());

        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();

        queryWrapper.eq("login_name", userInfo.getLoginName());

        queryWrapper.eq("passwd", newPasswd);

        UserInfo info = userInfoMapper.selectOne(queryWrapper);

        if (info != null) {

            return info;

        }

        return null;

    }

}

三、用户认证与服务网关整合

1、业务流程

  1. 所有请求都会经过服务网关,服务网关对外暴露服务,不管是api异步请求还是web同请求都走网关,在网关进行统一用户认证
  2. 既然要在网关进行用户认证,网关得知道对哪些url进行认证,所以我们得对url制定规则
  3. Web页面同请求(如:*.html),我采取配置白名单的形式,凡是配置在白名单里面的请求都是需要用户认证的(注:也可以采取域名的形式,方式多多)
  4. Api接口异步请求的,我们采取url规则匹配,如:/api/**/auth/**,如凡是满足该规则的都必须用户认证

网关全局过滤器做了什么事???

请求过滤,过滤到用户的请求,统一鉴权,鉴别有没有访问权限.

用户登录的认证,getUserId(),根据token去redis中查用户id.返回用户id之前 还有判断IP是否一致,当前访问的ip和登录时的ip比对,如果不一致,说明用户的环境发生变化了,让他去登录.

2、业务实现

/**

 * <p>

 * 全局Filter,统一处理会员登录与外部不允许访问的服务

 * </p>

 *

 */

@Component

public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Autowired

    private RedisTemplate redisTemplate;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    //web服务配置必须登录的url

    private static String[] authUrls = {"trade.html","myOrder.html","list.html"};

    @Override

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();

        String path = request.getURI().getPath();

        //内部服务接口,不允许外部访问

        if(antPathMatcher.match("/**/inner/**", path)) {

            ServerHttpResponse response = exchange.getResponse();

            return out(response);

        }

        String userId = this.getUserId(request);

        //api接口,异步请求,校验用户必须登录

        if(antPathMatcher.match("/api/**/auth/**", path)) {

            if(StringUtils.isEmpty(userId)) {

                ServerHttpResponse response = exchange.getResponse();

                return out(response);

            }

        }

        // web服务,同步请求,校验用户必须登录

        for(String url : authUrls) {

            if(path.indexOf(url) != -1 && StringUtils.isEmpty(userId)) {

                ServerHttpResponse response = exchange.getResponse();

                //303状态码表示由于请求对应的资源存在着另一个URI,应使用GET方法定向获取请求的资源

                response.setStatusCode(HttpStatus.SEE_OTHER);

                response.getHeaders().set(HttpHeaders.LOCATION, "http://www.gmall.com/login.html?originUrl="+request.getURI());

                return response.setComplete();

            }

        }

        //设置网关请求头

        String userTempId = this.getUserTempId(request);

        if(!StringUtils.isEmpty(userId) || !StringUtils.isEmpty(userTempId)) {

            if(!StringUtils.isEmpty(userId)) {

                request.mutate().header("userId", userId).build();

            }

            if(!StringUtils.isEmpty(userTempId)) {

                request.mutate().header("userTempId", userTempId).build();

            }

            //将现在的request 变成 exchange对象

            return chain.filter(exchange.mutate().request(request).build());

        }

        return chain.filter(exchange);

    }

    @Override

    public int getOrder() {

        return 0;

    }

    /**

     * api接口鉴权失败返回数据

     * @param response

     * @return

     */

    private Mono<Void> out(ServerHttpResponse response) {

        Result result = Result.build(null, ResultCodeEnum.LOGIN_AUTH);

        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));

    }

    /**

     * 获取当前登录用户id

     * @param request

     * @return

     */

    private String getUserId(ServerHttpRequest request) {

        String token = "";

        List<String> tokenList = request.getHeaders().get("token");

        if(null  != tokenList) {

            token = tokenList.get(0);

        } else {

            MultiValueMap<String, HttpCookie> cookieMultiValueMap =  request.getCookies();

            HttpCookie cookie = cookieMultiValueMap.getFirst("token");

            if(cookie != null){

                token = URLDecoder.decode(cookie.getValue());

            }

        }

        if(!StringUtils.isEmpty(token)) {

            String userId = (String)redisTemplate.opsForValue().get("user:login:" + token);

            return userId;

        }

        return "";

    }

    /**

     * 获取当前用户临时用户id

     * @param request

     * @return

     */

    private String getUserTempId(ServerHttpRequest request) {

        String userTempId = "";

        List<String> tokenList = request.getHeaders().get("userTempId");

        if(null  != tokenList) {

            userTempId = tokenList.get(0);

        } else {

            MultiValueMap<String, HttpCookie> cookieMultiValueMap =  request.getCookies();

            HttpCookie cookie = cookieMultiValueMap.getFirst("userTempId");

            if(cookie != null){

                userTempId = URLDecoder.decode(cookie.getValue());

            }

        }

        return userTempId;

    }

}

3、常见问题:

  1. cookie被禁用了能登录吗?-----不能

咱们把token放到了 cookie中,禁用了 放不进去了,后续就拿不到token了.

怎么解决的? -------给用户提示:请您启用浏览器Cookie功能或更换浏览器

从后端 能获取cookie信息,如果cookie为空.

前端 向 cookie中设置值,设置不进去,给你提示!!!

     2.一顿绕

用户在A浏览器登录了  B浏览器还用登录吗?

 需要登录,不同的浏览器有不同的cookie.

PC 端登录了,  客户端还用登录吗?

也是需要的,PC 和客户端 就相当于不同的浏览器.

PC:  电脑的浏览器

客户端:手机中的 APP这种

移动端:手机中的浏览器  QQ浏览器  UC浏览器 百度浏览器

   3.用户登录信息多久过期?

分情况的.

PC端:可以设置 浏览器关闭就退出登录,前端能获取.

       Redis中有效期和 cookie中有效期都行.

   2小时   24小时  7 都行.不能设置成 1分钟  20秒这种.

客户端: 登录信息 只要用户不清除APP的数据,登录一直有效.

   4、怎么防止cookie盗用?

 登录之后的token 不是存在用户浏览器的cookie中吗,用户做什么事,电商平台是保证不了的.别人获取到了 用户浏览器cookie中的数据,拿到了这个token,把token放到他自己浏览器的cookie中,访问咱们这个电商平台,就业安全问题了.

解决:

后续访问的时候  在网关 比对ip地址了,登录时的ip和后续访问的ip比较,如果不同,说明环境发生变化了,让他重新登录。

还有让token 不能见面之意。叫什么名 只要开发人员知道就行了。还可以设置一些混淆数据。

简历:

责任描述:

负责单点登录模块功能开发,包括账号密码、手机验证码、微信扫码等功能;

猜你喜欢

转载自blog.csdn.net/leader_song/article/details/132112915