当前位置: 首页 > 编程笔记 >

SpringBoot SSO轻松实现(附demo)

孔飞翔
2023-03-14
本文向大家介绍SpringBoot SSO轻松实现(附demo),包括了SpringBoot SSO轻松实现(附demo)的使用技巧和注意事项,需要的朋友参考一下

前言

网上SSO的框架很多,此篇文章使用的是自写的SSO来实现简单的登录授权功能,目的在于扩展性,权限这方面,自写扩展性会好点。

提示:以下是本篇文章正文内容,下面案例可供参考

一、技术介绍

1.SSO是什么?

单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的。

二、使用步骤

1.引入maven库

代码如下(示例):

 <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.4.1</version>
     <relativePath/>
  </parent>
   <dependencies>
    <dependencies>
    <dependency>
      <artifactId>hyh-boot-starter-redis</artifactId>
      <groupId>com.hyh.redis</groupId>
      <version>1.0.0</version>
    </dependency>
  </dependencies>

2.具体使用示例

ILogin接口:

package com.hyh.sso;

import com.hyh.sso.po.LoginResult;

/**
 * 登录接口
 *
 * @Author: heyuhua
 * @Date: 2021/1/8 17:14
 */
public interface ILogin {

  /**
   * 登录
   *
   * @param account   用户名
   * @param password  密码
   * @param callbackUrl 用户验证回调URL
   * @return
   */
  LoginResult login(String account, String password, String callbackUrl);
}

登录状态枚举:

package com.hyh.sso;

/**
 * 登录状态枚举
 *
 * @Author: heyuhua
 * @Date: 2021/1/8 16:59
 */
public enum LoginStatus {

  SUCCESS(1, "登录成功"), ING(0, "登录中"), FAIL(-1, "登录失败"),
  ERROR(-2, "登录异常"), CALLBACK_ERROR(-3, "登录回调异常"), ACCOUNT_LOCK(-4, "账户被锁定"),
  EXPIRE(-5,"登录用户已过期");
  /**
   * 登录状态码
   */
  private int code;
  /**
   * 登录状态消息
   */
  private String message;


  private LoginStatus(int code, String message) {
    this.code = code;
    this.message = message;
  }


  public int getCode() {
    return code;
  }

  public void setCode(int code) {
    this.code = code;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }
  }

登录类型枚举:

package com.hyh.sso;

/**
 * 登录类型
 *
 * @Author: heyuhua
 * @Date: 2021/1/8 17:16
 */
public enum LoginTypes {

  /**
   * 登入
   */
  IN,
  /**
   * 登出
   */
  OUT;
}

登录常规接口:

package com.hyh.sso;

package com.hyh.sso.service;

import com.hyh.sso.ILogin;

/**
 * 常规登录接口
 *
 * @Author: heyuhua
 * @Date: 2021/1/8 17:54
 */
public interface LoginService extends ILogin {

}

登录接口实现:

package com.hyh.sso.service.impl;

import com.alibaba.fastjson.JSON;
import com.hyh.sso.LoginStatus;
import com.hyh.sso.po.LoginResult;
import com.hyh.sso.po.LoginUser;
import com.hyh.sso.service.LoginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * 登录接口实现
 *
 * @Author: heyuhua
 * @Date: 2021/1/8 17:56
 */
@Service
public class LoginServiceImpl implements LoginService {

  private static final Logger LOG = LoggerFactory.getLogger(LoginServiceImpl.class);

  /**
   * rest接口请求模板
   */
  private static RestTemplate restTemplate = new RestTemplate();


  @Override
  public LoginResult login(String account, String password, String callbackUrl) {
    LoginResult loginResult = null;
    try {
      HttpHeaders headers = new HttpHeaders();
      //设置请求媒体数据类型
      headers.setContentType(MediaType.APPLICATION_JSON);
      //设置返回媒体数据类型
      headers.add("Accept", MediaType.APPLICATION_JSON.toString());
      HttpEntity<String> formEntity = new HttpEntity<String>(JSON.toJSONString(new LoginUser(account, password)), headers);
      loginResult = restTemplate.postForObject(callbackUrl, formEntity, LoginResult.class);
    } catch (Exception e) {
      LOG.error("login valid callback error", e);
      return new LoginResult(LoginStatus.CALLBACK_ERROR);
    }
    return loginResult == null ? new LoginResult(LoginStatus.ERROR) : loginResult;
  }
}

登录用户对象:

package com.hyh.sso.po;

/**
 * 登录用户对象
 *
 * @Author: heyuhua
 * @Date: 2021/1/8 16:58
 */
public class LoginUser {

  /**
   * 账号
   */
  private String account;
  /**
   * 密码
   */
  private String password;

  /**
   * 登录时间
   */
  private String loginTime;

  public LoginUser(String account, String password) {
    this.account = account;
    this.password = password;
  }
  public LoginUser() {

  }


  public String getAccount() {
    return account;
  }

  public void setAccount(String account) {
    this.account = account;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getLoginTime() {
    return loginTime;
  }

  public void setLoginTime(String loginTime) {
    this.loginTime = loginTime;
  }
}

用户Token对象:

package com.hyh.sso.po;

import com.hyh.utils.code.MD5;
import com.hyh.utils.common.StringUtils;

import java.util.Calendar;

/**
 * 用户Token对象
 *
 * @Author: heyuhua
 * @Date: 2021/1/8 17:07
 */
public class UserToken {

  /**
   * token
   */
  private String token;

  /**
   * 过期时间
   */
  private String expireTime;

  public UserToken(String token, String expireTime) {
    this.token = token;
    this.expireTime = expireTime;
  }

  public UserToken() {

  }

  public static UserToken getUserToken() {
    Calendar nowTime = Calendar.getInstance();
    nowTime.add(Calendar.MINUTE, 30);
    return new UserToken(MD5.getMD5String(StringUtils.ranStr(32)), String.valueOf(nowTime.getTimeInMillis()));
  }

  public String getToken() {
    return token;
  }

  public void setToken(String token) {
    this.token = token;
  }

  public String getExpireTime() {
    return expireTime;
  }

  public void setExpireTime(String expireTime) {
    this.expireTime = expireTime;
  }

  /**
   * 生成Token
   */
  private String generateToken() {
    return MD5.getMD5String(StringUtils.ranStr(32));
  }
}

登录结果对象:

package com.hyh.sso.po;

import com.hyh.sso.LoginStatus;
import com.hyh.sso.LoginTypes;

/**
 * 登录结果对象
 * @Author: heyuhua
 * @Date: 2021/1/8 16:58
 */
public class LoginResult {
  /**
   * 登录用户对象
   */
  private LoginUser loginUser;
  /**
   * 登录用户令牌
   */
  private UserToken userToken;

  /**
   * 登录状态
   */
  private LoginStatus loginStatus;

  /**
   * 登录类型
   */
  private LoginTypes loginTypes;

  public LoginResult(){}

  public LoginResult(LoginStatus loginStatus) {
    this.loginStatus = loginStatus;
  }

  public LoginUser getLoginUser() {
    return loginUser;
  }

  public void setLoginUser(LoginUser loginUser) {
    this.loginUser = loginUser;
  }

  public UserToken getUserToken() {
    return userToken;
  }

  public void setUserToken(UserToken userToken) {
    this.userToken = userToken;
  }

  public LoginStatus getLoginStatus() {
    return loginStatus;
  }

  public void setLoginStatus(LoginStatus loginStatus) {
    this.loginStatus = loginStatus;
  }

  public LoginTypes getLoginTypes() {
    return loginTypes;
  }

  public void setLoginTypes(LoginTypes loginTypes) {
    this.loginTypes = loginTypes;
  }

  @Override
  public String toString() {
    return "LoginResult{" +
        "loginUser=" + loginUser +
        ", userToken=" + userToken +
        ", loginStatus=" + loginStatus +
        ", loginTypes=" + loginTypes +
        '}';
  }
}

登录助手:

package com.hyh.sso.helper;

import com.alibaba.fastjson.JSON;
import com.hyh.redis.helper.RedisHelper;
import com.hyh.sso.LoginStatus;
import com.hyh.sso.po.LoginResult;
import com.hyh.sso.po.LoginUser;
import com.hyh.sso.po.UserToken;
import com.hyh.sso.service.LoginService;
import com.hyh.utils.common.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 登录助手
 *
 * @Author: heyuhua
 * @Date: 2021/1/8 17:13
 */
@Component
public class LoginHelper {

  /**
   * 日志
   */
  private static final Logger LOG = LoggerFactory.getLogger(LoginHelper.class);

  /**
   * 登录用户信息KEY
   */
  private final String LOGIN_USER_KEY = "login:user:";
  /**
   * 登录用户TOKEN KEY
   */
  private final String LOGIN_TOKEN_KEY = "login:token:";
  /**
   * 登录失败统计 KEY
   */
  private final String LOGIN_FAIL_COUNT_KEY = "login:fail:count";
  /**
   * 登录失败最多允许次数
   */
  private final long MAX_FAIL_COUNT = 5;


  /**
   * 登录服务
   */
  @Resource
  private LoginService loginService;

  /**
   * redis助手
   */
  @Autowired
  private RedisHelper redisHelper;


  /**
   * 登录
   *
   * @param account   用户名
   * @param password  密码
   * @param callbackUrl 回调URL
   * @return
   */
  public LoginResult login(String account, String password, String callbackUrl) {
    Assert.notNull(account, "account is null ");
    Assert.notNull(password, "password is null ");
    Assert.notNull(callbackUrl, "callbackUrl is null ");
    //判断账户是否多次登录失败被锁定
    String value = redisHelper.getStringValue(LOGIN_FAIL_COUNT_KEY + account);
    if (StringUtils.isNotBlank(value)) {
      Long loginFailCount = Long.parseLong(value);
      if (loginFailCount.longValue() >= MAX_FAIL_COUNT) {
        return new LoginResult(LoginStatus.ACCOUNT_LOCK);
      }
    }
    //登录操作
    LoginResult loginResult = loginService.login(account, password, callbackUrl);
    switch (loginResult.getLoginStatus()) {
      case SUCCESS:
        //登录成功
        loginSuccess(loginResult);
        break;
      case FAIL:
        //登录失败
        loginFail(loginResult);
        break;
      case ERROR:
        loginError(loginResult);
        //登录异常
        break;
      default:
        break;
    }
    return loginResult;
  }

  /**
   * 注销
   *
   * @param account
   * @param token
   */
  public void logout(String account, String token) {
    Assert.notNull(account, "account is null ");
    Assert.notNull(token, "token is null ");
    removeKey(account, token);
  }

  /**
   * 注销
   *
   * @param token
   */
  public void logout(String token) {
    Assert.notNull(token, "token is null ");
    removeKey(token);
  }

  /**
   * 获取登录用户
   *
   * @param token
   * @return
   */
  public LoginUser getLoginUser(String token) {
    Assert.notNull(token, "token is null ");
    String value = redisHelper.getStringValue(LOGIN_USER_KEY + token);
    if (StringUtils.isNotBlank(value)) {
      return JSON.parseObject(value, LoginUser.class);
    }
    return null;
  }

  /**
   * 移除 key
   *
   * @param account
   * @param token
   */
  private void removeKey(String account, String token) {
    redisHelper.del(LOGIN_FAIL_COUNT_KEY + account);
    redisHelper.del(LOGIN_TOKEN_KEY + account);
    redisHelper.del(LOGIN_USER_KEY + token);
  }

  /**
   * 移除 Key
   *
   * @param token
   */
  private void removeKey(String token) {
    redisHelper.del(LOGIN_USER_KEY + token);
    //其余的key到达过期时间自动过期
  }


  /**
   * 登录异常
   *
   * @param loginResult
   */
  private void loginError(LoginResult loginResult) {
    LOG.error("user 【" + loginResult.getLoginUser().getAccount() + "】 login error");
  }

  /**
   * 登录失败操作
   *
   * @param loginResult
   */
  private void loginFail(LoginResult loginResult) {
    String key = LOGIN_FAIL_COUNT_KEY + loginResult.getLoginUser();
    redisHelper.increment(key, 30 * 60 * 1000);
  }

  /**
   * 登录成功操作
   *
   * @param loginResult
   */
  private void loginSuccess(LoginResult loginResult) {
    LoginUser loginUser = loginResult.getLoginUser();
    loginUser.setLoginTime(String.valueOf(new Date().getTime()));
    UserToken userToken = UserToken.getUserToken();
    redisHelper.set(LOGIN_TOKEN_KEY + loginResult.getLoginUser().getAccount(), JSON.toJSONString(userToken), 30, TimeUnit.MINUTES);
    redisHelper.set(LOGIN_USER_KEY + userToken.getToken(), JSON.toJSONString(loginUser), 30, TimeUnit.MINUTES);
    redisHelper.del(LOGIN_FAIL_COUNT_KEY + loginResult.getLoginUser());
  }
}

3.配置文件

代码如下(示例):

server:
 port: 8088


spring:
 #redis配置
 redis:
  host: 192.168.6.134
  port: 30511
  password:

4.单元测试

测试代码如下(示例):

 @Autowired
  private LoginHelper loginHelper;

  @Test
  public void testLogin() {
    //测试时先开启HyhBootApplication
    String account = "hyh";
    String password = "hyh-pwd";
    String cllbackUrl = "http://localhost:8088/hyh/login";//在com.hyh.core.web下可查看
    LoginResult loginResult = loginHelper.login(account, password, cllbackUrl);
    System.out.println("loginResult:" + loginResult.toString());
  }
//控制层代码
  @RequestMapping(value = "login", method = RequestMethod.POST)
  public LoginResult login(@RequestBody LoginUser loginUser) {
    Assert.notNull(loginUser.getAccount(), "account is null");
    Assert.notNull(loginUser.getPassword(), "password is null");
    LoginResult loginResult = new LoginResult(LoginStatus.SUCCESS);
    loginResult.setLoginUser(loginUser);
    //模拟直接返回登录成功
    return loginResult;
  }

总结

是不是感觉很简单?更多用法请点击下方查看源码,关注我带你揭秘更多高级用法

源码地址:点此查看源码.

到此这篇关于SpringBoot SSO轻松实现(附demo)的文章就介绍到这了,更多相关SpringBoot SSO内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!

 类似资料:
  • 本文向大家介绍轻松实现Android锁屏功能,包括了轻松实现Android锁屏功能的使用技巧和注意事项,需要的朋友参考一下 锁屏需要引入设备超级管理员。在文档Android开发文档的Administration中有详细的说明。Android设备管理系统功能和控制访问。 主要有一下几个步骤: 1  创建广播接收者,实现DeviceAdminReceiver 2 在清单文件中注册该广播(不同普通的广播

  • 本文向大家介绍轻松实现Android语音识别功能,包括了轻松实现Android语音识别功能的使用技巧和注意事项,需要的朋友参考一下 苹果的iphone有语音识别用的是Google的技术,做为Google力推的Android 自然会将其核心技术往Android 系统里面植入,并结合google 的云端技术将其发扬光大。 所以Google Voice Recognition在Android 的实现就变

  • 本文向大家介绍jQuery轻松实现无缝轮播效果,包括了jQuery轻松实现无缝轮播效果的使用技巧和注意事项,需要的朋友参考一下 这个无缝轮播和那个图片平滑滚动的原理差不多。 原理:ul向左滚动,滚动一次,第一个li向ul插入,然后在让怎个ul的left值为0,也就是初始状态,这个状态太快我们无法看到,所以才会有平滑滚动的效果 //CSS //HTML //JQUERY 以上就是本文的全部内容,希望

  • 本文向大家介绍php轻松实现文件上传功能,包括了php轻松实现文件上传功能的使用技巧和注意事项,需要的朋友参考一下 本文分为五个部分针对php上传文件进行分析讲解,具体内容如下 文件上传变量 将服务器上的临时文件移动到指定目录下 php.ini上传相关配置 error错误号 单文件上传实例 1.文件上传变量 2.将服务器上的临时文件移动到指定目录下 3.php.ini上传相关配置 4.error错

  • 本文向大家介绍AngularJS轻松实现双击排序的功能,包括了AngularJS轻松实现双击排序的功能的使用技巧和注意事项,需要的朋友参考一下 话不多说,直接看示例代码 HTML代码 其中 la=!la 是用来判断当前点击是true还是false JS代码 总结 好了,以上就是AngularJS实现双击排序功能的全部内容,通过以上示例代码便可实现双击排序,希望对大家学习AngularJS能有所帮助

  • 本文向大家介绍iOS轻松实现导航栏透明渐变,包括了iOS轻松实现导航栏透明渐变的使用技巧和注意事项,需要的朋友参考一下 首先我们来看下效果 一开始当我们什么只设置了一张图片作为它的头部视图的时候,它是这样的 1.首当其冲的,我们先得把导航栏弄透明 那么我们首先得知道,设置navigationBar的BackgroundColor为Clear是没用的,你可以试着设置它的clear,但是没用,原因一会