Spring Security

第一章 了解 spring security

spring security 是基于 spring 的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。在 Spring Framework 基础上,spring security 充分 利用了 依赖 注入(DI)和 面向切 面编 程( AOP)功 能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。是一个轻量级的安全框架。它与 Spring MVC 有很好地集成.

1.1 spring security 核心功能

(1)认证(你是谁,用户/设备/系统)
(2)验证(你能干什么,也叫权限控制/授权,允许执行的操作)

1.2 spring security 原理

基于 Filter , Servlet, AOP 实现身份认证和权限验证

第二章 实例驱动学习

使用的框架和技术
spring boot 2.0.6 版本
spring security 5.0.9 版本
maven 3 以上
jdk8 以上
idea 2019

第一个例子:初探

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ydj</groupId>
    <artifactId>ch01-first</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--spring boot-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-parent</artifactId>
        <version>2.0.6.RELEASE</version>
    </parent>
    <dependencies>
        <!--web开发依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--security依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

@RestController
@RequestMapping("/hello")
public class HelloSecurityController {
    @RequestMapping("world")
    public String sayHello() {
        return "Hello Spring Security 安全管理框架";
    }
}

启动项目后控制台会出现一下信息
在这里插入图片描述
访问http://localhost:8080/hello/world
在这里插入图片描述User:user
Password:刚刚控制台生产的密码

自定义用户名和密码

spring:
  security:
    user:
      name: ydj
      password: 123456

关闭验证

//排除Security配置
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class FirstApplication {
    public static void main(String[] args) {
        SpringApplication.run(FirstApplication.class, args);
    }
}

第二个例子:使用内存中的用户信息

1)使用:WebSecurityConfigurerAdapter 控制安全管理的内容。

需要做的使用:继承 WebSecurityConfigurerAdapter,重写方法。实现自定义的认证信息。重写下面的方法。
protected void configure(AuthenticationManagerBuilder auth)

@Configuration
@EnableWebSecurity//表示启用 spring security 安全框架的功能
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder encoder = passwordEncoder();
        auth.inMemoryAuthentication().withUser("zhangsan").password(encoder.encode("123456")).roles();
        auth.inMemoryAuthentication().withUser("lisi").password(encoder.encode("123456")).roles();
        auth.inMemoryAuthentication().withUser("admin").password(encoder.encode("admin")).roles();
    }
    //创建密码加密类
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2)spring security 5 版本要求密码比较加密,否则报错

java.lang.IllegalArgumentException: There is no PasswordEncodermapped for the id "null"

实现密码加密

1)创建用来加密的实现类(选择一种加密的算法)

 //创建密码加密类
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

2)给每个密码加密

PasswordEncoder pe = passwordEncoder();
pe.encode("123456");

第三个例子:基于角色 Role 的身份认证

基于角色的实现步骤:
1.设置用户的角色
继承 WebSecurityConfigurerAdapter
重写 configure 方法。指定用户的 roles

**
 * @EnableGlobalMethodSecurity:启动方法级别的认证
 *      prePostEnabled:boolean 默认 false
 *      true:表示可以使用@PreAuthorize注解和@PostAuthorize
 */
@EnableWebSecurity//包含了@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder encoder = passwordEncoder();
        //定义2个角色 normal admin
        auth.inMemoryAuthentication().
                withUser("zhangsan").password(encoder.encode("123456")).
                roles("normal");
        auth.inMemoryAuthentication().
                withUser("lisi").password(encoder.encode("123456")).
                roles("normal");
        auth.inMemoryAuthentication()
                .withUser("admin").password(encoder.encode("admin")).
                roles("admin","normal");
    }
    //创建密码加密类
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
@RestController
@RequestMapping("/hello")
public class HelloSecurityController {
    @RequestMapping("world")
    public String sayHello() {
        return "Hello Spring Security 安全管理框架";
    }

    //指定normal和admin角色都可以访问的方法
    @RequestMapping("helloUser")
    @PreAuthorize(value = "hasAnyRole('admin','normal')")
    public String helloCommonUser() {
        return "hello 拥有normal,admin角色的用户";
    }
    //指定Admin角色的方法
    @RequestMapping("helloAdmin")
    @PreAuthorize("hasAnyRole('admin')")
    public String helloAdminUser() {
        return "hello 拥有admin角色的用户";
    }
}

第四个例子,基于 jdbc 的用户认证

从数据库 mysql 中获取用户的身份信息(用户名称,密码,角色)
1)在 spring security 框架对象用户信息的表示类是 UserDetails.
UserDetails 是一个接口,高度抽象的用户信息类(相当于项目中的
User 类)
User 类:是 UserDetails 接口的实现类, 构造方法有三个参数:
username,password, authorities
需要向 spring security 提供 User 对象, 这个对象的数据来自数据库
的查询。
2)实现 UserDetailsService 接口,
重写方法 UserDetails loadUserByUsername(String username)
在方法中获取数据库中的用户信息, 也就是执行数据库的查询,条
件是用户名称。

@Configuration
public class ApplicationConfig {
    @Autowired
    private DataSource dataSource;
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService jdbcUserDetailService() {
        PasswordEncoder encoder = passwordEncoder();
        JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
        if (!manager.userExists("admin")){
            manager.createUser(User.withUsername("admin").
                    password(encoder.encode("admin"))
                    .roles("ADMIN","USER","MAMAGER").build());
        }
        if (!manager.userExists("zhangsan")) {

            manager.createUser(User.withUsername("zhangsan")
                    .password(encoder.encode("123")).
                            roles("USER").build());
        }
        if (!manager.userExists("lisi")) {
            manager.createUser(User.withUsername("lisi")
                    .password(encoder.encode("123"))
                    .roles("MANAGER","USER").build());
        }
        return manager;
    }
}
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsService userDetailsService;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.userDetailsService(userDetailsService);
    }

}

第三章 基于角色的权限

3.1 认证和授权

authentication:认证, 认证访问者是谁。 一个用户或者一个其他系统是不是当前要访问的系统中的有效用户。
authorization:授权, 访问者能做什么

3.2 RBAC 是什么?

RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。
在这里插入图片描述
权限:能对资源的操作, 比如增加,修改,删除,查看等等。
角色:自定义的, 表示权限的集合。一个角色可以有多个权限。
RBAC 设计中的表:

  1. 用户表: 用户认证(登录用到的表)
    用户名,密码,是否启用,是否锁定等信息。
  2. 角色表:定义角色信息
    角色名称, 角色的描述。
  3. 用户和角色的关系表: 用户和角色是多对多的关系。
    一个用户可以有多个角色, 一个角色可以有多个用户。
  4. 权限表, 角色和权限的关系表
    角色可以有哪些权限。

3.3 spring specurity 中认证的接口和类

1) UserDetails:接口,表示用户信息的。
boolean isAccountNonExpired(); 账号是否过期
boolean isAccountNonLocked();账号是否锁定
boolean isCredentialsNonExpired();证书是否过期
boolean isEnabled();账号是否启用
Collection<? extends GrantedAuthority> getAuthorities(); 权限集合
User 实现类
org.springframework.security.core.userdetails.User
可以:自定义类实现 UserDetails 接口,作为你的系统中的用户类。这
个类可以交给 spring security 使用。
2) UserDetailsService 接口:
主要作用:获取用户信息,得到是 UserDetails 对象。一般项目中都需
要自定义类实现这个接口,从数据库中获取数据。
一个方法需要实现:
UserDetails loadUserByUsername(String var1) :根据用户名称,获取用
户信息(用户名称,密码,角色结合,是否可用,是否锁定等信息)
UserDetailsService 接口的实现类:

  1. InMemoryUserDetailsManager:在内存中维护用户信息。
    优点:使用方便。
    缺点:数据不是持久的。系统重启后数据恢复原样。
  2. JdbcUserDetailsManager :用户信息存放在数据库中,底层使用
    jdbcTemplate 操作数据库。 可以 JdbcUserDetailsManager 中的方法完
    成用户的管理
    createUser : 创建用户
    updateUser:更新用户
    deleteUser:删除用户
    userExists:判断用户是否存在
    数据库文件:
org.springframework.security.core.userdetails.jdbc

users.ddl 文件

create table users(
	username varchar_ignorecase(50) not null primary key,
	password varchar_ignorecase(500) notnull,
	enabled boolean not null
);
create table authorities (
	username varchar_ignorecase(50) not null,
	authority varchar_ignorecase(50) not null,
	constraint fk_authorities_users foreign key(username) references
	users(username)
);
create unique index ix_auth_username on authorities(username,authority);

3.4 定义用户,角色,角色关系表

用户信息表 sys_user:
在这里插入图片描述
sys_role:角色表
在这里插入图片描述
sys_user_role: 用户-角色关系表
在这里插入图片描述

3.5 pom 依赖

3.6 创建实体类

定义用户信息类,实现 spring security 框架中的 UserDetails

@TableName(value ="sys_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUser implements Serializable, UserDetails {

    @TableId(type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
    private String realname;
    private boolean isenable;
    private boolean islock;
    private boolean iscredentials;
    @TableField(exist = false)
    private boolean isExpired;

    private LocalDate createtime;
    private LocalDate logintime;


    @TableField(exist = false)
    private List<GrantedAuthority> authorities;



    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return isExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return islock;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return iscredentials;
    }

    @Override
    public boolean isEnabled() {
        return isenable;
    }
}

SysRole 类:

@TableName(value ="sys_role")
@Data
public class SysRole implements Serializable {
    /**
     * 
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 
     */
    private String rolename;

    /**
     * 
     */
    private String rolememo;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

SysUserRole类

@TableName(value ="sys_user_role")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUserRole implements Serializable {


    /**
     *
     */
    @TableId
    private Integer userid;

    /**
     *
     */
    private Integer roleid;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

3.7 Mapper 类

3.8 创建 UserDetatilsService 接口的实现类

根据 username 从数据查询账号信息 SysUser 对象。
在根据 user 的 id, 去查 Sys_Role 表获取 Role 信息

@Configuration
public class JdbcUserDetailsService implements UserDetailsService {
    @Autowired
    SysUserService sysUserService;
    @Autowired
    SysRoleService sysRoleService;
    @Autowired
    SysUserRoleService sysUserRoleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUser::getUsername,username);
        SysUser sysUser = sysUserService.getOne(wrapper);
        sysUser.setExpired(true);
        List<GrantedAuthority> authorities = new ArrayList<>();
        if (sysUser != null) {
            List<SysUserRole> sysUserRoles = sysUserRoleService.query().eq("userid", sysUser.getId()).list();
            sysUserRoles.
                    forEach((sysUserRole -> {
                Integer roleid = sysUserRole.getRoleid();
                        SysRole sysRole = sysRoleService.getById(roleid);
                        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+sysRole.getRolename());
                        authorities.add(authority);
            }));
            sysUser.setAuthorities(authorities);
            return sysUser;
        }
        return null;
    }
}

3.9 创建 Controller

  1. IndexController : 转发到首页 index.html
@Controller
public class IndexController {
    @GetMapping("/index")
    public String toIndexHtml() {
        return "forward:/index.html";
    }
}
  1. MyController
@RestController
public class MyController {

    @GetMapping(value = "/access/user",produces = "text/html;charset=utf-8")
    public String sayUser() {
        return "lisi是user角色";
    }
    @GetMapping(value = "/access/read",produces = "text/html;charset=utf-8")
    public String sayRead() {
        return "zhangsan是read角色";
    }
    @GetMapping(value = "/access/admin",produces = "text/html;charset=utf-8")
    public String sayAdmin() {
        return "admin是admin角色";
    }
}

3.10 继承 WebSecurityConfigurerAdapter

注入自定义的 UserDetatilsService

@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/index","/mylogin.html","/login","/error.html").permitAll()
                .antMatchers("/index.html").permitAll()
                .antMatchers("/access/user").hasAnyRole("USER")
                .antMatchers("/access/admin").hasAnyRole("ADMIN")
                .antMatchers("/access/read").hasAnyRole("READ")

                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/mylogin.html")
                .loginProcessingUrl("/login")
                .failureUrl("/error.html")
                .and()
                .csrf().disable();
    }
}

3.14 默认的登录页面

  1. 访问地址 /login
  2. 请求方式 post
  3. 请求参数
    用户名 username
    密码 password

第四章 自定义登录和验证码的使用

4.1 完善自定义登录页面

  1. 创建页面
    登录页面 resources/static/mylogin.html
    action: /login 可以自定义
    method:post 这个是一定的。
    提交表单的name参数: username ,password 可以自定义
http.usernameParameter("myname")
http.passwordParameter("mypwd")
  1. 设置自定义登录参数
    重写 protected void configure(HttpSecurity http) 方法
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        // ①:设置访问的白名单,无需登录验证就可以访问的地址
                .antMatchers("/index","/mylogin.html","/login","/error.html").permitAll()
                .antMatchers("/index.html").permitAll()
                .antMatchers("/access/user").hasAnyRole("USER")
                .antMatchers("/access/admin").hasAnyRole("ADMIN")
                .antMatchers("/access/read").hasAnyRole("READ")

                .anyRequest().authenticated()
                .and()
                .formLogin()
                //②:指定登录页面,登录的 uri 地址
                .loginPage("/mylogin.html")
                .loginProcessingUrl("/login")
                //③:指定登录错误的提示页面
                .failureUrl("/error.html")
                .and()
                //3.关闭跨域访问的安全设置
                .csrf().disable();
    }

4.2 ajax 登录方式

上面的登录方式是 基于表单 form 的。 对于现在的前后端分类的方式不适合。 如果要使用前后端分离, 一般使用 json 作为数据的交互格式。 需要使用另一种方式才可以。
ajax 方式,用户端发起请求, springsecurity 接收请求验证用户的用户名和密码,把验证结果返回给请求方(json 数据)

尚硅谷内容

3.7 注解使用

3.7.1 @Secured

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。使用注解先要开启注解功能!
@EnableGlobalMethodSecurity(securedEnabled=true)

@SpringBootApplication 
@EnableGlobalMethodSecurity(securedEnabled=true) 
public class DemosecurityApplication {
	public static void main(String[] args){
		SpringApplication.run(DemosecurityApplication.class, args);
	}
}

在控制器方法上添加注解

// 测试注解: 
@RequestMapping("testSecured")
@ResponseBody 
@Secured({"ROLE_normal","ROLE_admin"}) 
public String helloUser() {
	return "hello,user";
}
@Secured({"ROLE_normal","ROLE_管理员"})

3.7.2 @PreAuthorize

先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用户的 roles/permissions 参数传到方法中。

@RequestMapping("/preAuthorize")
@ResponseBody
//@PreAuthorize("hasRole('ROLE_管理员')")
@PreAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("preAuthorize");
return "preAuthorize";
}

3.7.3 @PostAuthorize

先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值
的权限.

@RequestMapping("/testPostAuthorize")
@ResponseBody
@PostAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
	System.out.println("test--PostAuthorize");
	return "PostAuthorize";
}

3.7.4 @PostFilter

@PostFilter :权限验证之后对数据进行过滤 留下用户名是 admin1 的数据表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素

@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<UserInfo> getAllUser(){
ArrayList<UserInfo> list = new ArrayList<>();
list.add(new UserInfo(1l,"admin1","6666"));
list.add(new UserInfo(2l,"admin2","888"));
return list;
}

3.7.5 @PreFilter

PreFilter: 进入控制器之前对数据进行过滤

@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> list){
	list.forEach(t-> {
	System.out.println(t.getId()+"\t"+t.getUsername());
	});
	return list;
}

3.7.6 权限表达式

权限表达式

3.8 基于数据库的记住我

3.8.1 创建表

CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.8.2 实现 WebSecurityConfigurerAdapter中的方法

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                .defaultSuccessUrl("/hello")
                .and()
                .authorizeRequests()
                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated()
                .and()
                //记住我配置
                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                .userDetailsService(userDetailsService)
                .tokenValiditySeconds(60)
                .and()
                .csrf().disable();
    }

3.8.3声明 PersistentTokenRepository 对象

  @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        repository.setDataSource(dataSource);
        // 自动创建表,第一次执行会创建,以后要执行就要删除掉!
//        repository.setCreateTableOnStartup(true);
        return repository;
    }

3.8.4 HTML页面

 <form action="/login" method="post">
        username:<input type="text" name="username"><br>
        password:<input type="password" name="password"><br>
        记住我:<input type="checkbox" name="remember-me" title="记住密码"><br>
        <input type="submit">
    </form>

3.9 测试

3.10 CSRF

3.10.1 CSRF 理解

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-clickattack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用
程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护。

3.10.2 案例

在登录页面添加一个隐藏域:

<input type="hidden"th:if="${_csrf}!=null"th:value="${_csrf.token}"name="_csrf"/>

关闭安全配置的类中的 csrf

// http.csrf().disable();

3.10.3 Spring Security 实现 CSRF 的原理:

  1. 生成 csrfToken 保存到 HttpSession 或者 Cookie 中。
  2. 请求到来时,从请求中提取 csrfToken,和保存的 csrfToken 做比较,进而判断当
    前请求是否合法。主要通过 CsrfFilter 过滤器来完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值