美文网首页jvmSpring
一、Spring Security入门

一、Spring Security入门

作者: zenghi | 来源:发表于2019-03-07 14:51 被阅读0次

    一、Spring security框架简介

    Spring SecuritySpring社区的一个顶级项目,也是Spring Boot官方推荐使用的Security框架。除了常规的Authentication(认证)和Authorization(授权)之外,Spring Security还提供了诸如ACLLDAPJAASCASOAuth等高级特性以满足复杂场景下的安全需求。

    spring security的主要核心功能为 Authentication(认证)Authentication(授权),所有的架构也是基于这两个核心功能去实现的:

    • Authentication(认证):认证就是判断用户身份是否合法,例如用户名密码登录就是认证,如果一个用户拥有正确的密码,即可通过认证;
    • Authorization(授权):用户认证通过,但是每个用户的权限不同,判断用户有哪些权限以及是否有权限访问某些资源,就是授权。

    而在认证和授权过程中,又会涉及到三个核心概念:

    • Principle(User):标识一个认证过的实体,在大多数场景下,在Spring Security中,一个Principal只是简单地代表一个用户,所以我当我们说一个安全实体的时候,你可以将其等同于说用户。
    • Authority(Role):可以理解为已认证的用户角色,如用户是老师角色或者是学生角色
    • Permission:用户拥有的权限,比如用户权限是只读,不能添加、修改、删除操作。

    这里注意AuthorityPermission,字面上都是权限,但在Spring Security中,Authority是指用户的角色(role),用户的Authority是必须以ROLE_开头的,比如ROLE_student表示学生角色。

    Spring Security中,AuthorityPermission是两个完全独立,两者并没有必然的联系,但可以通过配置进行关联。比如,同样是学生角色,拥有不同的权限。

    二、项目搭建

    2.1、hello spring security

    1. IDEA创建一个Spring Boot项目,依赖:

      <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>
      
    2. 编写Controller

      @RestController
      public class HelloController {
          @GetMapping("/hello")
          public String hello(){
              return "hello spring security";
          }
      }
      
    3. 启动应用程序,访问地址http://localhost:8080/hello,出现身份验证输入框:

      Snipaste_2019-02-26_18-24-23.png

      这是因为SpringBoot默认的Spring Security就是生效的,此时所有的请求接口都是被保护的,需要通过登录后才能正常访问。

      Spring Security提供了一个默认的用户,用户名是user,而密码则是启动项目的时候自动生成打印在控制台日志:

      Using generated security password: b0e4f3c9-1f3b-4e2e-bcba-697e148232b7
      

    2.2、关闭默认身份验证

    在启动类排除

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

    2.3、自定义用户名和密码

    上面的用户名是默认的,密码是启动时随机生成的。要自定义用户名和密码,只需要在配置文件application.properties文件中添加如下配置:

    spring.security.user.name = admin       
    spring.security.user.password = 123456
    

    三、获取登录用户信息

    @GetMapping("/get-user1")
    public Authentication getUser1(){
        return SecurityContextHolder.getContext().getAuthentication();
    }
    @GetMapping("/get-user2")
    public Authentication getUser2(Authentication authentication){
        return authentication;
    }
    @GetMapping("/get-user3")
    public Authentication getUser3(@AuthenticationPrincipal  Authentication authentication){
        return authentication;
    }
    @GetMapping("/get-user4")
    public UserDetails getUser4(@AuthenticationPrincipal UserDetails userDetails){
        return userDetails;
    }
    @GetMapping("/get-user5")   
    public UserDetails getUser5(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        return userDetails;
    }
    

    四、基于内存用户认证

    4.1、配置基于内存的用户

    内存初始化认证信息,需重写WebSecurityConfigurerAdapter类中的configure(AuthenticationManagerBuilder auth)方法,通过auth对象的inMemoryAuthentication()方法指定认证信息:

    // 添加注解
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 添加第一个用户
            auth
                    // 使用内存认证
                    .inMemoryAuthentication()
                    // 设置用户名和密码
                    .withUser("admin").password("admin123")
                    // 指定角色, 空为不指定
                    .roles();
    
            // 添加第二个用户
            auth
                    // 使用内存认证
                    .inMemoryAuthentication()
                    // 设置用户名和密码
                    .withUser("user").password("user123")
                    // 指定角色, 空为不指定
                    .roles();
        }
    }
    

    如果是5.x之前的版本,到这里启动就可以正常访问。但5.x的版本启动后,在登录页面输入账号进行访问时,报如下错误:

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

    这是因为Spring Security 5.0中新增了多种加密方式,也改变了密码的格式,需要指定加密方式。

    WebSecurityConfigurerAdapter有三个方法:

    • configure(AuthenticationManagerBuilder):用于通过允许AuthenticationProvider容易地添加来建立认证机制。以下定义了内置认证与内置的用户“user”和“admin”登录。
    AuthenticationManagerBuilder allows 
    public void configure(AuthenticationManagerBuilder auth) {
        auth
            .inMemoryAuthentication()
            .withUser("user")
            .password("password")
            .roles("USER")
        .and()
            .withUser("admin")
            .password("password")
            .roles("ADMIN","USER");
    }
    
    • configure(HttpSecurity):允许基于选择匹配在资源级配置基于网络的安全性。以下示例将以/admin/开头的请求限制为具有ADMIN角色的用户,并指定任何其他请求需要成功验证后才能访问:
    protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeUrls()
        .antMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
    }
    
    • configure(WebSecurity):用于影响全局安全性(配置资源,设置调试模式,通过实现自定义防火墙定义拒绝请求)的配置设置。例如,以下方法将导致以/resources/开头的任何请求被忽略认证,允许匿名访问。
    public void configure(WebSecurity web) throws Exception {
    web
        .ignoring()
        .antMatchers("/resources/**");
    }
    

    4.2、密码加密

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        // 配置加密方式
        @Bean
        public PasswordEncoder passwordEncoder(){
            /**
             * 使用SpringSecurity推荐的BCryptpassword加密类,该加密过程是不可逆的,
             * 相同的明文每一次加密,加密之后的密文都是不一样的
             *
             * 如果需要其他加密方式,SpringSecurity也提供有如下(在源码类PasswordEncoderFactories中可查看):
             * encoders.put("ldap", new LdapShaPasswordEncoder());
             * encoders.put("MD4", new Md4PasswordEncoder());
             * encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
             * encoders.put("noop", NoOpPasswordEncoder.getInstance());
             * encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
             * encoders.put("scrypt", new SCryptPasswordEncoder());
             * encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
             * encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
             * encoders.put("sha256", new StandardPasswordEncoder());
             *
             * 加密方法:     PasswordEncoder.encode()
             * 比较方法(判断用户密码输入是否正确): PasswordEncoder.matches(输入未加密的密码,加密的密码)
             */
            return new BCryptPasswordEncoder();
        }
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .inMemoryAuthentication()
                    // 加密密码
                    .withUser("admin").password(passwordEncoder().encode("admin123"))
                    .roles();
    
            auth
                    .inMemoryAuthentication()
                    // 加密密码
                    .withUser("user").password(passwordEncoder().encode("user123"))
                    .roles();
        }
    }
    

    4.3、密码明文

    @EnableWebSecurity
    public class WebSecirutyConfig extends WebSecurityConfigurerAdapter {
        @Bean
        public PasswordEncoder passwordEncoder(){
            // 密码不加密,使用明文方式
            return new PasswordEncoder() {
                @Override
                public String encode(CharSequence charSequence) {
                    return charSequence.toString();
                }
    
                @Override
                public boolean matches(CharSequence charSequence, String s) {
                    return s.equals(charSequence.toString());
                }
            };
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .inMemoryAuthentication()
                    // 配置密码不加密,下面两种方式都可以,不会加密密码
                    // .withUser("admin").password(passwordEncoder().encode("admin123"))
                    .withUser("admin").password("admin123")
                    .roles();
    
            auth
                    .inMemoryAuthentication()
                    // 不会加密密码
                    .withUser("user").password(passwordEncoder().encode("user123"))
                    .roles();
        }
    }
    

    五、用户角色认证

    5.1、为用户配置角色

    为用户设置角色:

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
        
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .inMemoryAuthentication()
                    .withUser("admin").password(passwordEncoder().encode("admin123"))
                    // 指定为 admin 角色
                    .roles("admin");
            
            auth
                    .inMemoryAuthentication()
                    .withUser("user").password(passwordEncoder().encode("user123"))
                    // 指定为 user 角色
                    .roles("user");
        }
    }
    

    5.2、开启方法安全验证

    5.2.1、开启方法安全

    WebSecurityConfig中添加注解@EnableGlobalMethodSecurity开启方法安全验证,该注解有几个属性:

    1. prePostEnabled 是否启用Spring Securityprepost注解,支持例如@PreAuthorize、@PostAuthorize,..
    2. secureEnabled是否启用Spring Security@Secured注解。少用,prePostEnabled能实现相同功能
    3. jsr250Enabled是否启用 JSR-250 annotations 注解[@RolesAllowed..]。少用,prePostEnabled能实现相同功能

    在一个应用程序中,可以启用多个类型的注解,但是只在接口、类、方法上设置一个注解。如:

    开发中,最常用的就是prePostEnabled,所以在WebSecurityConfig进行设置为true

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法安全验证,并设置prePostEnabled为true
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        ...
    }
    

    5.2.2、方法权限设置

    用到的表达式

    表达式

    对访问方法的权限设置,有两种方法:

    1. 在方法上配置注解:

      • @PreAuthorize:在执行方法之前进行权限校验。

        @RestController
        public class HelloController {
            @GetMapping("/hello")
            public String hello() {
                return "hello";
            }
        
            @PreAuthorize("hasAnyRole('user')")     //只能 user 角色才能访问
            @GetMapping("/user")
            public String user() {
                return "user";
            }
        
            @PreAuthorize("hasAnyRole('admin')")    // 只能 admin 角色才能访问
            @GetMapping("/admin")
            public String admin() {
                return "admin";
            }
            
            @PreAuthorize("hasAnyRole('admin','admin')")    // 拥有其中一个角色便可访问(也可用下面方法)
            @GetMapping("/admin")
            public String admin() {
                return "admin";
            }
            
            @PreAuthorize("hasAnyRole('admin') or hasAnyRole('user')")  // 拥有其中一个角色便可访问(和上面方法一样)
            @GetMapping("/userOrAdmin")
            public String user3() {
                return "userOrAdmin";
            }
        
            @PreAuthorize("hasAnyRole('admin') and hasAnyRole('user')")  // 同时拥有 admin 和 user 角色才能访问
            @GetMapping("/userAndAdmin")
            public String user2() {
                return "userAndAdmin";
            }
        

        该注解还支持Spring EL表达式,可以根据参数进行校验,如:

        • @PreAuthorize("#id < 10"):方法必须有id参数,而且参数的值必须小于10
        • @PreAuthorize("principal.username.equeals(#username)"):方法必须有username参数,而且参数的值必须是登录用户的用户名
        @RestController
        public class HelloController {
            @PreAuthorize("#id < 10")// 只有参数id小于10才能访问
            @GetMapping("/user/{id}")
            public String user3(@PathVariable("id") Integer id) {
                return "只有入参id小于10才能访问";
            }
            
            @PreAuthorize("principal.username.equals.(#username)")
            @GetMapping("/user")
            public String user3(@RequestParam("username") String username) {
                return "参数的姓名是登录的用户名才可访问";
            }
        }
        
      • @PostAuthorize:在执行方法之后进行校验。

        @RestController
        public class HelloController {
            // 查询到用户信息后,再验证用户名是否和登录用户名一致
            @PostAuthorize("returnObject.name == authentication.name")
            @GetMapping("/get-user")
            public User getUser(String name){
                return userService.getUser(name);
            }
            // 验证返回的数是否是偶数
            @PostAuthorize("returnObject % 2 == 0")
            public Integer test(){
                // ...
                return id;
            }
        }
        
      • @PreFilter:对集合类型的参数执行过滤,移除结果为false的元素

        @RestController
        public class HelloController {
            @Autowired
            private ObjectMapper objectMapper;
            
            // 指定过滤的参数,仅偶数能进入方法,如入参[[1,2,3,4,5]],则返回[2,4]
            @PreFilter(filterTarget="ids", value="filterObject%2==0")
           @PostMapping("/test")
            public void test(@RequestBody List<Integer> ids){
                return objectMapper.writeValueAsString(ids);    
            }
        }
        
      • @PostFilter

        对集合类型的返回值进行过滤,移除结果为false的元素

        @RestController
        public class HelloController {
            @PostFilter("filterObject.id%2==0")
            public List<User> findAll(){
                ...
                    return userList;
            }
        }
        
    2. 处理像上面在方法上使用注解外,还可通过Spring Security配置类WebSecurityConfig中设置

      @EnableWebSecurity
      @EnableGlobalMethodSecurity(prePostEnabled = true)
      public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
       ...
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                   // 对请求进行验证
                      .authorizeRequests()
                   // 对所有的 /user请求 和以 /user1/开头的请求 都必须 admin角色才能访问
                      .antMatchers("/user", "/user1/**").hasAnyRole("ROLE_admin")
                   // 也可指定请求方法验证
                   .antMatchers(HttpMethod.GET, "/userList").hasAnyRole("ROLE_admin")
                       // 也可以通过正则表达式进行匹配
                      .regexMatchers("users/([^/].*?)/tokens/.*").hasAnyRole("ROLE_user")
                   // 也可以对一些不需登录访问的请求(匿名请求),如注册用户就不需要登录后访问
                   .antMatchers(HttpMethod.POST,"addUser").permitAll()
                      // 所有请求都需要登录后才能访问,这里必须放在上面所有过滤请求之后
                      .anyRequest().authenticated() 
                      .and()
                      .formLogin() // 使用默认的登录页面
                      .and()
                      .csrf().disable();// post请求关闭csrf验证,不然访问报错。生产环境需开启   
          }
      }
      

      这里的配置的请求验证优先级比注解的优先级高,且这里的角色需要加ROLE_前缀。

      像上面,直接在WebSecurityConfig中配置请求路径的权限控制,如果请求路径过多,配置的权限控制也会过多,且权限控制与Security配置文件混合一起,不方便维护,所以我们可以把权限控制单独提取出来:

      1. 新建接口PathAuthenticationProvider

        public interface PathAuthenticationProvider{ 
            void config(
                ExpressionUrlAuthorizationConfigurer<HttpSecurity>
                .ExpressionInterceptUrlRegistry authorizeRequests);
        }
        

        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry对象即是下面的authorizeRequests()

        图片

        Ctrl + 鼠标左键点击进入源码便看到:

        图片
    1. 新建PathAuthenticationProviderImpl实现PathAuthenticationProvider接口:

      @Component
      public class PathAuthenticationProviderImpl implements PathAuthenticationProvider {
          @Override
          public void config(
              ExpressionUrlAuthorizationConfigurer<HttpSecurity>
               .ExpressionInterceptUrlRegistry authorizeRequests) {
              authorizeRequests
                  // 对所有的 /user请求 和以 /user1/开头的请求 都必须 admin角色才能访问
                      .antMatchers("/user", "/user1/**").hasAnyRole("ROLE_admin")
                   // 也可指定请求方法验证
                   .antMatchers(HttpMethod.GET, "/userList").hasAnyRole("ROLE_admin")
                       // 也可以通过正则表达式进行匹配
                      .regexMatchers("users/([^/].*?)/tokens/.*").hasAnyRole("ROLE_user")
                   // 也可以对一些不需登录访问的请求,如注册用户就不需要登录后访问
                   .antMatchers(HttpMethod.POST,"addUser").permitAll()
                      // 所有请求都需要登录后才能访问,这里必须放在上面所有过滤请求之后
                      .anyRequest().authenticated();
          }
      }
      

      这里的写法和WebSecurityConfig中的写法是一样的。

    2. WebSecurityConfig配置类中,把上面的路径权限配置引入:

      @Configuration
      @EnableWebSecurity
      @EnableGlobalMethodSecurity(prePostEnabled = true)
      public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
      
          // 省略...
          
          // 注入路径权限控制配置
          @Autowired
          private PathAuthenticationProvider authProvider;
          
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      .formLogin();
                      .and()
                      .csrf().disable();
      
              // 路径权限配置
              authProvider.config(http.authorizeRequests());
          }
      }
      

      像这样,单独把路径权限抽取出来,方便单独对路径权限进行配置。这里只是详细说明了http.authorizeRequests()的路径权限抽取为单独一个类,同理,其他的配置,如http.forLogin()等也可以抽取出来。

      还有一个重要配置 access()的使用,在前面的路径权限配置时,在编辑器提示可看到:

    图片

    提示的配置都和这一小节开头的表达式列表一一对应:

    图片

    但可看到多出了两个 not()access

    对于not(),没什么好说的,如下:

       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http
               .authorizeRequests()
               // 只要用户角色不是 ROLE_admin都可访问
               .antMatchers("/user").not().hasAnyRole("ROLE_admin")
       }
    

    而对于 access,看源码说明:

       /**
        * Allows specifying that URLs are secured by an arbitrary expression
        *
        * @param attribute the expression to secure the URLs (i.e.
        * "hasRole('ROLE_USER') and hasRole('ROLE_SUPER')")
        * @return the {@link ExpressionUrlAuthorizationConfigurer} for further
        * customization
        */
       public ExpressionInterceptUrlRegistry access(String attribute) {
           if (not) {
               attribute = "!" + attribute;
           }
           interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
           return ExpressionUrlAuthorizationConfigurer.this.REGISTRY;
       }
    

    可知道,其参数输入为 security的表达式,所以access中的参数是和方法注解@PreAuthorize()参数是一样的,如

       @Component
       public class PathAuthenticationProviderImpl implements PathAuthenticationProvider {
           @Override
           public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests) {
               authorizeRequests
                       // access参数为表达式
                       .antMatchers("/user").access("hasRole('user')")
                       .antMatchers("/hello").access("hasAnyRole('user','admin')")
                       .anyRequest().authenticated();
           }
       }
    

    对于access(),可以通过其实现基于数据库的RBAC(基于角色的权限访问控制)权限控制,可以实现动态配置用户的角色和权限来实现授权管理,例如:

    • 创建类`RbacService:

      @Component
      public class RbacServiceImpl {
          public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
              /**
               * 假设现在系统中的 uri的权限为:
               * 请求方式   uri    所需角色
               * get       /user   ROLE_user
               * post      /user   ROLE_admin
               * put       /user   ROLE_tenant
               *
               * get       /hello  ROLE_user
               * post      /hello  ROLE_admin
               */
              // 以下为模拟的数据,(实现开发中,可以通过数据库来维护 uri 的权限)
              Map<String, Map<String, String>> map = new HashMap<>();
      
              Map<String, String> userMap = new HashMap<>();
              userMap.put("get", "ROLE_user");
              userMap.put("post", "ROLE_admin");
              userMap.put("put", "ROLE_tenant");
              map.put("/user", userMap);
      
              Map<String, String> helloMap = new HashMap<>();
              helloMap.put("get", "ROLE_user");
              helloMap.put("post", "ROLE_admin");
              map.put("/hello", helloMap);
      
              Object principal = authentication.getPrincipal();
              if (principal instanceof UserDetails) {
                  // 获取到登录用户的所拥有的角色
                  Collection<? extends GrantedAuthority> authorities = ((UserDetails) principal).getAuthorities();
      
                  // 获取请求的 URI
                  String requestURI = request.getRequestURI();
                  // 获取请求的方法
                  String requestMethod = request.getMethod().toLowerCase();
      
                  // 通过 URI 获取权限
                  Map<String, String> stringMap = map.get(requestURI);
                  // 再通过 请求方法 获取到当前请求URI的权限角色
                  String uriRole = stringMap.get(requestMethod);
      
                  // 判断用户是否有该角色
                  for (GrantedAuthority authority : authorities) {
                      if (authority.getAuthority().equalsIgnoreCase(uriRole)) {
                          return true;
                      }
                  }
              }
              return false;
          }
      }
      
    1. 修改PathAuthenticationProviderImpl的权限控制:

      @Component
      public class PathAuthenticationProviderImpl implements PathAuthenticationProvider {
          @Override
          public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests) {
              authorizeRequests
                  // 所有请求都需要经过 rbacServiceImpl.hasPermission方法过滤,注意参数名称hasPermission()和类`rabcServiceImpl`一样
                  .anyRequest().access("@rbacService.hasPermission(request, authentication)");
          }
      }
      

      启动程序,所有请求都会经过rbacServiceImpl.hasPermission(request, authentication)方法。

    六、忽略权限认证

    前面配置的权限验证都是拦截认证所有的请求URI,但有些请求是无需进行拦截认证的,如静态文件js,css等,而如果使用前面的配置,则需要:

       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http
               .authorizeRequests()
               // 图标访问不用认证
               .antMatchers("/favicon.ico","/js/**","/css/**").permitAll()
               // GET请求/register不用认证
               .antMatchers(HttpMethod.GET, "/register").permitAll();
       }
    

    但其实Spring Security提供有一个configure(WebSecurity web)进行配置,在WebSecurityConfig配置类重写进行配置,如:

       @Override
       public void configure(WebSecurity web) throws Exception {
           web
               .ignoring()
               .mvcMatchers("/favicon.ico","/js/**","/css/**")
               // 其实这里配置非静态路径有坑,不推荐,请在configure(HttpSecurity http)设置
               .mvcMatchers(HttpMethod.GET, "/register");
       }
    

    七、基于数据库用户认证

    7.1、引入依赖

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    

    这里使用mybatis-plus

    7.2、数据源

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
    logging:
      level:
        com.example.bdatabaserole.mapper: debug
    

    7.3、表及数据

    图片

    对应的entity类:

    @Data
    @ToString
    public class UserInfo {
        private Integer id;
        private String username;
        private String password;
        private String role;
    }
    

    对应的mapper接口:

    @Repository
    public interface UserInfoMapper extends BaseMapper<UserInfo> {
    }
    

    插入一个用户并加密密码:

    @SpringBootTest
    class SpringSecurity20200328ApplicationTests {
        @Autowired
        private UserInfoMapper userInfoMapper;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Test
        void contextLoads() {
            UserInfo userInfo = new UserInfo();
            userInfo.setId(2);
            userInfo.setUsername("user");
            // 加密密码,密码匹配使用 passwordEncoder.matches(dbPwd, inputPwd)
            userInfo.setPassword(passwordEncoder.encode("123"));
            userInfo.setRole("user");
            userInfoMapper.insert(userInfo);
        }
    }
    

    7.4、配置从数据库获取用户信息验证

    实现UserDetailService,重写方法:

    @Component
    public class CustomUserDetailsService implements UserDetailsService {
        @Autowired
        private UserInfoMapper userInfoMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            Map<String, Object> map = new HashMap<>();
            map.put("username", username);
            UserInfo userInfo = userInfoMapper.selectByMap(map).get(0);
            if (userInfo == null) {
                throw new UsernameNotFoundException("用户不存在");
            }
            // 得到用户角色
            String role = userInfo.getRole();
            // 角色集合
            List<GrantedAuthority> authorities = new ArrayList<>();
            // 角色必须以`ROLE_`开头,数据库中没有,则在这里加
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
    
            return new User(
                    userInfo.getUsername(),
                    // 因为密码已加密,所以不用加密
                    userInfo.getPassword(),
                    authorities
            );
        }
    }
    

    WebSecurityConfig配置:

    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
    

    重启程序,正常使用数据库的用户登录。

    八、退出登录

    默认的退出登录URL为:/logout,如:http://localhost:8080/logout退出登录。

    如果开启了CSRF,必须使用post方式请求/logout

    相关文章

      网友评论

        本文标题:一、Spring Security入门

        本文链接:https://www.haomeiwen.com/subject/xxvcpqtx.html