Shiro+Redis 로그 인 횟수 동결 예시
만약 에 우리 가 이런 장면 이 필요 하 다 고 가정 합 니 다.만약 에 사용자 가 비밀 번 호 를 5 번 연속 으로 잘못 지면 누군가가 일 을 하고 있다 는 것 을 설명 할 수 있 기 때문에 이 계 정의 로그 인 기능 을 잠시 동결 해 야 합 니 다.
Shiro 통합 JWT 에 대해 서 는 여 기 를 볼 수 있 습 니 다:Springboot Shiro+JWT 인증 실현
만약 에 우리 프로젝트 에 shiro 를 사용 했다 고 가정 하면 Shiro 는 완벽 한 인터페이스 구동 디자인 과 대상 지향 원칙 위 에 세 워 진 것 이기 때문에 각종 사용자 정의 행 위 를 지원 하기 때문에 우 리 는 Shiro 프레임 워 크 의 인증 모듈 과 redis 를 결합 하여 이 기능 을 실현 할 수 있 습 니 다.
사고의 방향
우리 의 대체적인 사고방식 은 다음 과 같다.
Shiro 를 사용 해 야 하 는 것 외 에 우 리 는 Redis 를 사용 해 야 합 니 다.여 기 는 RedisTemplate 를 먼저 설정 해 야 합 니 다.(이것 이 중요 하지 않 기 때문에 코드 와 설정 방법 을 글 의 마지막 에 붙 여야 합 니 다)또한 Controller 층 에서 로그 인 인터페이스의 이상 처 리 는 이전의 로그 인 오 류 를 제외 하고 계 정 동결 류 의 이상 도 추가 해 야 합 니 다.코드 는 다음 과 같 습 니 다.
@PostMapping(value = "/login")
public AccountVO login(String userName, String password){
//
Subject subject = SecurityUtils.getSubject();
try {
// shiro
subject.login(new UsernamePasswordToken(userName, password));
} catch (ExcessiveAttemptsException e1) {
//
throw new AccountLockedException();
} catch (Exception e) {
//
throw new LoginFailed();
}
//
AccountVO account = accountService.getAccountByUserName(userName);
//
return account;
}
사용자 정의 Shiro 인증 관리자HashedCredentialsMatcher
위의 Controller 층 에서 subject.login 방법 을 호출 하면 사용자 정의 Realm 에 들 어가 서 Shiro 현재 Security Manager 에서 정의 하 는 HashedCredentialsMatcher 인증 관리자 의 doCredentialsMatch 방법 에 천천히 들 어가 비밀 번 호 를 매 칭 합 니 다.원본 코드 는 다음 과 같 습 니 다.
/**
* This implementation first hashes the {@code token}'s credentials, potentially using a
* {@code salt} if the {@code info} argument is a
* {@link org.apache.shiro.authc.SaltedAuthenticationInfo SaltedAuthenticationInfo}. It then compares the hash
* against the {@code AuthenticationInfo}'s
* {@link #getCredentials(org.apache.shiro.authc.AuthenticationInfo) already-hashed credentials}. This method
* returns {@code true} if those two values are {@link #equals(Object, Object) equal}, {@code false} otherwise.
*
* @param token the {@code AuthenticationToken} submitted during the authentication attempt.
* @param info the {@code AuthenticationInfo} stored in the system matching the token principal
* @return {@code true} if the provided token credentials hash match to the stored account credentials hash,
* {@code false} otherwise
* @since 1.1
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = hashProvidedCredentials(token, info);
Object accountCredentials = getCredentials(info);
return equals(tokenHashedCredentials, accountCredentials);
}
원본 의 논 리 는 매우 간단 하 다 는 것 을 알 수 있다.두 가지 일 을 해서 비밀 번 호 를 얻 었 다.비밀 번 호 를 비교 해 보 자.Redis 를 연결 해 야 하기 때문에 로그 인 하기 전에 동결 검 사 를 한 번 하고 로그 인 에 실패 할 때마다 redis 에 대한 쓰기 작업 을 해 야 하기 때문에 지금 은 인증 관리 자 를 다시 써 서 Security Manager 에 설정 해 야 합 니 다.
CustomMatcher
저 희 는 customer Matcher 를 사용자 정의 합 니 다.이 종 류 는 HashedCredentials Matcher 를 계승 하고 doCredentials Match 방법 만 다시 썼 습 니 다.이 안에 저희 만 의 논 리 를 추 가 했 습 니 다.코드 는 다음 과 같 습 니 다.
import com.imlehr.internship.redis.RedisStringService;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Lehr
* @create: 2020-02-25
*/
public class CustomMatcher extends HashedCredentialsMatcher {
// redis key
private static final String PREFIX = "USER_LOGIN_FAIL:";
@Autowired
RedisStringService redisUtils;
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//
//
UsernamePasswordToken myToken = (UsernamePasswordToken) token;
String userName = myToken.getUsername();
//
Integer errorNum = 0;
//
String errorTimes = (String)redisUtils.get(PREFIX+userName);
if(errorTimes!=null && errorTimes.trim().length()>0)
{
//
errorNum = Integer.parseInt(errorTimes);
}
//
if (errorNum >= 10) {
//
throw new ExcessiveAttemptsException();
}
//
boolean matched = super.doCredentialsMatch(token, info);
if(matched)
{
//
redisUtils.remove(PREFIX+userName);
}
else{
//
redisUtils.set(PREFIX+userName,String.valueOf(++errorNum),60*30L);
}
return matched;
}
}
우선,저 희 는 AuthenticationToken 에서 이전에 저 장 된 사용자 의 로그 인 정 보 를 받 았 습 니 다.이 대상 은 사실 Controller 층 에 있 습 니 다.
subject.login(new UsernamePasswordToken(userName, password));
이 단계 에서 너의 실례 화 된 대상그리고 사용자 의 로그 인 이름 에 고정 접 두 사 를 붙 여(userName 과 다른 홈 키 의 충돌 을 방지 하기 위해)Redis 에서 오류 횟수 를 가 져 옵 니 다.계 정 이 동결 되 었 는 지 여 부 를 판단 하 는 논 리 는 현재 사용자 의 오류 로그 인 횟수 가 특정한 규정 치 를 초과 하 는 지 여 부 를 보 는 것 입 니 다.여기 서 저 희 는 5 번 으로 정 합 니 다.
다음은 사용자 가 동결 되 지 않 았 고 로그 인 작업 을 수행 할 수 있 음 을 설명 합 니 다.그래서 우 리 는 부모 류 의 검증 방법 을 직접 호출 하여 암호 비교(전에 언급 한 세 줄 코드)를 하여 암호 비교 결 과 를 얻 었 습 니 다.
일치 하면 로그 인 에 성공 하고 트 루 로 돌아 가면 됩 니 다.로그 인 에 성공 하면 모든 오류 횟수 기록 을 없 앨 수 있 습 니 다.위의 코드 는 이렇게 합 니 다.
비교 결과 가 다 르 면 오류 기록 을 한 번 더 추가 하고 false 로 돌아 갑 니 다.
테스트
첫 로그 인:페이지 결과:
Redis 중:
그리고 10 회 연속 오류:
페이지 결과:
Redis 중:
그리고 30 분 을 기 다 렸 어 요.
오류 비밀번호 로그 인 다시 시도:
다시 한 번 오 류 를 보 고 했 습 니 다.이때 Redis 에 서 는 이전의 기록 이 만 료 되 어 자동 으로 소각 되 었 기 때문에 다시 오 류 를 일 으 키 면 또 한 번 오류 기록 을 추가 합 니 다.
현재 올 바른 로그 인 을 시도 합 니 다:
성공 로그 인
Redis 보기:
🎉Done!
RedisTemplate 코드 첨부
설정 클래스
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
{
//
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer ser = new JdkSerializationRedisSerializer();
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(ser);
return template;
}
@Bean
public RedisStringService myStringRedisTemplate()
{
return new RedisStringService();
}
}
도구 클래스 RedisStringServiceValue 를 처리 할 수 있 는 도구 클래스 는 String 입 니 다.바로 제 가 customer Matcher 에서 Autowired 를 사용 하 는 클래스 입 니 다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
public class RedisStringService {
@Autowired
protected StringRedisTemplate redisTemplate;
/**
* redis ( expire )
* @param key
* @param value
* @return
*/
public boolean set(final String key, String value){
boolean result = false;
try {
ValueOperations operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.getMessage();
}
return result;
}
/**
* redis ( expire )
* @param key
* @param value
* @param expire
* @return
*/
public boolean set(final String key, String value, Long expire){
boolean result = false;
try {
ValueOperations operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.getMessage();
}
return result;
}
/**
* redis
* @param key
* @return
*/
public Object get(final String key){
Object result = null;
try {
ValueOperations operations = redisTemplate.opsForValue();
result = operations.get(key);
} catch (Exception e) {
e.getMessage();
}
return result;
}
/**
* redis key
* @param key
* @return
*/
public boolean exists(final String key){
boolean result = false;
try {
result = redisTemplate.hasKey(key);
} catch (Exception e) {
e.getMessage();
}
return result;
}
/**
* redis key value
* @param key
* @return
*/
public boolean remove(final String key){
boolean result = false;
try {
if(exists(key)){
redisTemplate.delete(key);
}
result = true;
} catch (Exception e) {
e.getMessage();
}
return result;
}
/**
* redis keys value
* @param keys
* @return
*/
public void remove(final String... keys){
for(String key : keys){
remove(key);
}
}
}
여기에 Shiro+Redis 로그 인 횟수 동결 에 관 한 글 이 소개 되 었 습 니 다.Shiro+Redis 로그 인 동결 에 관 한 더 많은 내용 은 이전 글 을 검색 하거나 아래 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
[Shiro] Shiro 라벨 사용guest , , 。--> <shiro:guest> shiro:guest> <shiro:user> shiro:user> <--! authenticated , , Subject.login , 。...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.