Совет: если у вас есть какие-либо вопросы, пожалуйста, свяжитесь с личным сообщением, адрес исходного кода ниже, пожалуйста, получите его самостоятельно
предисловие
В Интернете существует множество фреймворков для SSO. В этой статье используется самописный SSO для реализации простой функции авторизации входа в систему. Цель состоит в расширении. С точки зрения разрешений самописная расширяемость будет лучше.
Совет: Ниже приведен основной текст этой статьи, следующие случаи приведены для справки.
1. Техническое введение
1. Что такое система единого входа?
Единый вход (SingleSignOn, SSO) заключается в входе в систему посредством одноразовой аутентификации пользователя. После того, как пользователь один раз войдет в систему на сервере аутентификации личности, он может получить разрешение на доступ к другим связанным системам и прикладному программному обеспечению в системе единого входа.В то же время эта реализация не требует от администратора изменения логина пользователя. статус или другую информацию.Это означает, что в системах с несколькими приложениями пользователям достаточно войти в систему только один раз, чтобы получить доступ ко всем взаимно доверенным системам приложений. Этот метод сокращает время, затрачиваемое на вход в систему, помогает управлять пользователями и в настоящее время более популярен.2. Используйте шаги
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;
}
}
Объект токена пользователя:
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;
}
Суммировать
Это кажется простым? Для более подробного использования, пожалуйста, нажмите ниже, чтобы просмотреть исходный код, следуйте за мной, чтобы показать вам более расширенное использование
Адрес источника:Нажмите здесь, чтобы просмотреть исходный код.