পলিমরফিক ইন্টারফেরেন্স -> 1

‘চোখ খোল মেট্রাউস’প্রায় ধমকের সুরে বলল মেগাপল৫।
‘এরকম আনন্দের সংবাদ শুনে কেউ আতংকে অজ্ঞান হয়ে যায়? কি আশ্চর্য!! আসলে আমাদের ভেতর মানবিক অনুভূতি দিয়ে যাওয়াটা উচিত হয়নি। মানুষগুলো আসলেই অদূরদর্শী ছিল।‘আপসোসের মত শোনালো মেগাপলের যান্ত্রিক কন্ঠ।
মেট্রাউসের তখনো জ্ঞান ফেরেনি। আবেগে তার কপোট্রনের বা দিকের অংশটা ফেইল করেছে। তাই রিস্টোর করে পুনরায় আবেগ লোড করতে একটু সময় লাগছে।
সিস্টেম রিস্টোর করার এই এক ঝামেলা। সুস্থ্য হুওয়ার পরেও আরো কিছুক্ষন মাথা ঝিমঝিম করবে,বমি বমি ভাব চলে আসে। অবশ্য আজ অব্দি কোন এন্ড্রয়েড সত্যিকার অর্থে বমি করেছে বলে শোনা যায়নি। তবে এই ফালতু একটা অনুভূতি কেন তাদের মধ্যে দিয়ে গেল মানুষ ভেবে মনে মনে মানুষকে কতক্ষন গালাগাল দিল মেট্রাউস। আসলে সবদিক থেকে নিজেদের মত তৈরী করতে গিয়ে খানিকটা দূর্বল করে ফেলা হয়েছে তাদেরকে। তারপরেও নিজেদের পেশীশক্তি ব্যাবহার করে মানুষকে পৃথীবী থেকে তাড়ানো গেছে এই বা কম কি? শোনা যায় এন্ড্রমিডা গ্যালাক্সির কোন এক গ্রহে আবাস গড়েছে মানুষ। ভালমত সংগঠিত হতে পারলে আবার পৃথীবীতে এসে জ্বালাতন শুরু করবে ভ…

Flood Control (Limit Login Attempts) in Spring Security

May be sometimes you wanna limit your users login attempt for better security, to protect them from buitforce attack, so that their account isn't compromised. I'm gonna show you how you can limit login attempts and control flooding.

1. Dependency


We'll use Googles Guava for caching, so please add this dependency in your pom.xml file.
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>

2. Login Attempt Service


To control login attemps determine if it's blocked we'll create a service called LoginAttemptService.
@Service
public class LoginAttemptService {
private final int MAX_ATTEMPT = 10;
private LoadingCache<String, Integer> attemptsCache;

public LoginAttemptService() {
super();
attemptsCache = CacheBuilder.newBuilder().
expireAfterWrite(1, TimeUnit.DAYS).build(new CacheLoader<String, Integer>() {
public Integer load(String key) {
return 0;
}
});
}

public void loginSucceeded(String key) {
attemptsCache.invalidate(key);
}

public void loginFailed(String key) {
int attempts = 0;
try {
attempts = attemptsCache.get(key);
} catch (ExecutionException e) {
attempts = 0;
}
attempts++;
attemptsCache.put(key, attempts);
}

public boolean isBlocked(String key) {
try {
return attemptsCache.get(key) >= MAX_ATTEMPT;
} catch (ExecutionException e) {
return false;
}
}
}

3. Authentication Listeners


Now we'll need two listeners AuthenticationSuccessEventListener and AuthenticationFailureListener to listen applications login attempt and know if user login attempt is successful or not.

AuthenticationSuccessEventListener.java
@Component
public class AuthenticationSuccessEventListener
implements ApplicationListener<AuthenticationSuccessEvent> {

private final LoginAttemptService loginAttemptService;

@Autowired
public AuthenticationSuccessEventListener(LoginAttemptService loginAttemptService) {
this.loginAttemptService = loginAttemptService;
}

public void onApplicationEvent(AuthenticationSuccessEvent e) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
loginAttemptService.loginSucceeded(request.getRemoteAddr());
}
}

AuthenticationFailureEventListener.java
@Component
public class AuthenticationFailureEventListener
implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {

private final LoginAttemptService loginAttemptService;

@Autowired
public AuthenticationFailureEventListener(LoginAttemptService loginAttemptService) {
this.loginAttemptService = loginAttemptService;
}

public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
loginAttemptService.loginFailed(request.getRemoteAddr());
}
}

These events will be fired when user will be trying to log in to the application. But this won't work yet. You need to let spring know that you've created those listeners and use them. So configure beans of those classes in your class annotated with @Configuration annotation.
@Configuration
public class AppConfig extends WebMvcConfigurerAdapter{

@Bean
public ApplicationListener loginSuccessListener(){
return new AuthenticationSuccessEventListener();
}

@Bean
public ApplicationListener loginFailureListener(){
return new AuthenticationFailureListener();
}

}

4. Block Future Login Attemps in userdetailsservice


Finally we'll block this user in case of too much login attempts, in this case 10 that was defined on LoginAttemptService.

But since we'll need to inject/autowire HttpServletRequest we need to configure a bean in AppConfig.java
    @Bean
public RequestContextListener requestContextListener(){
return new RequestContextListener();
}

Block in UserDetailsService:

CustomUserDetailsService.java
@Service
public class CustomUserDetailsService implements UserDetailsService {

private final UserService userService;
private final LoginAttemptService loginAttemptService;
private final HttpServletRequest request;

@Autowired
public CustomUserDetailsService(UserService userService, LoginAttemptService loginAttemptService, HttpServletRequest request) {
this.userService = userService;
this.loginAttemptService = loginAttemptService;
this.request = request;
}

@Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
String ip = getClientIP();
if (loginAttemptService.isBlocked(ip)) {
throw new RuntimeException("blocked");
}
User user = this.userService.findOne(id);
if (user == null) throw new UsernameNotFoundException("You are not registered yet. Please register first!");
return user;
}
private String getClientIP() {
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null){
return request.getRemoteAddr();
}
return xfHeader.split(",")[0];
}
}

Check it out guys, you're controlling flood in South East Asia!

 

Comments

  1. Both constructor of theses class
    AuthenticationSuccessEventListener
    AuthenticationFailureListener

    need a parameter... but in AppConfig you pass nothing.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Hi Marc,
      Thanks for the comment. Spring automatically injects LoginAttemptService bean here since we've used constructor injection.

      So what you'll have to do is, autowire those beans and use them. Here I user new as a demonstration. I apologize for the inconvenience. I'll change it.

      Delete

Post a Comment

Popular posts from this blog

Deploy Spring Boot app in digitalocean cloud (or any cloud as long asyou have ssh access)

Upload large files : Spring Boot

User activity logging: Spring