SpringBoot整合SSO(single sign on)单点登录

1、单点登录三种常见的方式

(1)Session广播机制(Session复制)
(2)使用Cookie+Redis实现
(3)使用token实现
SpringBoot整合SSO(single sign on)单点登录_第1张图片
2、单点登录介绍
SpringBoot整合SSO(single sign on)单点登录_第2张图片
SpringBoot整合SSO(single sign on)单点登录_第3张图片
SpringBoot整合SSO(single sign on)单点登录_第4张图片
SpringBoot整合SSO(single sign on)单点登录_第5张图片
举例:
SpringBoot整合SSO(single sign on)单点登录_第6张图片
(1)引入jwt依赖

            <!-- JWT-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
            </dependency>

(2)创建JWTUtils工具类

public class JwtUtils {

    //token过期时间
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    //秘钥
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                //设置头信息
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("user")
                .setIssuedAt(new Date())
                //设置过期时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                //设置token主体部分(这里使用id和nickname作为主体部分)
                .claim("id", id)
                .claim("nickname", nickname)
                //加密方式
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效(直接通过APP_SECRET解析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是否存在与有效(通过获取请求头信息获取token再使用APP_SECRET解析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");
    }

}

3、单点登录实现

项目目录结构
SpringBoot整合SSO(single sign on)单点登录_第7张图片
UcenterMemberController

@RestController
@RequestMapping("/user/")
@CrossOrigin
public class UcenterMemberController {

    @Autowired
    private UcenterMemberService ucenterMemberService;
	//登录
    @PostMapping("login")
    public ResponseResult login(@RequestBody MobileLoginRequest request) {
        String token = ucenterMemberService.login(request);
        return ResponseResult.success().data("token", token);
    }
	//注册
    @PostMapping("register")
    public ResponseResult register(@RequestBody RegisterRequest request) {
        ucenterMemberService.register(request);
        return ResponseResult.success().message("注册成功");
    }
	//根据token获取用户信息
    @GetMapping("getUserInfo")
    public ResponseResult getUserInfo(HttpServletRequest request) {
        //调用jwt工具类的方法,根据request对象获取头信息,返回用户id
        String id = JwtUtils.getMemberIdByJwtToken(request);
        //根据用户id查询用户
        UcenterMember member = ucenterMemberService.getById(id);
        return ResponseResult.success().data("userInfo", member);
    }

}

ServiceImpl

@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {

    @Autowired
    private StringRedisTemplate redisTemplate;

	//登录
    @Override
    public String login(MobileLoginRequest request) {

        String phone = request.getPhone();
        String password = request.getPassword();
        if (StrUtil.isBlank(phone) || StrUtil.isBlank(password)) {
            throw new GuliException(200001, "请输入用户名或者密码");
        }
        //根据输入的手机号码查找该用户信息
        UcenterMember ucenterByPhone = this.baseMapper.selectOne(new LambdaQueryWrapper<UcenterMember>().eq(UcenterMember::getMobile, phone));
        if (ucenterByPhone == null) {
            throw new GuliException(200002, "该用户名不存在");
        }
        //如果用户存在比对数据库密码和用户输入的密码
        if (!MD5Util.encrypt(password).equals(ucenterByPhone.getPassword())) {
            throw new GuliException(200003, "密码输入错误");
        }
        String token = JwtUtils.getJwtToken(ucenterByPhone.getId(), ucenterByPhone.getNickname());
        return token;
    }

	//注册
    @Override
    public void register(RegisterRequest request) {
        String phone = request.getPhone();
        String password = request.getPassword();
        String nickName = request.getNickName();
        String code = request.getCode();
        if (StrUtil.isBlank(phone) || StrUtil.isBlank(password) || StrUtil.isBlank(nickName) || StrUtil.isBlank(code)) {
            throw new GuliException(200001, "请填写相关信息");
        }
        //判断手机号是否重复
        Integer count = baseMapper.selectCount(new LambdaQueryWrapper<UcenterMember>().eq(UcenterMember::getMobile, phone));
        if (count > 0) {
            throw new GuliException(200001, "账号已经存在请重新输入");
        }
        //验证code
        String redisCode = redisTemplate.opsForValue().get(phone);
        if (StrUtil.isBlank(redisCode)) {
            throw new GuliException(200001, "验证码已经过期,请重新获取");
        }
        if (!redisCode.equals(code)) {
            throw new GuliException(200001, "验证码错误");
        }

        UcenterMember ucenterByPhone = new UcenterMember();
        ucenterByPhone.setMobile(phone);
        ucenterByPhone.setPassword(MD5Util.encrypt(password));
        ucenterByPhone.setNickname(nickName);
        ucenterByPhone.setIsDisabled(false);
        int insert = baseMapper.insert(ucenterByPhone);
        if(insert<=0){
            throw new GuliException(20001,"注册失败");
        }
    }
}

MD5加密算法工具类

public final class MD5Util {

    public static String encrypt(String strSrc) {
        try {
            char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f'};
            byte[] bytes = strSrc.getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (int i = 0; i < bytes.length; i++) {
                byte b = bytes[i];
                chars[k++] = hexChars[b >>> 4 & 0xf];
                chars[k++] = hexChars[b & 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException("MD5加密出错!!+" + e);
        }
    }

    public static void main(String[] args) {
        System.out.println(MD5Util.encrypt("111111"));
    }

}

4、登录完成后在前端界面展示用户信息
SpringBoot整合SSO(single sign on)单点登录_第8张图片
(1)第一、二、四步:登录的方法(记得npm install js-cookie)

  //登录的方法
      submitLogin() {
        //第一步 调用接口进行登录,返回token字符串
        loginApi.submitLoginUser(this.user) 
           .then(response => {
             //第二步 获取token字符串放到cookie里面
             //第一个参数cookie名称,第二个参数值,第三个参数作用范围
             cookie.set('user_token',response.data.data.token,{domain: 'localhost'})
             
              //第四步 调用接口 根据token获取用户信息,为了首页面显示
              loginApi.getLoginUserInfo()
                .then(response => {
                  this.loginInfo = response.data.data.userInfo
                  //获取返回用户信息,放到cookie里面(主页在cookie中获取用户信息进行展示)
                  cookie.set('user_info',this.loginInfo,{domain: 'localhost'})

                  //跳转页面
                  window.location.href = "/";
                })
           })
      },

(2)第三步:在request.js中编写前端请求拦截器(发送请求携带token)

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
    if (cookie.get('user_token')) {
      config.headers['token'] = cookie.get('user_token') // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

(3)第五步:主页显示用户信息(从cookie中获取用户信息)

//创建方法,从cookie获取用户信息
showInfo() {
  //从cookie获取用户信息
  var userStr = cookie.get('user_info')
  // 把字符串转换json对象(js对象),因为后端传过来的是"{'name','lucy','age':18}"的格式
  if(userStr) {
    this.loginInfo = JSON.parse(userStr)
  }
}

显示用户信息(根据userInfo中id来判断)

<ul class="h-r-login">
    //cookie中没有用户信息,显示登录和注册
    <li v-if="!loginInfo.id" id="no-login">
        <a href="/login" title="登录">
            <em class="icon18 login-icon">&nbsp;</em>
            <span class="vam ml5">登录</span>
        </a>
        |
        <a href="/register" title="注册">
            <span class="vam ml5">注册</span>
        </a>
    </li>
    //cookie中有用户信息,显示用户头像、昵称和退出
    <li v-if="loginInfo.id" id="is-login-two" class="h-r-user">
        <a href="/ucenter" title>
            <img
                 :src="loginInfo.avatar"
                 width="30"
                 height="30"
                 class="vam picImg"
                 alt
                 >
            <span id="userName" class="vam disIb">{{ loginInfo.nickname }}</span>
        </a>
        <a href="javascript:void(0);" title="退出" @click="logout()" class="ml5">退出</a>
    </li>
</ul>

退出登录,清空cookie中的token和用户信息

    //退出
    logout() {
      //清空cookie值
      cookie.set('user_token','',{domain: 'localhost'})
      cookie.set('user_info','',{domain: 'localhost'})
      //回到首页面
      window.location.href = "/";
    }
  }

你可能感兴趣的:(工具)