Skip to content

Commit 234196d

Browse files
cssruphp-coder
authored andcommitted
Add logging about missing or invalid CSRF tokens.
Fix #80
1 parent 0172e38 commit 234196d

File tree

7 files changed

+161
-1
lines changed

7 files changed

+161
-1
lines changed

src/main/java/ru/mystamps/web/service/SiteService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import java.util.Date;
2121

22+
import javax.servlet.http.HttpServletRequest;
23+
2224
public interface SiteService {
2325
@SuppressWarnings("PMD.UseObjectForClearerAPI")
2426
void logAboutAbsentPage(
@@ -39,4 +41,6 @@ void logAboutFailedAuthentication(
3941
String agent,
4042
Date date
4143
);
44+
void logAboutMissingCsrfToken(HttpServletRequest request);
45+
void logAboutInvalidCsrfToken(HttpServletRequest request);
4246
}

src/main/java/ru/mystamps/web/service/SiteServiceImpl.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import java.util.Date;
2121

22+
import javax.servlet.http.HttpServletRequest;
23+
2224
import org.apache.commons.lang3.StringUtils;
2325
import org.apache.commons.lang3.Validate;
2426

@@ -33,6 +35,7 @@
3335
import ru.mystamps.web.Db;
3436
import ru.mystamps.web.dao.SuspiciousActivityDao;
3537
import ru.mystamps.web.dao.dto.AddSuspiciousActivityDbDto;
38+
import ru.mystamps.web.support.spring.security.SecurityContextUtils;
3639

3740
@RequiredArgsConstructor
3841
public class SiteServiceImpl implements SiteService {
@@ -44,6 +47,11 @@ public class SiteServiceImpl implements SiteService {
4447
private static final String PAGE_NOT_FOUND = "PageNotFound";
4548
private static final String AUTHENTICATION_FAILED = "AuthenticationFailed";
4649

50+
// see add-types-for-csrf-tokens-to-suspicious_activities_types-table changeset
51+
// in src/main/resources/liquibase/version/0.4/2016-02-19--csrf_events.xml
52+
private static final String MISSING_CSRF_TOKEN = "MissingCsrfToken";
53+
private static final String INVALID_CSRF_TOKEN = "InvalidCsrfToken";
54+
4755
private final SuspiciousActivityDao suspiciousActivities;
4856

4957
@Override
@@ -76,6 +84,46 @@ public void logAboutFailedAuthentication(
7684
logEvent(AUTHENTICATION_FAILED, page, method, userId, ip, referer, agent, date);
7785
}
7886

87+
/**
88+
* @author Sergey Chechenev
89+
*/
90+
@Override
91+
@Transactional
92+
public void logAboutMissingCsrfToken(HttpServletRequest request) {
93+
94+
logEvent(
95+
MISSING_CSRF_TOKEN,
96+
request.getRequestURI(),
97+
request.getMethod(),
98+
SecurityContextUtils.getUserId(),
99+
request.getRemoteAddr(),
100+
request.getHeader("referer"),
101+
request.getHeader("user-agent"),
102+
new Date()
103+
);
104+
105+
}
106+
107+
/**
108+
* @author Sergey Chechenev
109+
*/
110+
@Override
111+
@Transactional
112+
public void logAboutInvalidCsrfToken(HttpServletRequest request) {
113+
114+
logEvent(
115+
INVALID_CSRF_TOKEN,
116+
request.getRequestURI(),
117+
request.getMethod(),
118+
SecurityContextUtils.getUserId(),
119+
request.getRemoteAddr(),
120+
request.getHeader("referer"),
121+
request.getHeader("user-agent"),
122+
new Date()
123+
);
124+
125+
}
126+
79127
@SuppressWarnings({"PMD.UseObjectForClearerAPI", "checkstyle:parameternumber"})
80128
private void logEvent(
81129
String type,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (C) 2009-2016 Slava Semushin <slava.semushin@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
package ru.mystamps.web.support.spring.security;
19+
20+
import java.io.IOException;
21+
22+
import javax.servlet.ServletException;
23+
import javax.servlet.http.HttpServletRequest;
24+
import javax.servlet.http.HttpServletResponse;
25+
26+
import org.springframework.security.access.AccessDeniedException;
27+
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
28+
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
29+
import org.springframework.security.web.csrf.MissingCsrfTokenException;
30+
31+
import ru.mystamps.web.service.SiteService;
32+
33+
/**
34+
* @author Sergey Chechenev
35+
*/
36+
public class LogCsrfEventAndShow403PageForAccessDenied extends AccessDeniedHandlerImpl {
37+
private final SiteService siteService;
38+
39+
public LogCsrfEventAndShow403PageForAccessDenied(SiteService siteService, String errorPage) {
40+
super();
41+
super.setErrorPage(errorPage);
42+
this.siteService = siteService;
43+
}
44+
45+
@Override
46+
public void handle(
47+
HttpServletRequest request,
48+
HttpServletResponse response,
49+
AccessDeniedException exception)
50+
throws IOException, ServletException {
51+
52+
if (exception instanceof MissingCsrfTokenException) {
53+
siteService.logAboutMissingCsrfToken(request);
54+
} else if (exception instanceof InvalidCsrfTokenException) {
55+
siteService.logAboutInvalidCsrfToken(request);
56+
}
57+
58+
super.handle(request, response, exception);
59+
}
60+
61+
}

src/main/java/ru/mystamps/web/support/spring/security/SecurityConfig.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.security.core.userdetails.UserDetailsService;
4040
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
4141
import org.springframework.security.crypto.password.PasswordEncoder;
42+
import org.springframework.security.web.access.AccessDeniedHandler;
4243
import org.springframework.security.web.util.matcher.RequestMatcher;
4344

4445
import ru.mystamps.web.Url;
@@ -88,7 +89,7 @@ protected void configure(HttpSecurity http) throws Exception {
8889
.permitAll()
8990
.and()
9091
.exceptionHandling()
91-
.accessDeniedPage(Url.UNAUTHORIZED_PAGE)
92+
.accessDeniedHandler(getAccessDeniedHandler())
9293
// This entry point handles when you request a protected page and you are
9394
// not yet authenticated (defaults to Http403ForbiddenEntryPoint)
9495
.authenticationEntryPoint(new Http401UnauthorizedEntryPoint())
@@ -121,6 +122,14 @@ public ApplicationListener<AuthenticationFailureBadCredentialsEvent> getApplicat
121122
return new AuthenticationFailureListener();
122123
}
123124

125+
@Bean
126+
public AccessDeniedHandler getAccessDeniedHandler() {
127+
return new LogCsrfEventAndShow403PageForAccessDenied(
128+
servicesConfig.getSiteService(),
129+
Url.FORBIDDEN_PAGE
130+
);
131+
}
132+
124133
private UserDetailsService getUserDetailsService() {
125134
return new CustomUserDetailsService(servicesConfig.getUserService());
126135
}

src/main/java/ru/mystamps/web/support/spring/security/SecurityContextUtils.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,16 @@
1717
*/
1818
package ru.mystamps.web.support.spring.security;
1919

20+
import java.util.Optional;
21+
2022
import javax.servlet.http.HttpServletRequest;
2123

24+
import org.springframework.security.core.Authentication;
25+
import org.springframework.security.core.context.SecurityContextHolder;
2226
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
2327

28+
import ru.mystamps.web.entity.User;
29+
2430
public final class SecurityContextUtils {
2531

2632
private SecurityContextUtils() {
@@ -30,4 +36,18 @@ public static boolean hasAuthority(HttpServletRequest request, String authority)
3036
return new SecurityContextHolderAwareRequestWrapper(request, null).isUserInRole(authority);
3137
}
3238

39+
/**
40+
* @author Sergey Chechenev
41+
*/
42+
public static Integer getUserId() {
43+
return Optional
44+
.ofNullable(SecurityContextHolder.getContext().getAuthentication())
45+
.map(Authentication::getPrincipal)
46+
.filter(CustomUserDetails.class::isInstance)
47+
.map(CustomUserDetails.class::cast)
48+
.map(CustomUserDetails::getUser)
49+
.map(User::getId)
50+
.orElse(null);
51+
}
52+
3353
}

src/main/resources/liquibase/version/0.4.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
<include file="0.4/2015-11-13--nullable_columns.xml" relativeToChangelogFile="true" />
1313
<include file="0.4/2016-01-02--non_unique_catalog_numbers.xml" relativeToChangelogFile="true" />
1414
<include file="0.4/2016-01-04--unique_series_in_collection.xml" relativeToChangelogFile="true" />
15+
<include file="0.4/2016-02-19--csrf_events.xml" relativeToChangelogFile="true" />
1516

1617
</databaseChangeLog>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<databaseChangeLog
3+
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
6+
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
7+
8+
<changeSet id="add-types-for-csrf-tokens-to-suspicious_activities_types-table" author="cssru" context="init-data">
9+
<insert tableName="suspicious_activities_types">
10+
<column name="name" value="MissingCsrfToken" />
11+
</insert>
12+
<insert tableName="suspicious_activities_types">
13+
<column name="name" value="InvalidCsrfToken" />
14+
</insert>
15+
</changeSet>
16+
17+
</databaseChangeLog>

0 commit comments

Comments
 (0)