单点登录SSO(Single Sign On)

  • 单点登录的三种常见方法
    • session广播机制(不常用)
      • 通过session登录之后,将session的值复制到每个模块中
    • cookies和redis实现
      • 在任意一个模块进行登录,登录之后将登录数据存到两个地方
        • redis:key生成唯一随机值,value是用户数据
        • cookie:把redis生成的key值存放到cookie中
      • 访问项目模块时,需要带着cookie发送请求,根据请求带来的cookie到redis查询,查询即表示登录
    • token实现(令牌机制)
      • token表示按照一定的规则(通用的、官方的,如JWT)生成的字符串(可以包含用户的信息)
        • jwt头部信息
        • 有效载荷,包含用户主体信息
        • 签名哈希,防伪标志
      • 在任意一个模块进行登录,登录之后按照一定的规则将登录之后的用户数据生成字符串,通过两种方法方式返回
        • 可以把字符串通过cookie返回
        • 把字符串加到地址栏返回
          • 访问项目模块时,地址栏需要带着生成的字符串发送请求,根据字符串获取用户信息,获取成功即表示登录
      • 导入依赖
        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>
        
      • JWT工具类(内容较为为固定,易知放在公共模块中)
        package com.atguigu.commonutils;
        
        import io.jsonwebtoken.Claims;
        import io.jsonwebtoken.Jws;
        import io.jsonwebtoken.Jwts;
        import io.jsonwebtoken.SignatureAlgorithm;
        import org.springframework.http.server.reactive.ServerHttpRequest;
        import org.springframework.util.StringUtils;
        
        import javax.servlet.http.HttpServletRequest;
        import java.util.Date;
        
        public class JwtUtils {
            // token过期时间
            public static final long EXPIRE = 1000 * 60 * 60 * 24;
            // 密钥(随意写,企业中有规定的字符串)
            public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
        
            // 生成token字符串的方法
            public static String getJwtToken(String id, String nickname){
        
                String JwtToken = Jwts.builder()
                        // 设置头部参数
                        .setHeaderParam("typ", "JWT")
                        .setHeaderParam("alg", "HS256")
                        // 设置分类以及过期时间
                        .setSubject("guli-user")
                        .setIssuedAt(new Date())
                        .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                        // 设置token主体部分,存储用户信息,不限个数(属性)
                        .claim("id", id)
                        .claim("nickname", nickname)
                        // 设置签名哈希,防伪标志
                        .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                        .compact();
        
                return JwtToken;
            }
        
            /**
             * 判断token是否存在与有效
             * @param: jwtToken
             * @return
             */
            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是否存在与有效
             * @param request
             * @return
             */
            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
             * @param request
             * @return
             */
            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");
            }
        }
        
      • 注册时,填写相关信息,获取验证码调用接口的业务代码(可参考博主的“SpringBoot整合阿里云短信服务”)
      • 登录成功时需要返回按照JWT(JWT工具类)规则生成的token字符串的业务代码(其中需求可改)
        @Override
        public String userLogin(UcenterMember member) throws MyException {
            String mobile = member.getMobile();
            String password = member.getPassword();
        
            // 手机号和密码是否为空
            if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
                throw new MyException(20001, "登录失败!");
            }
        
            // 判断手机号是否正确
            QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
            wrapper.eq("mobile", mobile);
            UcenterMember ucenterMember = this.getOne(wrapper);
            if(ucenterMember == null) {
                throw new MyException(20001, "手机号错误!");
            }
            // 判断密码是否正确
            // 将输入密码加密与数据库校验(每个企业的加密方式不一样)
            if(!MD5.encrypt(password).equals(ucenterMember.getPassword())) {
                throw new MyException(20001, "密码错误!");
            }
        
            // 判断用户的状态(禁用 or 活跃)
            if(ucenterMember.getIsDisabled()) {
                throw new MyException(20001, "登录失败!");
            }
        
            // 按照JWT的规则生成token字符串
            String id = ucenterMember.getId();
            String nickname = ucenterMember.getNickname();
            String token = JwtUtils.getJwtToken(id, nickname);
            return token;
        }
        
      • 前端发送请求时将token带回来进行解析(JWT工具类中已有解析token的方法)获取其中的用户主题信息,返回用户的信息给前端,
        // 根据token获取用户信息
        @GetMapping("getMemberInfo")
        public R getMemberInfo(HttpServletRequest request) {
            // 调用jwt工具类的方法,根据request对象获取头信息,返回用户id
            String memberId = JwtUtils.getMemberIdByJwtToken(request);
            // 查询数据库根据用户id获取用户信息
            UcenterMember member = memberService.getById(memberId);
            return R.ok().data("memberInfo", member);
        
        }
        
      • 接下来单点登录的重头戏,vue先下载js-cookie(前端代码)
        • 上面登录成功时返回的token字符串,前端将token字符串放到cookie里面
          //第二步 获取token字符串放到cookie里面
          //第一个参数cookie名称,第二个参数值,第三个参数作用范围
          cookie.set('guli_token',response.data.data.token,{domain: 'localhost'})
          
        • 创建前端拦截器,判断cookie里面是否有token字符串,如果有,就将token字符串放到header(请求头)中
          import axios from 'axios';
          import cookie from 'js-cookie';
          
          // 创建axios实例
          const service = axios.create({
            baseURL: 'http://localhost:9001', // api的base_url
            timeout: 20000 // 请求超时时间
          });
          
          //第三步 创建拦截器  http request 拦截器
          service.interceptors.request.use(
            config => {
            //debugger
            //判断cookie里面是否有名称是guli_token数据
            if (cookie.get('guli_token')) {
              //把获取cookie值放到header里面
              config.headers['token'] = cookie.get('guli_token');
            }
              return config
            },
            err => {
            return Promise.reject(err);
          });
          
        • 根据token值调用相关的接口,根据token获取用户信息,将返回的用户信息放到cookie中,为了显示页面(主页)
          //第四步 调用接口 根据token获取用户信息,为了首页面显示
          loginApi.getLoginUserInfo()
              .then(response => {
                  this.loginInfo = response.data.data.memberInfo
                  //获取返回用户信息,放到cookie里面
                  cookie.set('guli_ucenter',this.loginInfo,{domain: 'localhost'})
          
                  //跳转页面
                  window.location.href = "/";
              })
          
        • 完整的代码大致如下
          //登录的方法
          submitLogin() {
              //第一步 调用接口进行登录,返回token字符串
              loginApi.submitLoginUser(this.user) 
                  .then(response => {
                      //第二步 获取token字符串放到cookie里面
                      //第一个参数cookie名称,第二个参数值,第三个参数作用范围
                      cookie.set('guli_token',response.data.data.token,{domain: 'localhost'})
                   
                      //第四步 调用接口 根据token获取用户信息,为了首页面显示
                      loginApi.getLoginUserInfo()
                          .then(response => {
                              // 一定要在这里转成字符串
                              this.loginInfo = JSON.stringify(response.data.data.memberInfo);
                              //获取返回用户信息,放到cookie里面
                              cookie.set('guli_ucenter',this.loginInfo,{domain: 'localhost'})
              
                              //跳转页面
                              window.location.href = "/";
                          })
                 })
          }
          
        • 从cookie的获取数据显示到页面中
          var user = "cookie中存放用户的信息(上面将json格式转成了字符串类型)"
          // 将信息显示到页面中
          this.userInfo = JSON.parse(user);
          
posted @ 2022-03-05 15:27  xsha_h  阅读(317)  评论(0编辑  收藏  举报