参考
B站:https://www.bilibili.com/video/BV15a411A7kP
官网:https://spring.io/projects/spring-security
简介
Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是
Spring 家族中的 成员。Spring Security 基于 Spring 框架,提供了一套 Web
应用安全性的完整解决方 案。
正如你可能知道的关于安全方面的两个主要区域是“认证”和“授权”(或者访问控
制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权
(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问
该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认
证过程。通俗点说就是系统认为用户是否能登录
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户
所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以
进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的
权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
SpringSecurity 特点:
- 和 Spring 无缝整合。
 
- 全面的权限控制。
 
- 专门为 Web 开发而设计。
- 旧版本不能脱离 Web 环境使用。
 
- 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独
引入核心模块就可以脱离 Web 环境。
 
 
- 重量级。
 
入门示例
pom.xml
1 2 3 4
   | <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-security</artifactId> </dependency>
   | 
 
Controller测试
1 2 3 4 5 6 7 8
   | @RestController @RequestMapping("/test") public class TestController {     @GetMapping("hello")     public String hello() {         return "hello security";     } }
   | 
 
启动测试,需要密码登录,默认username为user,密码为控制台打印的内容
启动流程原理
UserDetailsService
PasswordEncoder
Web
设置登录系统的账号、密码
方式一
1 2 3 4 5
   | spring:   security:     user:       name: root       password: root
   | 
 
方式二
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {     @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();         String password = encoder.encode("123");         auth.inMemoryAuthentication().withUser("root").password(password).roles("admin");     }
      @Bean     PasswordEncoder passwordEncoder() {         return new BCryptPasswordEncoder();     } }
   | 
 
方式三
step1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | @Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
      @Autowired     private UserDetailsService userDetailsService;
      @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());     }
      @Bean     PasswordEncoder passwordEncoder() {         return new BCryptPasswordEncoder();     } }
   | 
 
step2
1 2 3 4 5 6 7 8
   | @Service("userDetailsService") public class MyUserDetailsService implements UserDetailsService {     @Override     public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {         List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");         return new User("root",new BCryptPasswordEncoder().encode("456"),auths);     } }
  | 
 
加入数据库
建立数据库
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
   | create table users(  id bigint primary key auto_increment, username varchar(20) unique not null, password varchar(100) );
  insert into users values(1,'张 san','$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na');
  insert into users values(2,'李 si','$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na'); create table role( id bigint primary key auto_increment, name varchar(20) );insert into role values(1,'管理员'); insert into role values(2,'普通用户'); create table role_user( uid bigint, rid bigint ); insert into role_user values(1,1); insert into role_user values(2,2); create table menu( id bigint primary key auto_increment, name varchar(20), url varchar(100), parentid bigint, permission varchar(20) ); insert into menu values(1,'系统管理','',0,'menu:system'); insert into menu values(2,'用户管理','',0,'menu:user'); create table role_menu( mid bigint, rid bigint ); insert into role_menu values(1,1);insert into role_menu values(2,1); insert into role_menu values(2,2);
 
   | 
 
pom.xml
1 2 3 4 5 6 7 8 9 10
   | <dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId> </dependency>
  <dependency>     <groupId>com.baomidou</groupId>     <artifactId>mybatis-plus-boot-starter</artifactId>     <version>3.4.2</version> </dependency>
   | 
 
application.yml
1 2 3 4 5 6
   | spring:   datasource:     driver-class-name: com.mysql.cj.jdbc.Driver     url: jdbc:mysql://localhost:3306/demo     username: root     password: root
   | 
 
entity
1 2 3 4 5 6 7
   | @Data @AllArgsConstructor public class Users {     private Long id;     private String username;     private String password; }
   | 
 
mapper
1 2 3
   | @Mapper public interface UsersMapper extends BaseMapper<Users> { }
   | 
 
user配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | @Service("userDetailsService") public class MyUserDetailsService implements UserDetailsService {     @Autowired     private UsersMapper usersMapper;
      @Override     public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {         
          QueryWrapper<Users> wrapper = new QueryWrapper<>();                  wrapper.eq("username", s);         Users users = usersMapper.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
   | @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").permitAll()              .anyRequest().authenticated()             .and().csrf().disable(); }
   | 
 
基于角色或权限进行访问控制
hasAuthority
hasAnyAuthority
hasRole
hasAnyRole
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | @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").permitAll()                                                                               .antMatchers("/test/index").hasRole("sale")                          .antMatchers("/test/index").hasAnyRole("sale,dba")             .anyRequest().authenticated()             .and().csrf().disable(); }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {     
      QueryWrapper<Users> wrapper = new QueryWrapper<>();          wrapper.eq("username", s);     Users users = usersMapper.selectOne(wrapper);          if (users == null) {         throw new UsernameNotFoundException("用户名不存在!");     }
      List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");          return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths); }
   | 
 
403页面
1 2
   |  http.exceptionHandling().accessDeniedPage("/unauth.html");
 
  | 
 
注解
@Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
使用注解先要开启注解功能!
@EnableGlobalMethodSecurity(securedEnabled=true)
1 2 3 4 5
   | @GetMapping("/update") @Secured({"ROLE_sale","ROLE_manager"}) public String update(){     return "hello update"; }
  | 
 
@PreAuthorize
注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用 户的
roles/permissions 参数传到方法中。
先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled
= true)
1 2 3 4 5 6
   | @GetMapping("/update")
  @PreAuthorize("hasAnyAuthority('admins')") public String update(){     return "hello update"; }
  | 
 
@PostAuthorize
使用并不多,在方法执行后再进行权限验证,适合验证带有返回值
的权限.
先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled
= true)
先执行,后验证权限,虽无权限,但还是输出了
1 2 3 4 5 6 7 8
   | @GetMapping("/update")
 
  @PostAuthorize("hasAnyAuthority('admin')") public String update(){     System.out.println("update...");     return "hello update"; }
  | 
 
@PostFilter
权限验证之后对数据进行过滤 留下用户名是 admin1 的数据 表达式中的
filterObject 引用的是方法返回值 List 中的某一个元素
@PreFilter
进入控制器之前对数据进行过滤
注销
1 2
   |  http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
 
  | 
 
记住我
原理
建表
1 2 3 4 5 6 7 8
   | 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;
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12
   | @Autowired private DataSource dataSource;
 
  @Bean public PersistentTokenRepository persistentTokenRepository() {     JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();     jdbcTokenRepository.setDataSource(dataSource);               return jdbcTokenRepository; }
   | 
 
1 2 3 4
   |  .and().rememberMe().tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60) .userDetailsService(userDetailsService)
 
  | 
 
1 2 3 4 5 6
   | <form action="/user/login" method="post">     用户名:<input type="text" name="username"/><br/>     密码:<input type="password" name="password"/><br/>     <input type="checkbox" name="remember-me"/>自动登录<br/>     <input type="submit" value="login"/> </form>
   | 
 
CSRF
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
方法进行防护。
默认是开启的
总结
简单的学习,原理部分没有深究,还有部分不完整,如结合数据库权限,当然还有分布式没有学习,目前分布式还有太多前期知识为准备,所以没有太注重这方面