【Spring-Security】Re09 CSFR处理
一、CSRF:
CSRF 全称 Cross Site Request Forgery 跨站请求伪造
又称为OneClick Attack & SessionRiding
是非法请求访问,通过伪造用户请求访问受信任网站
什么是跨域?
只要 协议 - IP地址 - 端口号 三者中的任何一个不相同进行的访问,就是跨域请求
相关资料:
https://blog.csdn.net/xiaoxinshuaiga/article/details/80766369
二、Security的CSRF设置:
Security4版本开始默认开启CSRF攻击拦截,验证的方法是要求凭证提供键为 _csrf 的ID
该Token在服务端生成,登录时会用户携带此ID一并给Security。
演示案例:
首先取消CSRF关闭:
package cn.zeal4j.configuration; import cn.zeal4j.handler.CustomAccessDeniedHandler; import cn.zeal4j.handler.FarsAuthenticationFailureHandler; import cn.zeal4j.handler.FarsAuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.parameters.P; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import javax.sql.DataSource; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:55 */ @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private AccessDeniedHandler accessDeniedHandler; @Qualifier("userDetailsServiceImpl") @Autowired private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; @Autowired private PersistentTokenRepository persistentTokenRepository; @Bean public PersistentTokenRepository getPersistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); // 数据源注入 jdbcTokenRepository.setCreateTableOnStartup(false); // 由Security完成Token表的创建,如果有了就设置false关闭 return jdbcTokenRepository; } @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆 // 登陆请求参数设置 usernameParameter("username"). passwordParameter("password"). loginPage("/login.html"). // 设置登陆页面URL路径 loginProcessingUrl("/login.action"). // 设置表单提交URL路径 successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求 failureForwardUrl("/error.page"); // 设置认证失败跳转URL路径 POST请求 // successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆 // failureHandler(new FarsAuthenticationFailureHandler("/error.html")).; // 跨域处理,不需要跳转了 httpSecurity.authorizeRequests(). regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式 antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行 antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问 antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问 antMatchers("/admin.page").hasAnyAuthority("admin"). /*antMatchers("/vip-01.page").hasAnyAuthority("vip-01").*/ antMatchers("/vip-01.page").hasRole("vip-01"). antMatchers("/ip.page").hasIpAddress("192.168.43.180"). // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配 // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀 anyRequest().authenticated(); // 其他请求均需要被授权访问 // anyRequest().access("@customServiceImpl.hasPermission(request, authentication)"); // 自定义Access配置 // CSRF攻击拦截关闭 // httpSecurity.csrf().disable(); httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 记住我 httpSecurity.rememberMe(). tokenValiditySeconds(60). // 设置Token有效时间, 以秒为单位取值 userDetailsService(userDetailsService). tokenRepository(persistentTokenRepository); // 退出登录处理 httpSecurity. logout(). // logoutUrl("/xxx/xxx/logout"). logoutSuccessUrl("/login.html"); } }
注意CSRF的Token需要Thymeleaf模板获取,也就是登录页面要放在模板目录里面,这里就重新编写一个登陆页面【csrf-login.html】
在登陆页面设置CSRF的Token处理:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" > <head> <meta charset="UTF-8"> <title>Title</title> <style type="text/css"> h3,p { text-align: center; } </style> </head> <body> <h3>custom login page</h3> <form method="post"th:action="@{/login.action}" > <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}" > <p>username: <input type="text" name="username"></p> <p>password: <input type="password" name="password"></p> <p>rememberMe: <input type="checkbox" name="remember-me" value="true"></p> <p><input type="submit" value="login"></p> </form> </body> </html>
编写控制器
package cn.zeal4j.controller; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 22:35 */ @Secured("ROLE_vip-01") @Controller public class LoginController { @PostMapping("csrf-login.page") public String toCsrfLoginPage() { return "csrf-login"; } @RequestMapping("main.page") public String toMainPage() { return "main"; // 模版内的页面不允许重定向,忘了忘了 } @PostMapping("error.page") // 控制器不支持POST请求跳转解析, 需要控制器跳转 Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported] public String redirectToErrorPage() { return "redirect:/error.html"; // 重定向要写/标识 区分模版解析 } }
配置再更改为这个控制接口:
package cn.zeal4j.configuration; import cn.zeal4j.handler.CustomAccessDeniedHandler; import cn.zeal4j.handler.FarsAuthenticationFailureHandler; import cn.zeal4j.handler.FarsAuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.parameters.P; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import javax.sql.DataSource; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:55 */ @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private AccessDeniedHandler accessDeniedHandler; @Qualifier("userDetailsServiceImpl") @Autowired private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; @Autowired private PersistentTokenRepository persistentTokenRepository; @Bean public PersistentTokenRepository getPersistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); // 数据源注入 jdbcTokenRepository.setCreateTableOnStartup(false); // 由Security完成Token表的创建,如果有了就设置false关闭 return jdbcTokenRepository; } @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆 // 登陆请求参数设置 usernameParameter("username"). passwordParameter("password"). // loginPage("/login.html"). // 设置登陆页面URL路径 loginPage("/csrf-login.page"). loginProcessingUrl("/login.action"). // 设置表单提交URL路径 successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求 failureForwardUrl("/error.page"); // 设置认证失败跳转URL路径 POST请求 // successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆 // failureHandler(new FarsAuthenticationFailureHandler("/error.html")).; // 跨域处理,不需要跳转了 httpSecurity.authorizeRequests(). regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式 antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行 antMatchers("/csrf-login.page").permitAll(). antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问 antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问 antMatchers("/admin.page").hasAnyAuthority("admin"). /*antMatchers("/vip-01.page").hasAnyAuthority("vip-01").*/ antMatchers("/vip-01.page").hasRole("vip-01"). antMatchers("/ip.page").hasIpAddress("192.168.43.180"). // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配 // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀 anyRequest().authenticated(); // 其他请求均需要被授权访问 // anyRequest().access("@customServiceImpl.hasPermission(request, authentication)"); // 自定义Access配置 // CSRF攻击拦截关闭 // httpSecurity.csrf().disable(); httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 记住我 httpSecurity.rememberMe(). tokenValiditySeconds(60). // 设置Token有效时间, 以秒为单位取值 userDetailsService(userDetailsService). tokenRepository(persistentTokenRepository); // 退出登录处理 httpSecurity. logout(). // logoutUrl("/xxx/xxx/logout"). logoutSuccessUrl("/login.html"); } }
使用csrf登录:
CSRF的显示在这里: