SpringBoot SSO輕松實現(xiàn)(附demo)
前言
網(wǎng)上SSO的框架很多,此篇文章使用的是自寫的SSO來實現(xiàn)簡單的登錄授權(quán)功能,目的在于擴展性,權(quán)限這方面,自寫擴展性會好點。
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
一、技術(shù)介紹
1.SSO是什么?
單點登錄(SingleSignOn,SSO),就是通過用戶的一次性鑒別登錄。當用戶在身份認證服務(wù)器上登錄一次以后,即可獲得訪問單點登錄系統(tǒng)中其他關(guān)聯(lián)系統(tǒng)和應(yīng)用軟件的權(quán)限,同時這種實現(xiàn)是不需要管理員對用戶的登錄狀態(tài)或其他信息進行修改的,這意味著在多個應(yīng)用系統(tǒng)中,用戶只需一次登錄就可以訪問所有相互信任的應(yīng)用系統(tǒng)。這種方式減少了由登錄產(chǎn)生的時間消耗,輔助了用戶管理,是目前比較流行的。
二、使用步驟
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 用戶驗證回調(diào)URL
* @return
*/
LoginResult login(String account, String password, String callbackUrl);
}
登錄狀態(tài)枚舉:
package com.hyh.sso;
/**
* 登錄狀態(tài)枚舉
*
* @Author: heyuhua
* @Date: 2021/1/8 16:59
*/
public enum LoginStatus {
SUCCESS(1, "登錄成功"), ING(0, "登錄中"), FAIL(-1, "登錄失敗"),
ERROR(-2, "登錄異常"), CALLBACK_ERROR(-3, "登錄回調(diào)異常"), ACCOUNT_LOCK(-4, "賬戶被鎖定"),
EXPIRE(-5,"登錄用戶已過期");
/**
* 登錄狀態(tài)碼
*/
private int code;
/**
* 登錄狀態(tài)消息
*/
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;
}
登錄常規(guī)接口:
package com.hyh.sso;
package com.hyh.sso.service;
import com.hyh.sso.ILogin;
/**
* 常規(guī)登錄接口
*
* @Author: heyuhua
* @Date: 2021/1/8 17:54
*/
public interface LoginService extends ILogin {
}
登錄接口實現(xiàn):
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;
/**
* 登錄接口實現(xiàn)
*
* @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();
//設(shè)置請求媒體數(shù)據(jù)類型
headers.setContentType(MediaType.APPLICATION_JSON);
//設(shè)置返回媒體數(shù)據(jù)類型
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));
}
}
登錄結(jié)果對象:
package com.hyh.sso.po;
import com.hyh.sso.LoginStatus;
import com.hyh.sso.LoginTypes;
/**
* 登錄結(jié)果對象
* @Author: heyuhua
* @Date: 2021/1/8 16:58
*/
public class LoginResult {
/**
* 登錄用戶對象
*/
private LoginUser loginUser;
/**
* 登錄用戶令牌
*/
private UserToken userToken;
/**
* 登錄狀態(tài)
*/
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:";
/**
* 登錄失敗統(tǒng)計 KEY
*/
private final String LOGIN_FAIL_COUNT_KEY = "login:fail:count";
/**
* 登錄失敗最多允許次數(shù)
*/
private final long MAX_FAIL_COUNT = 5;
/**
* 登錄服務(wù)
*/
@Resource
private LoginService loginService;
/**
* redis助手
*/
@Autowired
private RedisHelper redisHelper;
/**
* 登錄
*
* @param account 用戶名
* @param password 密碼
* @param callbackUrl 回調(diào)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;
}
總結(jié)
是不是感覺很簡單?更多用法請點擊下方查看源碼,關(guān)注我?guī)憬颐馗喔呒売梅?/p>
源碼地址:點此查看源碼.
到此這篇關(guān)于SpringBoot SSO輕松實現(xiàn)(附demo)的文章就介紹到這了,更多相關(guān)SpringBoot SSO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot集成CAS實現(xiàn)單點登錄的示例代碼
- SpringBoot整合SSO(single sign on)單點登錄
- SpringBoot+Vue+Redis實現(xiàn)單點登錄(一處登錄另一處退出登錄)
- 基于SpringBoot+Redis的Session共享與單點登錄詳解
- 使用springboot結(jié)合vue實現(xiàn)sso單點登錄
- 基于springboot和redis實現(xiàn)單點登錄
- vue+springboot前后端分離實現(xiàn)單點登錄跨域問題解決方法
- SpringBoot集成Redisson實現(xiàn)延遲隊列的場景分析
- 基于SpringBoot解決CORS跨域的問題(@CrossOrigin)
- SpringBoot使用Redisson實現(xiàn)分布式鎖(秒殺系統(tǒng))
- SpringBoot集成Redisson實現(xiàn)分布式鎖的方法示例
- SpringBoot如何實現(xiàn)同域SSO(單點登錄)
相關(guān)文章
Spring boot jpa 刪除數(shù)據(jù)和事務(wù)管理的問題實例詳解
這篇文章主要介紹了Spring boot jpa 刪除數(shù)據(jù)和事務(wù)管理的問題實例詳解,涉及業(yè)務(wù)場景的一些知識和遇到的的問題,需要的朋友可以參考。2017-09-09
Redis監(jiān)聽過期的key實現(xiàn)流程詳解
本文主要介紹了Redis監(jiān)聽key的過期時間,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2023-02-02
詳解java.lang.reflect.Modifier.isInterface()方法
這篇文章主要介紹了詳解java.lang.reflect.Modifier.isInterface()方法的相關(guān)資料,這里提供實例幫助大家理解這個方法的使用,需要的朋友可以參考下2017-09-09
SpringBoot用配置影響B(tài)ean加載@ConditionalOnProperty
這篇文章主要為大家介紹了SpringBoot用配置影響B(tài)ean加載@ConditionalOnProperty示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04

