SpringBoot SSO стал проще

Redis

Совет: если у вас есть какие-либо вопросы, пожалуйста, свяжитесь с личным сообщением, адрес исходного кода ниже, пожалуйста, получите его самостоятельно


предисловие

В Интернете существует множество фреймворков для 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;
    }

Суммировать

Это кажется простым? Для более подробного использования, пожалуйста, нажмите ниже, чтобы просмотреть исходный код, следуйте за мной, чтобы показать вам более расширенное использование

Адрес источника:Нажмите здесь, чтобы просмотреть исходный код.