一、CSRF:

CSRF 全称 Cross Site Request Forgery 跨站请求伪造

又称为OneClick Attack & SessionRiding 

是非法请求访问,通过伪造用户请求访问受信任网站

什么是跨域?

只要 协议 - IP地址 - 端口号 三者中的任何一个不相同进行的访问,就是跨域请求

二、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登录:
【Spring-Security】Re09 CSFR处理_post请求

CSRF的显示在这里:

【Spring-Security】Re09 CSFR处理_post请求_02