SpringBoot SSO輕松實現(xiàn)(附demo)
前言
網(wǎng)上SSO的框架很多,此篇文章使用的是自寫的SSO來實現(xiàn)簡單的登錄授權(quán)功能,目的在于擴展性,權(quán)限這方面,自寫擴展性會好點。
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
一、技術(shù)介紹
1.SSO是什么?
單點登錄(SingleSignOn,SSO),就是通過用戶的一次性鑒別登錄。當(dāng)用戶在身份認(rèn)證服務(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到達(dá)過期時間自動過期 } /** * 登錄異常 * * @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-09Redis監(jiān)聽過期的key實現(xiàn)流程詳解
本文主要介紹了Redis監(jiān)聽key的過期時間,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02詳解java.lang.reflect.Modifier.isInterface()方法
這篇文章主要介紹了詳解java.lang.reflect.Modifier.isInterface()方法的相關(guān)資料,這里提供實例幫助大家理解這個方法的使用,需要的朋友可以參考下2017-09-09SpringBoot用配置影響B(tài)ean加載@ConditionalOnProperty
這篇文章主要為大家介紹了SpringBoot用配置影響B(tài)ean加載@ConditionalOnProperty示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04Java中檢查值是否存在于數(shù)組中的4種詳細(xì)方法
這篇文章主要給大家介紹了關(guān)于Java中檢查值是否存在于數(shù)組中的4種詳細(xì)方法,相信大家在操作Java的時候經(jīng)常會要檢查一個數(shù)組(無序)是否包含一個特定的值,需要的朋友可以參考下2023-08-08