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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于SpringBoot解决CORS跨域的问题(@CrossOrigin)

    一.关于跨域介绍 在前后分离的架构下,跨域问题难免会遇见比如,站点 http://domain-a.com 的某 HTML 页面通过 的 src 请求 http://domain-b.com/image.jpg. 网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源. 出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求. 例如,XMLHttpRequest和Fetch API遵循同源策略. 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非

  • SpringBoot使用Redisson实现分布式锁(秒杀系统)

    前面讲完了Redis的分布式锁的实现,接下来讲Redisson的分布式锁的实现,一般提及到Redis的分布式锁我们更多的使用的是Redisson的分布式锁,Redis的官方也是建议我们这样去做的.Redisson点我可以直接跳转到Redisson的官方文档. 1.1.引入Maven依赖 <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter&l

  • SpringBoot集成Redisson实现延迟队列的场景分析

    使用场景 1.下单成功,30分钟未支付.支付超时,自动取消订单 2.订单签收,签收后7天未进行评价.订单超时未评价,系统默认好评 3.下单成功,商家5分钟未接单,订单取消 4.配送超时,推送短信提醒 ...... 对于延时比较长的场景.实时性不高的场景,我们可以采用任务调度的方式定时轮询处理.如:xxl-job 今天我们采用一种比较简单.轻量级的方式,使用 Redis 的延迟队列来进行处理.当然有更好的解决方案,可根据公司的技术选型和业务体系选择最优方案.如:使用消息中间件Kafka.Rabbi

  • 基于springboot和redis实现单点登录

    本文实例为大家分享了基于springboot和redis实现单点登录的具体代码,供大家参考,具体内容如下 1.具体的加密和解密方法 package com.example.demo.util; import com.google.common.base.Strings; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import javax.crypto.Cipher; import javax.crypto.KeyG

  • springboot集成CAS实现单点登录的示例代码

    最近新参与的项目用到了cas单点登录,我还不会,这怎么能容忍!空了学习并搭建了一个spring-boot 集成CAS 的demo.实现了单点登录与登出. 单点登录英文全称是:Single Sign On,简称SSO. 含义:在多个相互信任的系统中,只要登录一个系统其他系统均可访问. CAS 是一种使用广泛的单点登录实现,分为客户端CAS Client和服务端 CAS Service,客户端就是我们的系统,服务端是认证中心,由CAS提供,我们需要稍作修改,启动起来就可以用.~~~~ 效果演示 ht

  • 基于SpringBoot+Redis的Session共享与单点登录详解

    前言 使用Redis来实现Session共享,其实网上已经有很多例子了,这是确保在集群部署中最典型的redis使用场景.在SpringBoot项目中,其实可以一行运行代码都不用写,只需要简单添加添加依赖和一行注解就可以实现(当然配置信息还是需要的). 然后简单地把该项目部署到不同的tomcat下,比如不同的端口(A.B),但项目访问路径是相同的.此时在A中使用set方法,然后在B中使用get方法,就可以发现B中可以获取A中设置的内容. 但如果就把这样的一个项目在多个tomcat中的部署说实现了单

  • SpringBoot集成Redisson实现分布式锁的方法示例

    上篇 <SpringBoot 集成 redis 分布式锁优化>对死锁的问题进行了优化,今天介绍的是 redis 官方推荐使用的 Redisson ,Redisson 架设在 redis 基础上的 Java 驻内存数据网格(In-Memory Data Grid),基于NIO的 Netty 框架上,利用了 redis 键值数据库.功能非常强大,解决了很多分布式架构中的问题. Github的wiki地址: https://github.com/redisson/redisson/wiki 官方文档

  • 使用springboot结合vue实现sso单点登录

    本文实例为大家分享了springboot vue实现sso单点登录的具体代码,供大家参考,具体内容如下 项目结构: 开发工具:idea, maven3 静态文件下载地址 1.pom文件: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.or

  • SpringBoot+Vue+Redis实现单点登录(一处登录另一处退出登录)

    一.需求 实现用户在浏览器登录后,跳转到其他页面,当用户在其它地方又登录时,前面用户登录的页面退出登录(列如qq挤号那种方式) 二.实现思路 用户在前端填写用户信息登录后,后台接收数据先去数据库进行判断,如果登录成功,创建map集合,以用户id为键,token为值,先通过当前登录用户的id去获取token,如果token存在说明该用户已经登录过,调用redis以token为键删除上个用户的信息,调用方法生成新token,并将token存入map集合,将用户信息存入redis,并将token存入c

  • SpringBoot如何实现同域SSO(单点登录)

    单点登录,其实看起来不是很复杂,只是细节上的处理,单点区分有三种 同域SSO 同父域SSO 跨域的SSO 如何实现同域SSO? 个人理解:当用户登录访问demo1.lzmvlog.top时,同时具有访问demo2.lzmvlog.top的能力,即认证完成一次,可以访问所有系统. 实现方式:可以采用Cookie实现,即用户在访问一个系统时,携带认证颁发的信息,系统响应是否具有访问资格,否则跳转认证,也可以采用Session,即Session共享,校验访问用户是否具有有效的信息,提供访问资格 代码实

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

    1.单点登录三种常见的方式 (1)Session广播机制(Session复制) (2)使用Cookie+Redis实现 (3)使用token实现 2.单点登录介绍 举例: (1)引入jwt依赖 <!-- JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> (2)创建JWTUtil

  • vue+springboot前后端分离实现单点登录跨域问题解决方法

    最近在做一个后台管理系统,前端是用时下火热的vue.js,后台是基于springboot的.因为后台系统没有登录功能,但是公司要求统一登录,登录认证统一使用.net项目组的认证系统.那就意味着做单点登录咯,至于不知道什么是单点登录的同学,建议去找一下万能的度娘. 刚接到这个需求的时候,老夫心里便不屑的认为:区区登录何足挂齿,但是,开发的过程狠狠的打了我一巴掌(火辣辣的一巴掌)...,所以这次必须得好好记录一下这次教训,以免以后再踩这样的坑. 我面临的第一个问题是跨域,浏览器控制台直接报CORS,

随机推荐