1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/justsosoG-easy-shiro

Клонировать/Скачать
README.md 22 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 27.11.2024 21:42 12e0448

Проект

Данный проект помогает разработчикам быстро, просто и без настройки интегрировать Shiro-фреймворк.

Демо: https://github.com/justsosoG/idong/tree/master

Документация проекта:

https://apidoc.gitee.com/justsosoG/easy-shiro/

Особенности:

  1. Пользовательский интерфейс для входа в систему.
  2. Пользовательские параметры для отправки при входе в систему.
  3. Интерфейс без аутентификации (доступный без входа).
  4. Включение или отключение проверки подлинности (см. раздел «Использование»).
  5. Пять форматов проверки подлинности: PNG, GIF, арифметическая задача, случайный шрифт (поддержка пользовательских шрифтов не предусмотрена), поддержка только китайского языка не предусмотрена.
  6. Возможность выбора между постоянной проверкой подлинности при каждом входе в систему и автоматической активацией проверки после определённого количества неудачных попыток входа.
  7. После успешного входа используется система выдачи токенов. При каждом вызове защищённого интерфейса необходимо передать токен в заголовке запроса (по умолчанию — token). Поле заголовка с токеном может быть настроено пользователем (см. раздел «Использование»).
  8. Обратные вызовы для событий входа (успех, неудача, выход из системы).
  9. Ограничение одновременных входов для одного аккаунта (на основе Redis или памяти).
  10. Автоматическое обновление токенов в течение одной минуты после истечения срока их действия.
  11. Контроль QPS для дополнительной защиты интерфейсов (см. раздел «Использование»).
  12. Проверка ролей и разрешений для интерфейсов (см. раздел «Использование»).
  13. Начиная с версии 1.0.2, клиенты, которые не отключают cookie, могут не добавлять определённые заголовки при вызовах интерфейса, так как теперь приоритет отдаётся получению токенов из cookie, которые также автоматически записываются сервером.

Быстрый старт

Maven-импорт

Рекомендуется использовать последнюю версию вместо загрузки исходного кода с GitHub для самостоятельной компиляции.

Пример использования в Spring Boot:

<dependency>
  <groupId>org.njgzr</groupId>
  <artifactId>easy-shiro</artifactId>
  <version>${latest.version}</version>
</dependency>

Актуальную версию можно найти здесь: https://mvnrepository.com/artifact/org.njgzr/easy-shiro

Использование

Шаг 1

Создайте класс конфигурации и добавьте аннотацию @EnableEasyShiro:

@EnableEasyShiro
@Configuration
public class LoginConfig {
    
}

Для проектов на Spring Boot достаточно добавить аннотацию к стартовому классу:

@SpringBootApplication
@EnableEasyShro
public class Demo2Application {

    public static void main(String[] args) {
        SpringApplication.run(Demo2Application.class, args);
    }

}

Шаг 2

Реализуйте четыре интерфейса: ConfigGetService, LoginResultService, SecurityService и AuthorizedUser.

  • ConfigGetService — для внедрения конфигурации.
  • LoginResultService — для обработки результатов входа. Используется для ведения журнала входа и других целей.
  • SecurityService — для проверки правильности учётных данных.
  • AuthorizedUser — после успешного входа можно получить информацию о текущем пользователе через метод SecurityUtil.getCurrentUser().

Обратите внимание: если используется база данных, то пароли должны быть зашифрованы с использованием метода new Password(строка для шифрования).toString(). В этом проекте используется безопасный способ шифрования, который обеспечивает уникальность зашифрованных паролей.

Пример реализации:

@Service
public class ServiceImpl implements ConfigGetService,LoginResultService,SecurityService{
    
    /**
     * principal — это логин пользователя.
     */
    @Override
    public AuthorizedUser findByPrincipal(Object principal) {
        // В процессе разработки следует получать данные из базы данных, используя параметр principal в качестве логина.
        if (principal instanceof String) {
            
            User user = userService.findByLoginName((String) principal);// Локальный аккаунт
            if (user == null)
                return null;

            AuthorizedUserInfo info = new AuthorizedUserInfo();// Здесь AuthorizedUserInfo реализует интерфейс AuthorizedUser
            Long orgid = user.getOrganizationId();
            info.setUploadUrl(user.getUploadUrl());
            info.setDisplayName(user.getDisplayName());
            info.setId(user.getId());
            info.setLoginName(user.getLoginName());
            info.setMobile(user.getMobile());
            info.setType(user.getType());
            info.setOrganizationId(orgid);
            info.setDefaultCauseId(user.getDefaultCauseId());
            info.setAddress(user.getAddress());
            info.setPermissions(permissions);
            info.setRoles(roles);
            log.info(info.toString());
            return info;
            // Здесь info будет возвращена на фронтэнд.
            // Также можно использовать AuthorizedUserInfo info  = (AuthorizedUserInfo) SecurityUtil.getCurrentUser(); для получения информации.
        }
        return null;
    }

    @Override
    public Password findPassword(AuthorizedUser user) {
        // Во время разработки следует извлекать данные из базы данных, используя идентификатор пользователя для поиска соответствующей записи в базе данных и возврата поля пароля. Обратите внимание, что здесь должен быть возвращён тип Password.
        User user = userService.findById(authorizedUser.getId());
        return user == null ? null : user.getPassword();
    }
    
    /**
     * Обратный вызов после успешного входа.
     */
    @Override
    public void loginSuccess(Long userId, String loginName, String terminal, String addr, String ip,String os,String browser) {
        System.err.println(loginName+" успешно вошёл в систему");
    }
    
    /**
     * Установка максимального количества одновременных входов для одного аккаунта.
     */
    @Override
    public Long maxSessionCount() {
        return 2L;
    }
}
``` **Текст запроса:**

* 免认证的接口
    */
    @Override
    public List<String> anons() {
        List<String> dList = Lists.newArrayList();
        return dList;
    }
    
    /**
     * 返回null则基于内存模式
     */
    @Override
    public StringRedisTemplate getStringRedisTemplate() {
        return stringRedisTemplate;
    }
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    /**
     * 系统标识符
     */
    @Override
    public String getAppId() {
        return "Idong";
    }
    
    /**
     * web应用的令牌有效时间,返回null则默认为30分钟
     */
    @Override
    public Long webExpireTime() {
        return 30 * 60 * 1000L;
    }
    
    /**
     * 手机应用的令牌有效时间,返回null则默认为1个月
     */
    @Override
    public Long appExpireTime() {
        return 30 * 60 * 1000L;
    }
    
    /**
     * pc应用的令牌有效时间,返回null则默认为1天
     */
    @Override
    public Long pcExpireTime() {
        return 30 * 60 * 1000L;
    }
    
    /**
     * 登录请求中用户名的字段,返回null则默认为username
     */
    @Override
    public String loginUserNameParam() {
        return null;
    }
    
    /**
     * 登录请求中密码的字段,返回null则默认为password
     */
    @Override
    public String loginPasswordParam() {
        return null;
    }
    
    /**
     * 请求头中的令牌字段,返回null则默认为 token
     */
    @Override
    public String headerToken() {
        return "myToken";
    }
    
    /**
     * 登录接口的地址,返回null则默认是 /login
     */
    @Override
    public String loginUrl() {
        return null;
    }
    
    /**
     * 登出回调
     */
    @Override
    public void logout(Long userId, String loginName) {
        // TODO Auto-generated method stub
        
    }
    
    /**
     * 登录失败的回调
     */
    @Override
    public void loginFail(String loginName, Exception e) {
        // TODO Auto-generated method stub
        
    }
    
    /**
     * 登录请求中验证码的字段,返回null则默认为captchaCode
     */
    @Override
    public String captchaParam() {
        return null;
    }
    
    /**
     * 最大的错误次数,如果超过该数字或者返回0,系统将校验验证码
     */
    @Override
    public int maxClfCount() {
        return 2;
    }
    
    /**
     * 验证码的长度,最长6位,最短4位,如为算术验证码,则默认是两个数字的算术题
     */
    @Override
    public int captchaLenth() {
        return 4;
    }
    
    /**
     * 验证码图片的大小,格式为 宽,高 ,逗号为英文状态的逗号,返回null则默认 110,40
     */
    @Override
    public String captchaSize() {
        return "120,60";
    }
    
    /**
     * 是否开启验证码
     */
    @Override
    public boolean enableCaptcha() {
        return true;
    }
    
    /**
     * 验证码类型,目前可选的为 specCaptcha,gifCaptcha,chineseCaptcha,chineseGifCaptcha,arithmeticCaptcha,
     * 如果返回null,则默认随机,中文验证码存在问题,暂时不推荐使用
     * 可选值参考org.njgzr.security.enums.CaptchaType
     * 若返回随意字符串,则默认specCaptcha
     */
    @Override
    public String captchaType() {
        return null;
    }

**Перевод текста на русский язык:**

* Интерфейс без аутентификации
    */
    @Override
    public List<String> анонимный() {
        Список dList = новый ArrayList();
        вернуть dList;
    }
    
    /**
    * Возвращает null, тогда используется режим памяти
    */
    @Override
    общедоступный StringRedisTemplate getStringRedisTemplate() {
        возврат stringRedisTemplate;
    }
    
    @Autowired
    частный StringRedisTemplate stringRedisTemplate;
    
    /**
    * Идентификатор системы
    */
    @Override
    публичный String getId() {
        возвращение «Идонг»;
    }
    
    /**
    * Время действия токена для веб-приложения, возвращает null по умолчанию 30 минут
    */
    @Override
    общедоступное длинное время истечения срока действия веб-интерфейса() {
        возвращает 30 * 60 * 1000L;
    }
    
    /**
    * Срок действия токена мобильного приложения, возвращает null по умолчанию один месяц
    */
    @Override
    общедоступное длительное время истечения срока действия приложения() {
        возвращает 30 * 60 * 1000L;
    }
    
    /**
    * Срок действия токена настольного приложения, возвращает null по умолчанию один день
    */
    @Override
    общедоступное долгое время истечения срока действия ПК() {
        возвращает 30 * 60 * 1000L;
    }
    
    /**
    * Поле имени пользователя в запросе на вход, возвращает null по умолчанию username
    */
    @Override
    общедоступная строка loginUserNameParam() {
        возвратить null;
    }
    
    /**
    * Поле пароля в запросе на вход, возвращает null по умолчанию password
    */
    @Override
    общедоступная строка loginPasswordParam() {
        возвратить null;
    }
    
    /**
    * Поле токена в заголовке запроса, возвращает null по умолчанию token
    */
    @Override
    общедоступная строка headerToken() {
        возвращается «myToken»;
    }
    
    /**
    * Адрес интерфейса входа, возвращает null по умолчанию /login
    */
    @Override
    общедоступная строка loginUrl() {
        возвратить null;
    }
    
    /**
    * Выход из системы
    */
    @Override
    общедоступный выход (длинный идентификатор пользователя, строка loginName) {
        // TODO Автоматически сгенерированный метод-заглушка
        
    }
    
    /**
    * Обратный вызов при неудачном входе
    */
    @Override
    общедоступный логин не удался (строка loginName, исключение e) {
        // TODO Автоматически сгенерированный метод-заглушка
        
    }
    
    /**
    * Поле кода проверки в запросе входа, возвращает null по умолчанию captchaCode
    */
    @Override
    общедоступная строка captchaParam() {
        возвратить null;
    }
    
    /**
    * Максимальное количество ошибок, если оно превышает это число или возвращается 0, система проверит код подтверждения
    */
    @Override
    общедоступные int maxClfCount() {
        возвращает 2;
    }
    
    /**
    * Длина кода подтверждения, максимум 6 цифр, минимум 4 цифры, если это арифметический код подтверждения, то по умолчанию это арифметическая задача с двумя цифрами
    */
    @Override
    общедоступный int captchaLength() {
        возвращает 4;
    }
    
    /**
    * Размер изображения кода подтверждения, формат 宽, высота, запятая — английская запятая, возвращает null по умолчанию 110, 40
    */
    @Override
    общедоступная строка captchaSize() {
        возвращается "120, 60";
    }
    
    /**
    * Включить ли код подтверждения
    */
    @Override
    общедоступная логическая поддержка капчи() {
        возвращает истину;
    }
    
    /**
    * Тип кода подтверждения, в настоящее время доступны варианты specCaptcha, gifCaptcha, chineseCaptcha, chineseGifCaptcha, arithmeticCaptcha, если возвращается null, то по умолчанию случайный, китайский код подтверждения имеет проблемы, временно не рекомендуется использовать
    * Доступные значения см. в org.njgzr.security.enums.CaptchaType.
    * Если возвращается произвольная строка, то по умолчанию specCaptcha
    */
    @Override
    общедоступная строка типа капчи() {
        возвращать null;
    }}

В тексте запроса нет кода на каком-либо языке программирования, гиперссылок, специальных тегов форматирования в markdown, html, yaml, json, plantuml и других. **Срок действия токена и льготный период**

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

В течение этой минуты при возврате запроса в заголовках ответа будет возвращаться новый токен, который также будет записан в cookie. В процессе реальной разработки, как только вы обнаружите, что в response headers есть возвращаемое значение токена, просто замените старый токен на новый.

Также вы можете создать таймер, который будет периодически обращаться к определённому интерфейсу, чтобы гарантировать актуальность вашего токена.

**Адрес верификационного кода:** http://ip:port/captcha

Когда требуется верификационный код?
```json
{
    "code": 501,
    "data": {
        "captchaEnabled": true
    },
    "desc": "Ошибка верификации",
    "success": false,
    "time": 1576639456428
}

Этот ответ означает, что требуется верификация.

Шаг 5

Как ограничить поток интерфейса?

Добавьте аннотацию @LxRateLimit(perSecond = 10.0) к нужному интерфейсу.

perSecond указывает количество вызовов этого интерфейса в секунду.

Обратите внимание:

Все интерфейсы возвращают тип Result.

@LxRateLimit(perSecond = 10.0)
@RequestMapping(value="/getStr",method={RequestMethod.POST})
public Result getString() {
    return Result.success("hello-"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
}

Шаг 6

Как провести проверку роли или прав доступа для интерфейса?

Сначала определите Set permissions и Set roles в классе AuthorizedUser.

Затем присвойте значения этим полям в методе findByPrincipal класса SecurityService.

Наконец, добавьте следующие аннотации к интерфейсу, требующему аутентификации:

@LxRateLimit(perSecond = 10.0)
@RequestMapping(value="/getStr",method={RequestMethod.POST})
@RequiresRoles(value = { "role1","role2" })
@RequiresPermissions(value = { "per1","per2" })
public Result getString() {
    return Result.success("hello-"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
}

Если у вас возникнут проблемы при использовании, вы можете связаться со мной через qq980061784 (отметьте github). Если вы обнаружите ошибку, пожалуйста, свяжитесь со мной как можно скорее. Я впервые пишу этот материал, структура кода немного хаотична, и могут быть ошибки. Я планирую переработать его позже. Если у вас есть какие-либо предложения, не стесняйтесь их высказывать. Моя цель — создать простой, быстрый и гибкий shiro. Надеюсь, это поможет вам.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/justsosoG-easy-shiro.git
git@api.gitlife.ru:oschina-mirror/justsosoG-easy-shiro.git
oschina-mirror
justsosoG-easy-shiro
justsosoG-easy-shiro
master