Spring Security学习记录基础版(一)

本文详细介绍了Spring Security的基本原理,包括用户认证、授权机制,自定义登录页面、数据库用户管理、访问控制策略、CSRF防护及注销登录等,适合Java初学者学习和实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring Security学习记录基础版(一)

  • 一个Java萌新的学习记录,文章中可能说的不严谨或者错的地方,请指出,希望能跟大家一起学习。

1.概要

  • Spring Security的核心功能
    • 用户认证:就是系统认为用户是否登录
    • 用户授权:就是系统判断用户是否有权限去做某些事情

2.Hello Security

  1. 创建一个SpringBoot工程

  2. 引入相关依赖

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
  3. 编写controller测试

    @RestController
    @RequestMapping("quick")
    public class QuickController {
        @GetMapping("/hello")
        public Object hello(){
            return "hello security";
        }
    }
    
    
  4. 测试结果:security启动默认会有一个登录页,security默认得用户名是:user 密码每次执行会在控制台打印,登录成功后显示测试结果

在这里插入图片描述

在这里插入图片描述

测试结果

3.SpringSecurity基本原理

  • SpringSecurity本质是一个过滤器链,由很多过滤器组成

  • FilterSecurityInterceptor:是一个方法级的权限过滤器,基本位于过滤链的最底部

  • ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中的异常

  • UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中用户名,密码

  • SpringSecurity两个重要的接口

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

4.security设置用户和密码

  1. 通过配置文件设置(了解)

    spring:
      security:
        user:
          name: admin
          password: 123456
          roles: root
    
    
  2. 通过配置类设置(了解)

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        PasswordEncoder passwordEncoder;
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //将密码加密
            String password = passwordEncoder.encode("123456");
            auth.inMemoryAuthentication().withUser("test")
                .password(password).roles("test");
        }
        //不加这一步会报@PreAuthorizeConsider defining a bean of type 		'org.springframework.security.crypto.password.PasswordEncoder' in your configuration.
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    }
    
    
  3. 自定义实现类设置

    • 第一步 创建配置类,设置使用哪个userDetailsService实现类

      @Configuration
      public class MySecurityConfig extends WebSecurityConfigurerAdapter {
          @Autowired
          private UserDetailsService userDetailsService;
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
          }
          @Bean
          public PasswordEncoder passwordEncoder() {
              return new BCryptPasswordEncoder();
          }
      
      }
      
    • 第二步 编写实现类,返回User对象,User对象有用户名密码和操作权限

      @Service
      public class MyUserDetailsService implements UserDetailsService {
          @Override
          public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
              List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
              return new User("test",new BCryptPasswordEncoder().encode("123"),auths);
          }
      }
      
      

5.查询数据库完成用户认证

  • 引入相关依赖

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.2</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.23</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
  • 创建数据库

    CREATE DATABASE test
    USE test
    CREATE TABLE users(
    	id INT(10) PRIMARY KEY AUTO_INCREMENT,
    	uname VARCHAR(30) NOT NULL,
    	upassword VARCHAR(80) NOT NULL
    )
    INSERT INTO users VALUES (DEFAULT,'admin','123456');
    INSERT INTO users VALUES (DEFAULT,'test','123');
    INSERT INTO users VALUES (DEFAULT,'root','123456')
    
  • 配置数据库相关信息

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
        username: root
        password: 123456
    
  • 创建users表对应实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Users {
        private long id;
        private  String uname;
        private  String upassword;
    }
    
    
  • 整合mybatisPlus,创建接口,继承mp的接口

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

    @Service
    public class MyUserDetailsService implements UserDetailsService {
        @Autowired
        UserMapper userMapper;
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //mybatisplus提供的条件构造器
            QueryWrapper<Users> wrapper = new QueryWrapper();
            //下面这句话等同于where uname=?
            wrapper.eq("uname",username);
            //因为用户名不能重复,使用selectOne得到某一条数据
            Users user = userMapper.selectOne(wrapper);
            if(user == null){
                throw new UsernameNotFoundException("用户不存在");
            }
            List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
           //查询数据库得到的user对象的用户名和密码返回给security提供的User对象里面
            return new User(user.getUname(),new BCryptPasswordEncoder().encode(user.getUpassword()),auths);
        }
    }
    
  • 在启动类上添加@MapperScan注解

    @SpringBootApplication
    @MapperScan("com.project.securityquickstart.mapper")
    public class SecurityQuickStartApplication {
        public static void main(String[] args) {
            SpringApplication.run(SecurityQuickStartApplication.class, args);
        }
    
    }
    
  • 启动测试

    @RestController
    @RequestMapping("quick")
    public class QuickController {
        @GetMapping("/hello")
        public Object hello(){
            return "hello security";
        }
    }
    

    测试成功,三个账号都可以进行登录,但是启动时出现如下警告: This primary key of “id” is primitive !不建议如此请使用包装类 in Class: “com.project.securityquickstart.bean.Users”

6.自定义登录页

  • 配置类编写相关的配置

    @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()    //自定义自己的登录页面
                    .loginPage("/login.html")   //登录页面设置
                    .loginProcessingUrl("/quick/login")  //登录页访问路径
                    .defaultSuccessUrl("/quick/index").permitAll()   //登录成功后跳转的地址
                    .and().authorizeRequests()
                    //配置不用认证就能访问的页面
                    .antMatchers("/","/quick/login","/quick/hello").permitAll()
                    .anyRequest().authenticated()
                    .and().csrf().disable();   //关闭csrf防护
        }
    
  • 创建登录的页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>security自定义登录页</title>
    </head>
    <body>
            <form action="/quick/login" method="post">
                <input type="text" name="username"/>
                <input type="password" name="password"/>
                <input type="submit" value="Login"/>
            </form>
    </body>
    </html>
    
  • 编写controller测试

    @RestController
    @RequestMapping("quick")
    public class QuickController {
        @GetMapping("/hello")
        public Object hello(){
            return "hello security";
        }
        @GetMapping("/index")
        public Object login(){
            return "hello index";
        }
    }
    
  • 效果图

    • 当我输入http://localhost:8080/quick/index的时候会自动跳转登录页

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-er8IdNWC-1616488941368)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\image-20210319154008262.png)]

    • 登录成功后会跳转到配置类里配置的/quick/index的controller

    在这里插入图片描述

7.基于权限进行访问控制

  • 如果当前的主体具有指定权限,则返回true,否则返回false

  • 没有访问权限 403

  • hasAuthority和hasAnyAuthority方法的使用实例

    • 在配置类编写基于权限进行访问控制的相关配置

       @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.formLogin()    //自定义自己的登录页面
                      .loginPage("/login.html")   //登录页面设置
                      .loginProcessingUrl("/quick/login")  //登录页访问路径
                      .defaultSuccessUrl("/quick/index").permitAll()   //登录成功后跳转的地址
                      .and().authorizeRequests()
                      //配置访问权限为admins的才能访问/quick/index,只能配置单个
      //                .antMatchers("/quick/index").hasAuthority("admins")
                      //配置访问权限为admins和role的都能访问/quick/index,可配置多个
                      .antMatchers("/quick/index").hasAnyAuthority("admins,role")
                      //配置不用认证就能访问的页面
                      .antMatchers("/","/quick/login","/quick/hello").permitAll()
                      .anyRequest().authenticated()
                      .and().csrf().disable();   //关闭csrf防护
          }
      
    • 在MyUserDetailsService里配置用户的访问权限

      List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
      

8.基于角色进行访问控制

  • 如果当前主题具有指定角色,则返回true

  • hasRole和hasAnyRole使用实例

    • 在配置类编写基于角色进行访问控制的相关配置

                      //配置角色为root的才能访问/quick/index,只能配置单个角色
      //                .antMatchers("/quick/index").hasRole("root")
                      //配置角色为root和admin中的一个的就能访问/quick/index,可配置多个角色
                      .antMatchers("quick/index").hasAnyRole("root,admin")
      
    • 在MyUserDetailsService里配置用户的角色

      List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role,ROLE_root,ROLE_admin");
      
    • 注意:security底层的hasRole和hasAnyRole方法会给你加上"ROLE_"的前缀,在配置用户角色的时候要记得加上

在这里插入图片描述

9.自定义403页面

  • 在配置类中编写

    http.exceptionHandling().accessDeniedPage("/unauth.html");
    
  • 编写unauth.html页面,效果图:

在这里插入图片描述

10.注解的使用

10.1@Secured --用户具有某个角色,可以访问方法
  • 在启动类或配置类开启注解

    @EnableGlobalMethodSecurity(securedEnabled = true)
    public class SecurityQuickStartApplication {
        public static void main(String[] args) {
            SpringApplication.run(SecurityQuickStartApplication.class, args);
        }
    
    }
    
  • 在controller添加@Secured能够访问的角色

  @GetMapping("/getUpd")
    @Secured("ROLE_root")
    public Object upd(){
        return "hello,update";
    }
  • 在MyUserDetailsService查看有没有配置能访问的角色并测试

     List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role,admins,ROLE_root");
    
10.2@PreAuthorize --在进入方法之前进行权限和角色验证
  • 在启动类或配置类开启注解

    @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
    
  • 在在controller添加@PreAuthorize能够访问的角色和权限

       @GetMapping("/getUpd")
      @PreAuthorize("hasAuthority('role')")
        public Object upd(){
            return "hello,update";
        }
    
  • 在MyUserDetailsService查看有没有配置能访问的角色并测试

  • 这个注解里面可以写hasRole、hasAnyRole、hasAuthority和hasAnyAuthority

10.3@PostAuthorize --在方法执行之后进行权限和角色验证
  • 在启动类或配置类开启注解 --同上

  • 在controller添加@PreAuthorize能够访问的角色和权限

        @GetMapping("/getUpd")
    //    @Secured("ROLE_root")
    //   @PreAuthorize("hasAuthority('role')")
        @PostAuthorize("hasAuthority('role')")
        public Object upd(){
            System.out.println("update执行中");
            return "hello,update";
        }
    }
    
  • 在MyUserDetailsService查看有没有配置能访问的角色并测试,测试发现虽然没权限访问,但是里面的方法还是打印了

在这里插入图片描述

在这里插入图片描述

  • 这个注解里面可以写hasRole、hasAnyRole、hasAuthority和hasAnyAuthority
10.4@PostFilter --方法返回的数据进行过滤
  • 在controller注解中添加@PostFilter 需要过滤的数据,可以搭配上面的注解一起使用

     @GetMapping("/getAll")
        @PreAuthorize("hasAuthority('role')")
        @PostFilter("filterObject.uname == 'admin1'")
        public List<Users> getAll(){
            List<Users> list = new ArrayList<Users>();
            list.add(new Users(0,"admin1","123"));
            list.add(new Users(0,"admin2","123"));
            return list;
        }
    
  • 测试结果,只返回指定过滤的值

在这里插入图片描述

10.5@PreFilter --方法传入的数据进行过滤
  • 暂时还不知道怎么用,出现如下错误
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: Filter target must be a collection, array, map or stream type, but was Users(uname=admin1, upassword=123456)] with root cause

11.用户注销

  • 在登录页添加一个退出的超链接

    <a href="/logout">注销</a>
    
  • 在配置类中添加退出映射地址

     http.logout().logoutUrl("/logout").logoutSuccessUrl("/quick/exit").permitAll();
    
  • 测试类测试

     @GetMapping("/exit")
        public Object exit(){
            return "注销成功。。。。";
        }
    
  • 测试结果注销是能注销,但是在访问其他方法不需要登录验证了

12.自动登录

  • 创建保存用户token的数据库

    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;
        }
    
  • 配置类中配置自动登录

     .and().rememberMe().tokenRepository(persistentTokenRepository())
      //配置自动登录的有效时间              .tokenValiditySeconds(120)
         .userDetailsService(userDetailsService)
    
  • 在登录页面添加复选框

    /*name必须叫remember-me,因为security内部封装了只识别这个名字的方法*/
    <input type="checkbox" name="remember-me" title="自动登录"> 自动登录
    

13.csrf防护

  • 默认开启的,了解就行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值