SpringSecurity入门

SpringSecurity

1. 快速入门

  • pom.xml中要有这个配置

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  • 写一个简单的controller

    @RestController
    @RequestMapping("/test")
    public class TestController {
    
        @GetMapping("hello")
        public String add(){
            return "hello";
        }
    }
    
  • 运行程序,控制台会出现密码
    SpringSecurity入门

  • 在浏览器中输入路径,会发现出现了这个页面

SpringSecurity入门

  • 输入用户名user,以及刚才的密码即可登录,跳转到我们真正的页面

2. 两个重要的接口

  • UserDetailsService接口:查询数据库用户名和密码过程
    1. 创建类继承UsernamePasswordAuthenticationFilter,重写三个方法
    2. 创建类实现UserDetailService,编写查询数据过程,返回User对象,这个User对象是安全框架提供的对象。
  • PasswordEncoder接口:实现密码加密,用于返回User对象里面的密码加密

3. 认证和授权

3.1认证入门(3种方式)

(1)通过配置文件

spring.security.user.name=root
spring.security.user.password=sasa

(2)通过配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //加密
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String password = bCryptPasswordEncoder.encode("sasa");
      auth.inMemoryAuthentication().withUser("root").password(password).roles("admin");
    }

    //对象加密需要用到这个对象,因此要new出来
    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }
}

(3)自定义编写实现类(最常用)

  • 创建配置类,设置使用哪个UserDetailService实现类
  • 编写实现类,返回User对象,对象中有用户名、密码和操作权限
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }

    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }
}
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //模拟数据库查询
        List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User("root",new BCryptPasswordEncoder().encode("sasa"),auth);
    }
}

(4)认证的实例(MyBatisPlus)

  • 引入依赖

    <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>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    
  • 创建数据库

    ​ 创建users表,有id、username、password第三个字段

  • 创建users表对应的实体类

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Users {
        private Integer id;
        private String username;
        private String password;
    }
    
  • 配置数据库

    spring.datasource.url=jdbc:mysql://localhost:3306/springsecuritydemo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=sasa
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    
  • 整合MyBatisPlus

    @Repository
    public interface UserMapper extends BaseMapper<Users> {}
    
  • 在MyUserDetailsService调用mapper方法查询数据库进行用户认证

    @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 user = userMapper.selectOne(wrapper);
            //判断
            if(user == null){
                throw new UsernameNotFoundException("用户名不存在");
            }
            List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
            return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auth);
        }
    }
    
  • 在启动类上开启扫描

    @SpringBootApplication
    @MapperScan("com.nnutyh.springsecurity.mapper")
    public class SpringsecurityApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringsecurityApplication.class, args);
        }
    
    }
    

3.2页面且无需认证即可访问

  • 在配置类中实现相关的配置

        @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();    //关闭csrf防护
        }
    
  • 页面创建

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="/user/login" method="post">
            用户名:<input type="text" name="username">
            <br>
            密码:<input type="password" name="password">
            <br>
            <input type="submit" value="登录">
        </form>
    </body>
    </html>
    
  • 编写Controller

    @RestController
    @RequestMapping("/test")
    public class TestController {
    
        @GetMapping("/hello")
        public String add(){
            return "hello";
        }
    
        @GetMapping("/index")
        public String index(){
            return "index";
        }
    }
    

3.3基于角色或权限进行访问控制

授权

  1. hasAuthority方法:如果当前的主体具有指定的权限,返回true,否则返回false

    • 在配置类中设置当前访问地址有哪些权限

      //当前登录用户,只有具有admins权限才能访问这个路径
      http.formLogin().antMatchers("/test/index").hasAuthority("admins")
      
    • 在UserDetailService,把返回的 User对象设置权限

      //这里的admins要与config中相同,才能访问		
      List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
              
      return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auth);
      
  2. hasAnyAuthority方法:多个权限,多个角色,用这个

    • 示例

      http.formLogin()
          .antMatchers("/test/index").hasAnyAuthority("admins","manager")
      
    • 在UserDetailService,与上同

  3. hasRole方法:如果用户具备给定角色就允许访问,否则403

    ​ 角色与权限的关系是多对多

    • 示例

      http.formLogin().antMatchers("test/index").hasRole("sale")
      
    • 在UserDetailService,有区别,因为在源码中,创建Role时,会在你输入之前加上ROLE_,即输入的sale,最终变化为ROLE_sale

      List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
              
      return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auth);
      
  4. hasAnyRole方法:表示用户具备任何一个条件都可以访问(与上同)

3.4 自定义403页面

  1. 创建页面

  2. 在配置类中

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.exceptionHandling().accessDeniedPage("/unauth.html");
        }
    

3.5 注解的使用

  1. @Secured

    判断用户是否具有角色,注意,要加前缀ROLE_

    ​ 使用条件:在启动类上添加@EnableGlobalMethodSecurity(securedEnabled=true)

    @GetMapping("/update")
    @Secured({"ROLE_admin","ROLE_sale"})
    public String update(){
        return "hello,update";
    }
    
  2. @PreAuthorize

    进入方法之前进行权限验证

    ​ 使用条件:在启动类上添加@EnableGlobalMethodSecurity(prePostEnabled=true)

    @GetMapping("/update")
    @PreAuthorize("hasAnyAuthority('admins')")
    public String update(){
        return "hello,update";
    }
    
  3. @PostAuthorize

    方法执行之后进行验证

    ​ 使用条件:在启动类上添加@EnableGlobalMethodSecurity(prePostEnabled=true)

    ​ 没有权限,执行方法体,返回403

    @GetMapping("/update")
    @PostAuthorize("hasAnyAuthority('admins')")
    public String update(){
        return "hello,update";
    }
    
  4. @PostFilter

    方法返回数据进行过滤,比如只返回用户为admin1的数据,其余的不返回

  5. @PreFilter

    传入方法数据进行过滤,比如只传入id大于1的数据

4. 用户注销

在配置类中添加退出的配置

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
}

5. 实现记住我

  1. 目前的方法

    (1)cookie技术

    (2)安全框架机制实现自动登录

  2. 原理分析

    ​ 当认证成功后,会向浏览器中存储cookie,并把cookie和用户信息存到数据库中。

    ​ 再次进行访问,获取cookie信息,拿cookie去数据库查询,查询对应信息即可认证成功。

    SpringSecurity入门

  3. 具体实现

    • 创建数据库

      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;
      
    • 修改配置类

      注入数据源,配置操作数据库对象

      //注入数据源
      @Autowired
      private DataSource dataSource;
      //配置对象
      @Bean
      public PersistentTokenRepository persistentTokenRepository(){
          JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
          jdbcTokenRepository.setDataSource(dataSource);
          return jdbcTokenRepository;
      }
      
    • @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.formLogin()
                      .rememberMe()
                      .tokenRepository(persistentTokenRepository())
                      .tokenValiditySeconds(60)//单位秒
                      .userDetailsService(userDetailsService)
      }
      
    • 在登录页面添加复选框

      ​ checkbox名字必须为remember-me

      	<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="登录">
          </form>
      
    • 效果

      SpringSecurity入门

SpringSecurity入门

6.CSRF

跨站请求伪造(Cross-site request forgery),网页读取cookie去请求本没有权限的内容。

​ 如何配置

  • 在页面添加隐藏域

     <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
    

原理

  1. 生成csrfToken保存到HttpSession或Cookie中
  2. 访问时拿token与Session或Cookie中的token进行对比,如果相同就通过。
上一篇:直面春招!一文了解OOM及解决方案,知识点总结+面试题解析


下一篇:SpringSecurity(二十):异常处理