Skip to content

Commit 792a1ff

Browse files
committed
Added Anti-Brute-force attack.
1 parent 99f5c72 commit 792a1ff

15 files changed

+278
-28
lines changed

build.gradle

+6-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ dependencies {
6868
compile('org.springframework.boot:spring-boot-starter-web')
6969
compile('de.codecentric:spring-boot-admin-starter-client')
7070
compile('de.codecentric:spring-boot-admin-starter-server')
71+
// https://mvnrepository.com/artifact/com.google.guava/guava
72+
compile('com.google.guava:guava:25.1-jre')
73+
7174
compile('org.webjars:bootstrap:4.1.0')
7275
compile('org.webjars:font-awesome:5.0.13')
7376
// https://mvnrepository.com/artifact/org.webjars/jquery
@@ -76,7 +79,9 @@ dependencies {
7679
runtime('com.h2database:h2')
7780
// runtime('mysql:mysql-connector-java')
7881
compileOnly('org.springframework.boot:spring-boot-configuration-processor')
79-
compileOnly('org.projectlombok:lombok:1.18.0')
82+
// https://mvnrepository.com/artifact/org.projectlombok/lombok
83+
compile('org.projectlombok:lombok')
84+
8085
testCompile('org.springframework.boot:spring-boot-starter-test')
8186
testCompile('org.springframework.restdocs:spring-restdocs-mockmvc')
8287
testCompile('org.springframework.security:spring-security-test')

pi.iml

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
<facet type="Spring" name="Spring">
55
<configuration />
66
</facet>
7+
<facet type="web" name="Web">
8+
<configuration>
9+
<webroots />
10+
<sourceRoots>
11+
<root url="file://$MODULE_DIR$/src/main/resources" />
12+
<root url="file://$MODULE_DIR$/src/main/java" />
13+
</sourceRoots>
14+
</configuration>
15+
</facet>
716
</component>
817
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
918
<exclude-output />

src/main/java/io/home/pi/component/AuthenticationFailureListener.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616
* CELL : +27-78-683-1982
1717
*/
1818
@Component
19-
public class AuthenticationFailureListener
20-
implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
19+
public class AuthenticationFailureListener implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
2120

2221
@Autowired
2322
private LoginAttemptService loginAttemptService;
2423

2524
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) {
26-
WebAuthenticationDetails auth = (WebAuthenticationDetails)
27-
e.getAuthentication().getDetails();
25+
WebAuthenticationDetails auth = (WebAuthenticationDetails) e.getAuthentication().getDetails();
2826

2927
loginAttemptService.loginFailed(auth.getRemoteAddress());
3028
}

src/main/java/io/home/pi/component/AuthenticationSuccessEventListener.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616
* CELL : +27-78-683-1982
1717
*/
1818
@Component
19-
public class AuthenticationSuccessEventListener
20-
implements ApplicationListener<AuthenticationSuccessEvent> {
19+
public class AuthenticationSuccessEventListener implements ApplicationListener<AuthenticationSuccessEvent> {
2120

2221
@Autowired
2322
private LoginAttemptService loginAttemptService;
2423

2524
public void onApplicationEvent(AuthenticationSuccessEvent e) {
26-
WebAuthenticationDetails auth = (WebAuthenticationDetails)
27-
e.getAuthentication().getDetails();
25+
WebAuthenticationDetails auth = (WebAuthenticationDetails) e.getAuthentication().getDetails();
2826

2927
loginAttemptService.loginSucceeded(auth.getRemoteAddress());
3028
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package io.home.pi.component;
2+
3+
import io.home.pi.constant.SpringConstants;
4+
import org.springframework.beans.factory.annotation.Autowired;
5+
import org.springframework.context.MessageSource;
6+
import org.springframework.security.core.AuthenticationException;
7+
import org.springframework.security.web.WebAttributes;
8+
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
9+
import org.springframework.stereotype.Component;
10+
import org.springframework.web.servlet.LocaleResolver;
11+
12+
import javax.servlet.ServletException;
13+
import javax.servlet.http.HttpServletRequest;
14+
import javax.servlet.http.HttpServletResponse;
15+
import java.io.IOException;
16+
import java.util.Locale;
17+
18+
/**
19+
* PROJECT : pi
20+
* PACKAGE : io.home.pi.component
21+
* USER : sean
22+
* DATE : 29-June-2018
23+
* TIME : 20:09
24+
*/
25+
@Component("authenticationFailureHandler")
26+
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
27+
private MessageSource messageSource;
28+
29+
@Autowired
30+
private LocaleResolver localeResolver;
31+
32+
@Autowired
33+
public CustomAuthenticationFailureHandler(MessageSource messageSource) {
34+
this.messageSource = messageSource;
35+
}
36+
37+
38+
@Autowired
39+
public void setMessageSource(MessageSource messageSource) {
40+
this.messageSource = messageSource;
41+
}
42+
43+
/**
44+
* Performs the redirect or forward to the {@code defaultFailureUrl} if set, otherwise
45+
* returns a 401 error code.
46+
* <p>
47+
* If redirecting or forwarding, {@code saveException} will be called to cache the
48+
* exception for use in the target view.
49+
*
50+
* @param request
51+
* @param response
52+
* @param exception
53+
*/
54+
@Override
55+
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
56+
setDefaultFailureUrl(SpringConstants.URL_LOGIN_ERROR_TRUE);
57+
super.onAuthenticationFailure(request, response, exception);
58+
59+
final Locale locale = localeResolver.resolveLocale(request);
60+
61+
String errorMessage = messageSource.getMessage("message.badCredentials", null, locale);
62+
63+
if (exception.getMessage().equalsIgnoreCase("User is disabled")) {
64+
errorMessage = messageSource.getMessage("auth.message.disabled", null, locale);
65+
} else if (exception.getMessage().equalsIgnoreCase("User account has expired")) {
66+
errorMessage = messageSource.getMessage("auth.message.expired", null, locale);
67+
} else if (exception.getMessage().equalsIgnoreCase("blocked")) {
68+
errorMessage = messageSource.getMessage("auth.message.blocked", null, locale);
69+
}
70+
71+
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, errorMessage);
72+
}
73+
}

src/main/java/io/home/pi/config/WebConfig.java

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package io.home.pi.config;
22

3+
import org.springframework.context.MessageSource;
34
import org.springframework.context.annotation.Bean;
45
import org.springframework.context.annotation.ComponentScan;
56
import org.springframework.context.annotation.Configuration;
67
import org.springframework.context.annotation.PropertySource;
8+
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
9+
import org.springframework.web.context.request.RequestContextListener;
10+
import org.springframework.web.servlet.LocaleResolver;
711
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
812
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
913
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
10-
import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;
11-
import org.thymeleaf.spring5.SpringTemplateEngine;
14+
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
15+
16+
import java.util.Locale;
1217

1318
import static io.home.pi.constant.SpringConstants.EXTERNAL_URL_RESOURCES;
1419
import static io.home.pi.constant.SpringConstants.INTERNAL_URL_RESOURCES;
@@ -25,6 +30,28 @@
2530
@ComponentScan
2631
@PropertySource(value = {"classpath:application.properties"})
2732
public class WebConfig implements WebMvcConfigurer {
33+
34+
@Bean
35+
public RequestContextListener requestContextListener() {
36+
return new RequestContextListener();
37+
}
38+
39+
@Bean
40+
public LocaleResolver localeResolver() {
41+
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
42+
localeResolver.setDefaultLocale(Locale.getDefault());
43+
return localeResolver;
44+
}
45+
46+
@Bean
47+
public MessageSource messageSource() {
48+
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
49+
messageSource.setBasename("classpath:messages/messages");
50+
messageSource.setCacheSeconds(60); //reload messages every 10 seconds
51+
return messageSource;
52+
}
53+
54+
2855
/**
2956
* Adds resource handlers.
3057
*
@@ -35,4 +62,5 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) {
3562
registry.addResourceHandler(EXTERNAL_URL_RESOURCES)
3663
.addResourceLocations(INTERNAL_URL_RESOURCES);
3764
}
65+
3866
}

src/main/java/io/home/pi/config/WebSecurityConfig.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.home.pi.config;
22

3+
import io.home.pi.component.CustomAuthenticationFailureHandler;
34
import io.home.pi.service.impl.UserDetailServiceImpl;
45
import org.springframework.beans.factory.annotation.Autowired;
56
import org.springframework.context.annotation.Bean;
@@ -10,7 +11,6 @@
1011
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
1112
import org.springframework.security.crypto.password.PasswordEncoder;
1213
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
13-
import org.springframework.transaction.annotation.EnableTransactionManagement;
1414

1515
import static io.home.pi.constant.SpringConstants.*;
1616

@@ -23,10 +23,13 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
2323

2424
private UserDetailServiceImpl userDetailsService;
2525

26+
private CustomAuthenticationFailureHandler authenticationFailureHandler;
27+
2628
@Autowired
27-
public WebSecurityConfig(PersistentTokenRepository persistenceTokenRepository, UserDetailServiceImpl userDetailsService) {
29+
public WebSecurityConfig(PersistentTokenRepository persistenceTokenRepository, UserDetailServiceImpl userDetailsService, CustomAuthenticationFailureHandler authenticationFailureHandler) {
2830
this.persistenceTokenRepository = persistenceTokenRepository;
2931
this.userDetailsService = userDetailsService;
32+
this.authenticationFailureHandler = authenticationFailureHandler;
3033
}
3134

3235
// @Bean
@@ -54,6 +57,8 @@ protected void configure(HttpSecurity httpSecurity) throws Exception {
5457
.formLogin()
5558
.loginPage(URL_LOGIN_PAGE)
5659
.defaultSuccessUrl(URL_DEFAULT_AUTH_USER_PAGE, true)
60+
// .successHandler(myAuthenticationSuccessHandler)
61+
.failureHandler(authenticationFailureHandler)
5762
.permitAll()
5863
.and()
5964
.sessionManagement()

src/main/java/io/home/pi/constant/SpringConstants.java

+1
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ public class SpringConstants {
4343
public static final String[] INTERNAL_URL_RESOURCES = {
4444
URL_INTERNAL_RESOURCES_PUB, URL_INTERNAL_RESOURCES_STATIC, URL_INTERNAL_RESOURCES_WEBJAR,};
4545

46+
public static final String URL_LOGIN_ERROR_TRUE = "/user/login?error=true";
4647
}

src/main/java/io/home/pi/controller/UserProfileCtrl.java

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import io.home.pi.constant.SpringConstants;
44
import org.slf4j.Logger;
55
import org.slf4j.LoggerFactory;
6-
import org.springframework.security.access.prepost.PreAuthorize;
76
import org.springframework.security.core.Authentication;
87
import org.springframework.security.core.context.SecurityContextHolder;
98
import org.springframework.stereotype.Controller;

src/main/java/io/home/pi/service/impl/LoginAttemptService.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.home.pi.service.impl;
22

3-
import org.springframework.cglib.core.internal.LoadingCache;
3+
import com.google.common.cache.CacheBuilder;
4+
import com.google.common.cache.CacheLoader;
5+
import com.google.common.cache.LoadingCache;
46
import org.springframework.stereotype.Service;
57

68
import java.util.concurrent.ExecutionException;
@@ -17,7 +19,7 @@
1719
@Service
1820
public class LoginAttemptService {
1921

20-
private final int MAX_ATTEMPT = 10;
22+
private final int MAX_ATTEMPT = 3;
2123
private LoadingCache<String, Integer> attemptsCache;
2224

2325
public LoginAttemptService() {

src/main/java/io/home/pi/service/impl/UserDetailServiceImpl.java

+31-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.springframework.security.core.userdetails.UsernameNotFoundException;
1414
import org.springframework.stereotype.Service;
1515

16+
import javax.servlet.http.HttpServletRequest;
1617
import java.util.ArrayList;
1718
import java.util.List;
1819
import java.util.Optional;
@@ -30,15 +31,17 @@
3031
@Service
3132
public class UserDetailServiceImpl implements UserDetailsService {
3233
private static final Logger LOGGER = LoggerFactory.getLogger(UserDetailServiceImpl.class);
33-
3434
private final UserService userService;
35+
private HttpServletRequest request;
36+
private LoginAttemptService loginAttemptService;
3537

3638
@Autowired
37-
public UserDetailServiceImpl(UserService userService) {
39+
public UserDetailServiceImpl(HttpServletRequest request, LoginAttemptService loginAttemptService, UserService userService) {
40+
this.request = request;
41+
this.loginAttemptService = loginAttemptService;
3842
this.userService = userService;
3943
}
4044

41-
4245
/**
4346
* Locates the user based on the username. In the actual implementation, the search
4447
* may possibly be case sensitive, or case insensitive depending on how the
@@ -52,8 +55,14 @@ public UserDetailServiceImpl(UserService userService) {
5255
* GrantedAuthority
5356
*/
5457
@Override
55-
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
58+
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, SecurityException {
59+
String ip = getClientIP(request);
60+
if (loginAttemptService.isBlocked(ip)) {
61+
throw new SecurityException("blocked");
62+
}
63+
5664
Optional<User> userOptional = Optional.ofNullable(userService.findByUsername(username));
65+
5766
if (!userOptional.isPresent()) {
5867
throw new UsernameNotFoundException("Username & Password doesn't exist!");
5968
}
@@ -68,10 +77,11 @@ private List<GrantedAuthority> getGrantedAuthorities(User user) {
6877

6978
groupAuthorities.add(user.getTeam().getGrpAuth());
7079

71-
7280
for (GrpAuth userAuth : groupAuthorities) {
7381
LOGGER.info("User Auth: " + userAuth.toString());
74-
authorities.add(new SimpleGrantedAuthority(USER_ROLE_PREFIX + userAuth.getAuthorities().iterator().next().getLevel()));
82+
while (userAuth.getAuthorities().iterator().hasNext()) {
83+
authorities.add(new SimpleGrantedAuthority(USER_ROLE_PREFIX + userAuth.getAuthorities().iterator().next().getLevel()));
84+
}
7585
}
7686
} catch (Exception e) {
7787
LOGGER.error(e.getMessage(), e);
@@ -86,4 +96,19 @@ private List<GrantedAuthority> getGrantedAuthorities(User user) {
8696
}
8797

8898

99+
private String getClientIP(HttpServletRequest request) {
100+
String clientIP;
101+
String xfHeader = request.getHeader("X-Fowarded-For");
102+
if (xfHeader == null) {
103+
String remoteIP = request.getRemoteAddr();
104+
LOGGER.info("Remote-IP: {}", remoteIP);
105+
return remoteIP;
106+
}
107+
108+
clientIP = xfHeader.split(",")[0];
109+
LOGGER.info("Client-IP: {}", clientIP);
110+
return clientIP;
111+
}
112+
113+
89114
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.home.pi.util;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
/**
7+
* PROJECT : pi
8+
* PACKAGE : io.home.pi.util
9+
* USER : sean
10+
* DATE : 29-June-2018
11+
* TIME : 19:46
12+
*/
13+
public class PiUtil {
14+
private static final Logger LOGGER = LoggerFactory.getLogger(PiUtil.class);
15+
16+
17+
}

src/main/resources/data.sql

+2-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,5 @@ insert into rpi.team (grp_id, grp_auth_id) values (1, 1);
1818
insert into rpi.user (username, password, enabled, team_id)
1919
values ('demo@email.com', '$2a$10$.R9eHFOfpQDa.BEmkUUdVegl/5XUQ8ELXaKFz/7QJmqunjbVyg/0y', true, 1);
2020

21-
22-
insert into RPI.TOKEN_LOG (id,series, timestamp, token, username)
23-
values(1, 'KDRbKsElGjpY0abA1vwZUQ==', CURRENT_TIMESTAMP(), '83AjDxYvEBxDgLMJLNzkaA==', 'demo@email.com');
21+
-- insert into RPI.TOKEN_LOG (id,series, timestamp, token, username)
22+
-- values(1, 'KDRbKsElGjpY0abA1vwZUQ==', CURRENT_TIMESTAMP(), '83AjDxYvEBxDgLMJLNzkaA==', 'demo@email.com');

0 commit comments

Comments
 (0)