Bootstrap

单点登录实现

5.1. 单点登录

  1. 单一服务器登陆 : 不使用集群部署,使用Session,将用户数据放进去再取出来
  2. 集群部署( 单点登录 SSO) : 分布式,多个模块,独立运行,各个模块端口号不同服务器不同,要求只在一个模块登录其他模块就不需要再登录
  3. 单点登录实现方式( 三种 )
  • session广播机制 : 也就是复制用户信息去其他模块,缺点是模块多的话会消耗太多资源 --> 默认过期时间30分钟
  • cookie + redis 实现 : cookie是浏览器携带的,redis缓存(key,value) --> redis可以设置过期时间
  1. 登录后把数据放在两个地方,cookie以及redis
    • redis: 在key生成唯一随机数(id,ip等) 在value放用户数据
    • cookie: 把redis里面生成的key存放在cookie里面
  1. 访问项目其他模块,发送请求时带着cookie进行发送,获取cookie值,拿着cookie去做事
  • 把cookie获取值,到redis进行查询,根据key查询,查询到数据就登录
  • token实现( 自包含令牌 ) : token 是什么 --> 按照一定的规则生成字符串,字符串中可以包含用户信息 --> 也可以设置过期时间
  1. 在项目某个模块进行登录后,按照一定规则生成字符串,把登录之后的信息包含在生成的的字符串当中,再将字符串返回
  • 可以将字符串通过cookie返回
  • 把字符串通过地址栏返回Header中
  1. 再去访问项目的其他模块的时候 , 每次访问时在地址栏带着生成的字符串,在访问模块里面获取地址栏字符串,根据字符串获取用户信息,可以获取的到那就登录

5.1.1. JWT介绍

  1. JWT: token是按照一定的规则生成字符串,其中包含用户信息,规则没有一个很确定的准则,而JWT就是定义了一种通用的规则去让token生成字符串包含用户信息
  2. JWT主要由三部分组成: JWT头信息,有效载荷( 用户信息 ),签名哈希( 防伪标志 )
String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("qinxue-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

  1. JWT中的方法介绍
public class JwtUtils {
    //token过期时间24h
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    //防伪哈希
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
    //设置JWT
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()
        .setHeaderParam("typ", "JWT")
        .setHeaderParam("alg", "HS256")
        .setSubject("qinxue-user")
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
        .claim("id", id)
        .claim("nickname", nickname)
        .signWith(SignatureAlgorithm.HS256, APP_SECRET)
        .compact();
        return JwtToken;
    }
    //判断token是否存在与有效
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    //判断token是否存在与有效
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    // 根据token获取会员id
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

5.2. 登录思路

  1. 将前端返回来的会员对象信息接收,,调用后端servcice的登录方法判断用户是否存在,返回一个token令牌
@PostMapping("login")
@ApiOperation("登录")
public R login(@ApiParam(name = "loginDto",value = "会员对象")
                   @RequestBody LoginDto loginDto){
    String token = memberService.login(loginDto);//登录时会传给后端一个token令牌用,缓存起来
    return R.ok().data("token",token);
}
  1. service思路:
  • 首先接收前端传过来的loginDto对象信息,获取其中的手机号看是否存在
  • 然后根据手机号查询会员信息看会员是否存在
  • 接着验证密码,密码的话数据库不会采取明文密码( 看得见认识的 ),所以采用MD5加密
  • MD5一般只能用于加密而非解密,所以将我们获取到的密码加密后与数据库密码进行对比
  • 然后验证该用户是否被禁用
  • 最后利用JWT工具类里面的方法去将获取到用户信息变成字符串生成token并返回
 @Override
public String login(LoginDto loginDto) {//登录
    //获取前端传过来的账号密码
    String mobile = loginDto.getMobile();
    String password = loginDto.getPassword();
    //校验参数
    if(StringUtils.isEmpty(mobile) ||
       StringUtils.isEmpty(password)){
        throw new QinXueException(20001,"登录失败");
    }
    //获取会员 通过电话号码查询该用户
    Member member = baseMapper.selectOne(new QueryWrapper<Member>().eq("mobile", mobile));
    if(null == member) {
        throw new QinXueException(20001,"手机号或密码错误");
    }
    //校验密码  MD5加密
    if(!MD5.encrypt(password).equals(member.getPassword())) {
        throw new QinXueException(20001,"手机号或密码错误");
    }
    //校验是否被禁用
    if(member.getIsDisabled()) {
        throw new QinXueException(20001,"error");
    }
    //使用JWT生成token字符串
    String token = JwtUtils.String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("qinxue-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();n(member.getId(), member.getNickname());
    return token;
}

5.3. token获取用

  1. 首先是从JWT中获取到token令牌,获取到会员登录的ID号
  2. 调用service中的方法去根据会员ID获取到登录信息用于前端登录页面个人信息展示
  3. 将获取到的信息返回给前端
@ApiOperation("根据token获取登录信息")
@GetMapping("getLoginInfo")
public R getLoginInfo(HttpServletRequest request) {
try {
    //JWT就是授权给第三方可以登录注册
    String memberId = JwtUtils.getMemberIdByJwtToken(request);
    Member member = memberService.getLoginInfo(memberId);
    return R.ok().data("item", member);
} catch (Exception e) {
    e.printStackTrace();
    throw new QinXueException(20001, "error");
}
}
  1. service思路:
  • 首先根据token中的会员ID去查询到该会员
  • 其次将会员信息中对应的字段信息传递给登录信息用于右上角前端页面回显
@Override
public Member getLoginInfo(String memberId) {//获取登录信从 token 中获取
    Member member = baseMapper.selectById(memberId);
    LoginDto loginDto = new LoginDto();
    BeanUtils.copyProperties(member, loginDto);
    return member;
}
;