一:CSRF的说明
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已 登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。 跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个 自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买 商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。 这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的 浏览器,却不能保证请求本身是用户自愿发出的。 从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用 程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护。
二:SpringSecurity实现CSRF的原理
1.生成 csrfToken 保存到 HttpSession 或者 Cookie 中。
/**
* Provides the information about an expected CSRF token.
*
* @author Rob Winch
* @since 3.2
* @see DefaultCsrfToken
*/
public interface CsrfToken extends Serializable {
/**
* Gets the HTTP header that the CSRF is populated on the response and can be placed
* on requests instead of the parameter. Cannot be null.
* @return the HTTP header that the CSRF is populated on the response and can be
* placed on requests instead of the parameter
*/
String getHeaderName();
/**
* Gets the HTTP parameter name that should contain the token. Cannot be null.
* @return the HTTP parameter name that should contain the token.
*/
String getParameterName();
/**
* Gets the token value. Cannot be null.
* @return the token value
*/
String getToken();
}
2.在其实现类,有一个类有一个接口如下:
3.此接口实现三个类,分别处理对应的场景,HTTP请求,Cookie,以及一些特定场景的实现,例如lazy等:
这个接口有两个方法是重点,一个是生成token,一个是保存token,在创建token都有一个方法,名为:
private String createNewToken() {
return UUID.randomUUID().toString();
}
请求到来时,从请求中提取 csrfToken,和保存的 csrfToken 做比较,进而判断当 前请求是否合法。主要通过 CsrfFilter 过滤器来完成。
3.CsrfFilter的解析
两个值,一个为token的参数名,一个为token的值。
二.结合案列对csrf的实例展示:
1.首先贴上对应的POI依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!--对Thymeleaf添加Spring Security标签支持--> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.14</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency>
2.对应的配置文件
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver # druid数据库连接池配置 type: com.alibaba.druid.pool.DruidDataSource druid: initial-size: 20 # 初始化数据连接池大小 min-idle: 10 # 空闲数 max-active: 100 #最大连接数 #配置Mybatis的相关属性 mybatis: mapper-locations: classpath:mapper/*.xml #指定mapper XML文件的位置 #type-aliases-package: com.zsc.mybatis_demo.domain #指定实体类的别名的映射路径 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印输出SQL语句 map-underscore-to-camel-case: true # 启动驼峰式转换 use-generated-keys: true #开启自增组件 #debug: true
3.主要的实现配置类等
3.1SecurityConfig
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
UserDetailsService userDetailsService;
//实现用户身份认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置url的访问权限
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/**update**").permitAll()
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated();
//关闭csrf保护功能
http.csrf().disable();
//
//使用自定义的登录窗口
http.formLogin()
.loginPage("/userLogin").permitAll()
.usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/")
.failureUrl("/userLogin?error");
}
}
3.2SpringSecurity中UserDetailsService的实现
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
List<SimpleGrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority("role"));
UserDetails userDetails = new User("lucy", new BCryptPasswordEncoder().encode("123")
, list);
return userDetails;
}
}
在测试成功之后结果为: