Java 使用Spring Security和AngularJS预防CSRF
我使用的是Spring 4.3.12.0发行版AngularJS 1.4.8。我正试图阻止对应用程序的CSRF攻击Java 使用Spring Security和AngularJS预防CSRF,java,angularjs,spring-security,csrf,csrf-token,Java,Angularjs,Spring Security,Csrf,Csrf Token,我使用的是Spring 4.3.12.0发行版AngularJS 1.4.8。我正试图阻止对应用程序的CSRF攻击 @Configuration @Order(2) public static class SecurityConfig extends WebSecurityConfigurerAdapter { String[] pathsToRemoveAuthorizaton = { "/mobile/**",
@Configuration
@Order(2)
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
String[] pathsToRemoveAuthorizaton = {
"/mobile/**",
"/logout",
"/j_spring_security_logout",
"/login",
};
private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
@Override
public void configure(HttpSecurity http) throws Exception {
logger.info("http configure");
http.antMatcher("/**").authorizeRequests().antMatchers(pathsToRemoveAuthorizaton).permitAll()
.antMatchers("/**").authenticated()
.and().formLogin().loginPage("/login")
.usernameParameter("employeeId").passwordParameter("password")
.successForwardUrl("/dashboard").defaultSuccessUrl("/dashboard", true)
.successHandler(customAuthenticationSuccessHandler()).loginProcessingUrl("/j_spring_security_check")
.and().logout().logoutSuccessUrl("/logout").logoutUrl("/j_spring_security_logout")
.logoutSuccessHandler(customLogoutSuccessHandler()).permitAll().invalidateHttpSession(true)
.deleteCookies("JSESSIONID").and().sessionManagement().sessionFixation().newSession()
.maximumSessions(1).maxSessionsPreventsLogin(true).and()
.sessionCreationPolicy(SessionCreationPolicy.NEVER).invalidSessionUrl("/logout").and()
.exceptionHandling().accessDeniedPage("/logout");
// http.csrf().csrfTokenRepository(csrfTokenRepository()).and()
// .addFilterAfter(new StatelessCSRFFilter(), CsrfFilter.class);
http.csrf().csrfTokenRepository(csrfTokenRepository());
// http.csrf().disable();
// http.csrf().ignoringAntMatchers("/mobile/**");
http.authorizeRequests().anyRequest().authenticated();
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
@Bean
public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
return new CustomAuthenticationSuccessHandler();
}
@Bean
public LogoutSuccessHandler customLogoutSuccessHandler() {
return new CustomLogoutSuccessHandler();
}
}
下面是我的角度服务代码
govtPMS.service('Interceptor', function($q, $location, $rootScope, pinesNotifications, Auth) {
return {
request: function(config) {
config.headers.Authorization = 'Bearer '+$rootScope.authToken;
// document.cookie = 'CSRF-TOKEN=' + $rootScope.generateKey;
return config;
},
requestError: function (rejection) {
return $q.reject(rejection);
},
response: function(res) {
if(res.status === 200 || res.status === 201){
if(res.data.response !== undefined){
if(res.data.status === 1 || res.data.status === 3 || res.data.status === 2) {
pinesNotifications.notify({
'title': 'Success',
'text': res.data.message,
'type': 'success',
'delay': 5000
});
}
else if(res.data.status === 5) {
pinesNotifications.notify({
'title': 'Warning',
'text': res.data.message,
'type': 'warning',
'delay': 5000
});
}
}
}
return res || $q.when(res);
},
responseError: function(error) {
return $q.reject(error);
}
};
}).config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
$httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN';
$httpProvider.interceptors.push('Interceptor');
}])
我仍然无法看到CSRF令牌头和请求
在这个应用程序中,我们使用3个jsp页面——login.jsp、logout.jsp和dashboard.jsp
角度范围在dashboard.jsp中定义,因此登录和注销不在AngularJS的范围内。
我也尝试过无状态的方式,例如angular生成UUID并附加cookie和请求头,下面的过滤器可以很好地完成这项工作。
直到注销攻击。在此攻击中,攻击者试图成功注销用户,因为要从应用程序注销,我们只需使用href
<li><a href="j_spring_security_logout" ><i class="fa fa-sign-out"></i><span>Logout</span></a></li>
现在,由于它的注销超出了角度,angularjs拦截器无法将UUID附加到那里。
从上个星期开始我就一直在努力,如果有任何帮助,我将不胜感激
无状态CSRFFilter.java
package com.leadwinner.sms.config.filters;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.web.filter.OncePerRequestFilter;
public class StatelessCSRFFilter extends OncePerRequestFilter {
private static final String CSRF_TOKEN = "CSRF-TOKEN";
private static final String X_CSRF_TOKEN = "X-CSRF-TOKEN";
private final AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
List<String> excludedUrls = new ArrayList<>();
excludedUrls.add("/resources");
excludedUrls.add("/j_spring_security_check");
excludedUrls.add("/j_spring_security_logout");
excludedUrls.add("/login");
excludedUrls.add("/logout");
excludedUrls.add("/mobile");
excludedUrls.add("/migrate");
excludedUrls.add("/dashboard");
String path = request.getServletPath();
System.out.println(path);
AtomicBoolean ignoreUrl = new AtomicBoolean(false);
excludedUrls.forEach(url -> {
if (request.getServletPath().startsWith(url.toLowerCase()) || request.getServletPath().equals("/")) {
ignoreUrl.set(true);
}
});
if (!ignoreUrl.get()) {
final String csrfTokenValue = request.getHeader(X_CSRF_TOKEN);
final Cookie[] cookies = request.getCookies();
System.out.println("**************************************************");
System.out.println("--------------------------------------------------");
String csrfCookieValue = null;
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(CSRF_TOKEN)) {
csrfCookieValue = cookie.getValue();
}
}
}
System.out.println("csrfTokenValue = "+csrfTokenValue);
System.out.println("csrfCookieValue = "+csrfCookieValue);
System.out.println("--------------------------------------------------");
System.out.println("**************************************************");
if (csrfTokenValue == null || !csrfTokenValue.equals(csrfCookieValue)) {
accessDeniedHandler.handle(request, response, new AccessDeniedException(
"Missing or non-matching CSRF-token"));
return;
}
}
filterChain.doFilter(request, response);
}
}
package com.leadwenner.sms.config.filters;
导入java.io.IOException;
导入java.util.ArrayList;
导入java.util.List;
导入java.util.concurrent.AtomicBoolean;
导入javax.servlet.FilterChain;
导入javax.servlet.ServletException;
导入javax.servlet.http.Cookie;
导入javax.servlet.http.HttpServletRequest;
导入javax.servlet.http.HttpServletResponse;
导入org.springframework.security.access.AccessDeniedException;
导入org.springframework.security.web.access.AccessDeniedHandler;
导入org.springframework.security.web.access.AccessDeniedHandlerImpl;
导入org.springframework.web.filter.OncePerRequestFilter;
公共类无状态CSRFFilter扩展了OncePerRequestFilter{
私有静态最终字符串CSRF_TOKEN=“CSRF-TOKEN”;
私有静态最终字符串X_CSRF_TOKEN=“X-CSRF-TOKEN”;
private final AccessDeniedHandler AccessDeniedHandler=new AccessDeniedHandlerImpl();
@凌驾
受保护的void doFilterInternal(HttpServletRequest请求、HttpServletResponse响应、FilterChain FilterChain)
抛出ServletException、IOException{
List excludedUrls=new ArrayList();
添加(“/resources”);
添加(“/j_-spring_-security_-check”);
添加(“/j_spring_security_logout”);
添加(“/login”);
添加(“/注销”);
添加(“/mobile”);
添加(“/migrate”);
不包括仪表盘。添加(“/仪表盘”);
String path=request.getServletPath();
System.out.println(路径);
AtomicBoolean ignoreUrl=新的AtomicBoolean(false);
excludedUrls.forEach(url->{
if(request.getServletPath().startsWith(url.toLowerCase())| | request.getServletPath().equals(“/”){
ignoreUrl.set(true);
}
});
如果(!ignoreUrl.get()){
最终字符串csrfTokenValue=request.getHeader(X_CSRF_TOKEN);
最终Cookie[]cookies=request.getCookies();
System.out.println(“*******************************************************************”);
System.out.println(“-------------------------------------------------------------”;
字符串csrfCookieValue=null;
如果(cookies!=null){
用于(Cookie:cookies){
if(cookie.getName().equals(CSRF_令牌)){
csrfCookieValue=cookie.getValue();
}
}
}
System.out.println(“csrfTokenValue=“+csrfTokenValue”);
System.out.println(“csrfCookieValue=“+csrfCookieValue”);
System.out.println(“-------------------------------------------------------------”;
System.out.println(“*******************************************************************”);
如果(csrfTokenValue==null | |!csrfTokenValue.equals(csrfCookieValue)){
handle(请求、响应、新AccessDeniedException(
“丢失或不匹配的CSRF令牌”);
返回;
}
}
filterChain.doFilter(请求、响应);
}
}
如果可以通过浏览器发出请求并自动提交凭据(会话cookie、基本身份验证凭据),则即使使用移动API,也需要CSRF保护 考虑到应用程序中有一个移动API,问题是浏览器能否成功地处理这些API 我建议您创建两个独立的过滤链,一个用于web应用程序,另一个用于移动API:
@配置
@订单(100)
公共类WebAppConfig扩展了WebSecurity配置适配器{
@凌驾
受保护的无效配置(HttpSecurity http){
http
.requestMatchers()
.antMatchers(“/app/**”)
.及()
.授权请求()
// ...
.anyRequest().authenticated()
.及()
.formLogin();
}
}
@配置
@命令(101)
公共类MobileApiConfig扩展了WebSecurity配置适配器{
@凌驾
受保护的无效配置(HttpSecurity http){
http
.requestMatchers()
.antMatchers(“/api/**”)
.及()
.授权请求()
// ...
.anyRequest().authenticated()
.及()
.oauth2ResourceServer()
.jwt();
}
}
这样做的目的是将两种配置分开。相对于web应用程序的端点使用一种设置,而相对于移动API的端点使用另一种设置
现在,有几条关于移动API的评论。我假设您正在使用OAuth 2.0承载令牌进行身份验证,这就是为什么上面的配置使用Spring Security 5.1+中的oauth2ResourceServer()。这是什么
http
.authorizeRequests()
.antMatchers(patterns)
.permitAll()
.antMatchers("/hello/**")
.hasRole("USER")
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.requireCsrfProtectionMatcher(csrfProtectionMatcher(patterns))
.and()
.httpBasic()
.and()
.addFilterAfter(csrfFilter(patterns), FilterSecurityInterceptor.class)
.addFilterAfter(new StatelessCSRFFilter(), CsrfFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
CsrfToken csrf = (CsrfToken) servletRequest.getAttribute(CsrfToken.class.getName());
String token = csrf.getToken();
if (token != null && isAuthenticating(servletRequest)) {
HttpServletResponse response = (HttpServletResponse) servletResponse;
Cookie cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private boolean isAuthenticating(ServletRequest servletRequest) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
return request.getRequestURI().equals("/login");
}`