Spring Security
Spring Security (spring.io)
Spring Security核心功能
关于安全方面的两个主要区域是认证和授权(或者访问控制),这两点也是Spring Security核心功能
- 用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
- 用户授权:验证谋而用户是否有权限执行某一操作。在一个系统中,不同用户所具有的权限是不同的。
SpringSecurity特点
和Spring无缝整合。
全面的权限控制。
专门为Web开发而设计。
重量级。
Shiro
Apache旗下的轻量级权限控制框架。
特点:
- 轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
- 通用性。
- 好处:不局限于Web环境,可以脱离Web环境使用。
- 缺陷:在Web环境下一些特定的需求需要手动编写代码定制。
和Shiro的区别
Shiro - 轻量级
SpringSecurity - 重量级
相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有 Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。
自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。 因此,一般来说,常见的安全管理技术栈的组合是这样的:
测试security
导入对应security坐标和web坐标
1 2 3 4 5 6 7 8 9 10
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
|
编写controller类
1 2 3 4 5 6 7 8 9
| @RestController @RequestMapping("/test") public class TestController {
@GetMapping("hello") public String hello(){ return "hello security"; } }
|
访问页面
设置端口为8080
启动springboot项目访问页面链接会进入到security的拦截界面不能直接访问hello
需要进行登录
默认的用户名:user
密码在项目启动的时候在控制台会打印,注意每次启动的时候密码都回发生变化!
登录完成才能访问到对应的信息
SpringSecurity 基本原理
SpringSecurity 本质是一个过滤器链:
在启动springboot项目的时候可以看见一整条过滤器链(每个过滤器按顺序执行完成后才能正常启动)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter org.springframework.security.web.context.SecurityContextPersistenceFilter org.springframework.security.web.header.HeaderWriterFilter org.springframework.security.web.csrf.CsrfFilter org.springframework.security.web.authentication.logout.LogoutFilter org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter org.springframework.security.web.savedrequest.RequestCacheAwareFilter org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter org.springframework.security.web.authentication.AnonymousAuthenticationFilter org.springframework.security.web.session.SessionManagementFilter org.springframework.security.web.access.ExceptionTranslationFilter org.springframework.security.web.access.intercept.FilterSecurityInterceptor
|
知识补充
过滤器 和 拦截器的 6个区别,别再傻傻分不清了_程序员小富的博客-CSDN博客_过滤器和拦截器
过滤器和拦截器的区别_至今没搞明白的博客-CSDN博客_过滤器和拦截器的区别
过滤器(Filter)和拦截器(Interceptor)
Spring 的拦截器与 Servlet 的Filter 有相似之处,比如二者都是 AOP编程思想的体现, 都能实现权限检查、日志记录等。不同的是:
- 使用范围不同:Filter 是 Servlet 规范规定的,只能用于 Web 程序中。而拦截器既 可以用于 web程序,也可以用于 Application、Swing 程序中。
- 口 规范不同:Filter 是在 Servlet 规范中定义的,是Serviet 容器支持的。而拦截器是 在 Spring 容器内的,是 Spring 框架支特的。
- 使用的资源不同:同其他的代码块一样,拦截器也是一个Spring 的组件,归 Spring 管理,配置在 Spring 文件中,因此能使用 Spring 里的任何资源、对象,例如 Sersice 对象、数据源、事务管理等,通过1oC 注入到拦截器即可:而Filter 则不能。
- 深度不同:Filter 在只在 Servlet 前后起作用。而拦截器能够深入到方法前后、异常 抛出前后等,因此拦我器的使用具有更大的弹性。所以在 Spring 构架的程序中, 要优先使用拦截器.
1、实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。
2、使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
3、触发时机不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
4、拦截的请求范围不同
过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。
5、注入Bean情况不同
这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。
6、控制执行顺序不同
过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行
三个重要的过滤器
- FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。
super.beforeInvocation(fi) 表示查看之前的filter 是否通过。
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务
两个重要接口
UserDetailsService接口讲解
当什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
继承接口实现从数据库匹配用户账号密码
如果需要自定义逻辑时,只需要实现UserDetailsService接口即可
返回值是UserDetail
这个类是系统默认的用户“主体” :包含了用户在数据库字段的各种信息
PasswordEncoder 接口讲解
1 2 3 4 5 6 7 8
| String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) { return false; }
|
web权限方案
设置登录的用户名和密码
1.通过配置application文件
1 2
| spring.security.user.name=02 spring.security.user.password=nian0209
|
使用配置的账号密码可以登录
2.通过配置类
- 直接创建BCryptPasswordEncoder对象在访问的时候会出现对象匹配不上的问题报错无法访问
- 有两种解决办法
- 第一种:返回一个PasswordEncoder对象将其注入到spring容器中
- 修改代码:auth.inMemoryAuthentication().passwordEncoder(passwordEncoder).withUser(“02”).password(password).roles(“admin”);
在Spring Security5.7版本中将弃用WebSecurityConfigurerAdapter接口类
Spring Security 弃用WebSecurityConfigurerAdapter_鲨雕东西的博客-CSDN博客
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean PasswordEncoder password(){ return new BCryptPasswordEncoder(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String password = passwordEncoder.encode("nian0209"); auth.inMemoryAuthentication().withUser("02").password(password).roles("admin");
}
}
|
3.自定义编写实现类—–继承UserDetailsService
这里并没有连接数据库测试,没有对应的用户账号密码匹配,只是模拟了用户登录,而且只有密码匹配规则,所以在登录的时候,只要密码输入正确即可登录
1 2 3 4 5 6 7 8 9 10
| @Service("UserDetailsService") public class MyUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User("02",new BCryptPasswordEncoder().encode("nian0209"),auths); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Bean PasswordEncoder password(){ return new BCryptPasswordEncoder(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); }
}
|
配置数据库访问
配置数据库访问相关坐标
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency>
|
配置数据源
1 2 3 4 5 6
| server.port=8080
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=?
|
实体类
1 2 3 4 5 6
| @Data public class Users { private Integer id; private String username; private String password; }
|
mapper接口
1 2 3 4
| @Mapper public interface UserMapper extends BaseMapper<Users> {
}
|
service层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Service("UserDetailsService") public class MyUserDetailService implements UserDetailsService {
@Autowired private UserMapper userMapper;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<Users> wrapper = new QueryWrapper<>(); wrapper.eq("username",username); Users users = userMapper.selectOne(wrapper); if (users == null){ throw new UsernameNotFoundException("用户名不存在!"); } List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Bean PasswordEncoder password(){ return new BCryptPasswordEncoder(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); }
}
|
BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析器。
BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10.
设置自定义页面
编写自定义页面连接方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); }
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll() .and().authorizeRequests() .antMatchers("/","/test/hello","/user/login").permitAll() .anyRequest().authenticated() .and().csrf().disable(); } }
|
编写登录页面
用户名和密码的name必须和实体类中定义的一样
1 2 3 4 5 6 7
| <form action="/user/login" method="post"> 用户名:<input type="text" name="username"> <br/> 密码: <input type="text" name="password"> <br/> <input type="submit" value="login"> </form>
|
基于角色或权限进行访问控制
hasAuthority 方法
如果当前的主体具有指定的权限,则返回 true,否则返回false
在配置类中设置有哪些权限可以访问当前访问地址
.antMatchers(“/test/index”).hasAnyAuthority(“admins”)//设置权限为admins才问/test/index
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll() .and().authorizeRequests() .antMatchers("/","/test/hello","/user/login").permitAll() .antMatchers("/test/index").hasAuthority("admins") .anyRequest().authenticated() .and().csrf().disable(); }
|
在UserDetailService,把返回User对象设置权限
AuthorityUtils.commaSeparatedStringToAuthorityList(“admins”)设置权限角色为:admins
1 2
| List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
|
hasAnyAuthority方法
如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true.
一个用户拥有两个角色且其中一个角色能访问这个地址,就可以访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll() .and().authorizeRequests() .antMatchers("/","/test/hello","/user/login").permitAll() .antMatchers("/test/index").hasAnyAuthority("admins","manager") .anyRequest().authenticated() .and().csrf().disable(); }
|
hasRole 方法
如果用户具备给定角色就允许访问,否则出现403。
如果当前主体具有指定的角色,则返回true。
给用户设置多个角色
设置角色名前带ROLE_前缀
1 2
| List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
|
设置访问权限
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll() .and().authorizeRequests() .antMatchers("/","/test/hello","/user/login").permitAll() .antMatchers("/test/index").hasRole("admins") .anyRequest().authenticated() .and().csrf().disable(); }
|
hasAnyRole
表示用户具备任何一个条件都可以访问。
设置可访问的角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll() .and().authorizeRequests() .antMatchers("/","/test/hello","/user/login").permitAll() .antMatchers("/test/index").hasAnyRole("admins","role") .anyRequest().authenticated() .and().csrf().disable(); }
|
用户service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Service("UserDetailsService") public class MyUserDetailService implements UserDetailsService {
@Autowired private UserMapper userMapper;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<Users> wrapper = new QueryWrapper<>(); wrapper.eq("username",username); Users users = userMapper.selectOne(wrapper); if (users == null){ throw new UsernameNotFoundException("用户名不存在!"); } List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale"); return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths); } }
|
自定义403没有权限访问页面
需要有对应的页面
403是因为角色没有对应权限访问该页面所以出403错误
1 2
| http.exceptionHandling().accessDeniedPage("/unauth.html");
|
注解的使用
hasRole(role) 用户拥有指定的角色权限时返回true
hasAnyRole([role1,role2]) 用户拥有任意一个指定的角色权限时返回true
hasAuthority(authority) 用户拥有指定的权限时返回true
hasAnyAuthority([authority1,authority2]) 用户拥有任意一个指定的权限时返回true
@Secured
@Secured(“ROLE_VIEWER”) 表示只有拥有ROLE_VIEWER角色的用户,才能够访问getUsername()方法。
@Secured({ “ROLE_DBA”, “ROLE_ADMIN” }) 表示用户拥有”ROLE_DBA”, “ROLE_ADMIN” 两个角色中的任意一个角色,均可访问
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
使用注解先要开启注解功能!
在启动类或者配置类上添加启动注解开发的注解
@EnableGlobalMethodSecurity(securedEnabled=true)
1 2 3 4 5 6 7 8 9
| @EnableGlobalMethodSecurity(securedEnabled=true) @SpringBootApplication public class SpringsecurityApplication {
public static void main(String[] args) { SpringApplication.run(SpringsecurityApplication.class, args); }
}
|
在controller层使用注解开发
1 2 3 4 5
| @GetMapping("update") @Secured({"ROLE_sale","manager"}) public String update(){ return "hello update"; }
|
在userServiceDetail中设置用户角色
1 2
| List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
|
@PreAuthorize
@PreAuthorize:注解适合进入方法前的权限验证,
@PreAuthorize可以将登录用户的roles/permissions参数传到方法中。
先开启注解功能: @EnableGlobalMethodSecurity(prePostEnabled = true)
1 2 3 4 5 6 7 8 9
| @EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true) @SpringBootApplication public class SpringsecurityApplication {
public static void main(String[] args) { SpringApplication.run(SpringsecurityApplication.class, args); }
}
|
在controller层使用注解开发
1 2 3 4 5
| @GetMapping("update") @PreAuthorize("hasAnyAuthority('admins')") public String update(){ return "hello update"; }
|
@PostAuthorize
先开启注解功能: @EnableGlobalMethodSecurity(prePostEnabled = true)
和@PreAuthorize的开启注解一样,使用也类似
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.
方法执行完毕再进行校验
1 2 3 4 5 6
| @GetMapping("update") @PostAuthorize("hasAnyAuthority('menu:system')") public String update(){ System.out.println("update....."); return "hello update"; }
|
@PostFilter
对返回的数据进行过滤
@PostFilter :权限验证之后对数据进行过滤 留下用户名是admin1的数据
表达式中的 filterObject 引用的是方法返回值List中的某一个元素
1 2 3 4 5 6 7 8 9 10
| @GetMapping("getAll") @PreAuthorize("hasAnyAuthority('admins')") @PostFilter("filterObject.username == 'admin1'") public List<Users> getAllUser() { ArrayList<Users> list = new ArrayList<>(); list.add(new Users(12,"admin1","1111")); list.add(new Users(22, "admin2", "888")); System.out.println(list); return list; }
|
@PreFilter
@PreFilter: 进入控制器之前对数据进行过滤
将id能整除二的保留,其他过滤
1 2 3 4 5 6 7 8 9
| @RequestMapping("getTestPreFilter") @PreAuthorize("hasRole('ROLE_管理员')") @PreFilter(value = "filterObject.id%2==0") public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> list) { list.forEach(t -> { System.out.println(t.getId() + "\t" + t.getUsername()); }); return list; }
|
用户注销
登录成功后跳转到success页面,在用户登录的情况下,可以访问该用户对应角色后权限能访问的页面,退出登录后,无法访问之前需要相应权限或角色的页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Override protected void configure(HttpSecurity http) throws Exception {
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll(); http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/success.html").permitAll() .and().authorizeRequests() .antMatchers("/", "/test/hello", "/user/login").permitAll() .antMatchers("/test/index").hasRole("sale")
.anyRequest().authenticated() .and().csrf().disable(); } }
|
基于安全登录的数据库记住我
设置登录页面
记住我的name标签必须为:remember-me
设置记住我复选框选项
1 2 3 4 5 6 7 8 9
| <form action="/user/login" method="post"> 用户名:<input type="text" name="username"> <br/> 密码: <input type="text" name="password"> <br/> <input type="checkbox" name="remember-me">自动登录 <br/> <input type="submit" value="login"> </form>
|
config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| @Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Autowired private DataSource dataSource;
@Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository; }
@Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); }
@Override protected void configure(HttpSecurity http) throws Exception {
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll(); http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/success.html").permitAll() .and().authorizeRequests() .antMatchers("/", "/test/hello", "/user/login").permitAll() .antMatchers("/test/index").hasRole("sale")
.anyRequest().authenticated() .and().rememberMe().tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60) .userDetailsService(userDetailsService) .and().csrf().disable(); } }
|
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方法进行防护
预防CSRF跨站攻击
不够明白
防止 ASP.NET MVC 中的跨站点请求伪造 (CSRF) 攻击 | Microsoft Docs
在登录页面添加一个隐藏域:
<input type=”hidden”th:if=”${_csrf}!=null”th:value=”${_csrf.token}”name=”_csrf”/>
关闭安全配置的类中的csrf // http.csrf().disable();
微服务权限授权
待更~
等待springcloud