SpringBoot SSO轻松实现(附demo)

作者:何裕华 时间:2022-04-05 02:24:33 

前言

网上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;
 }

总结

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

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

来源:https://juejin.cn/post/6922620512107823112

标签:SpringBoot,SSO
0
投稿

猜你喜欢

  • Java实现读取文章中重复出现的中文字符串

    2022-04-27 04:29:05
  • OpenHarmony实现类Android短信验证码及倒计时流程详解

    2021-11-14 10:55:26
  • Android布局之绝对布局AbsoluteLayout详解

    2023-07-26 07:44:27
  • JAVA通过HttpClient发送HTTP请求的方法示例

    2023-08-24 18:45:47
  • c# 单例模式的实现

    2023-06-12 01:16:44
  • Android Handler 机制实现原理分析

    2022-01-14 23:46:49
  • 一文搞懂Android RecyclerView点击展开、折叠效果的实现代码

    2023-02-17 14:52:24
  • Java自定义异常类的实例详解

    2021-10-21 07:18:33
  • c#入门之类型转换详解

    2022-03-17 01:36:52
  • Android图片处理实例分析

    2022-09-10 20:34:50
  • Java 中 String,StringBuffer 和 StringBuilder 的区别及用法

    2023-08-22 16:40:57
  • C#实现GZip压缩和解压缩入门实例

    2021-05-29 08:20:45
  • C#游戏开发之实现华容道游戏

    2023-01-03 03:17:20
  • Java枚举类用法实例

    2023-09-25 01:47:34
  • Hibernate hql查询代码实例

    2021-07-24 18:03:33
  • C#创建压缩文件的实现代码

    2022-07-28 23:55:04
  • Android自动播放Banner图片轮播效果

    2022-09-06 05:55:42
  • 浅谈Maven镜像更换为阿里云中央仓库(精)

    2022-08-06 04:48:17
  • Java如何利用递归计算出阶乘

    2023-02-08 12:43:03
  • 基于JWT的spring boot权限验证技术实现教程

    2022-07-20 01:48:58
  • asp之家 软件编程 m.aspxhome.com