关于程序员:Spring-Security-的学习与使用

39次阅读

共计 6485 个字符,预计需要花费 17 分钟才能阅读完成。

Spring Security 的学习与应用

本文的

1.Spring Security 简介

Spring Security 实质是一个过滤器链,有很多过滤器

2.SpringSecurity 的过滤器加载过程

应用 SpringSecurity 配置过滤器

外围类:DelegatingFilterProxy

  • 首先进入 DelegatingFilterProxydoFilter()办法中
  • 而后在这个办法中有这样一个delegateToUse = initDelegate(wac);
  • 跟进去这个办法,能够看到这样一个Filter delegate = wac.getBean(targetBeanName, Filter.class);,这个获取的就是FilterChainProxy
  • FilterChainProxy中的 doFilter() 办法会调用 doFilterInternal() 办法,而这个办法中的 List<Filter> filters = getFilters(fwRequest); 会获取 Filter 的汇合并执行

3.SpringSecurity 的两个外围接口

这两个接口是用来给咱们自定义去开发的入口。

3.1 UserDetailsService

查问用户名明码的逻辑放在这个接口中。

具体的实现逻辑

  • 创立类继承 UsernamePasswordAuthenticationFilter,并重写其中的 attemptAuthentication() 以及再上一级父类中的胜利 successfulAuthentication() 和失败 unsuccessfulAuthentication 的办法.
  • 创立类实现UserDetailsService, 编写查问数据过程,返回 User 对象,这个 User 对象是平安框架中定义的对象。

3.2 PasswordEncoder

用于加密的接口,在下面返回的 User 对象中的 password 字段能够应用这个官网的加密办法,SpringSecurity 只反对这种加密形式。

4.SpringSecurity 的权限计划 - 认证受权

认证就是用户名明码验证,有三种计划:通过配置文件、通过配置类、通过自定义编写 UserDetailsService 的实现类。

  • 通过配置文件,间接在 application.properties 文件中增加配置即可。
spring.security.user.name=hello
spring.security.user.password=world
  • 通过配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("ppp");
        auth.inMemoryAuthentication().withUser("qqq").password(password).roles("admin");
    }

    /**
     * 这里应用的时候如果不申明这个 bean,下面应用加密的中央会报错
     * 所以这里要注册一个这个 bean
     * @return
     */
    @Bean
    PasswordEncoder password(){return new BCryptPasswordEncoder();
    }
}
  • 自定义实现类(罕用)

在 SpringSecurity 中,会先去找配置文件和配置类,如果找到则应用,如果找不到,则会去找 UserDetailsService 的实现类

step1: 创立配置类,设置应用哪个 UserDetailsService 实现类

/**
 * @Author: njitzyd
 * @Date: 2020/11/1 22:57
 * @Description: 应用自定义的实现实现 securityConfig
 * @Version 1.0.0
 */
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsService;

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

    /**
     * 这里应用的时候如果不申明这个 bean,下面应用加密的中央会报错
     * 所以这里要注册一个这个 bean
     * @return
     */
    @Bean
    PasswordEncoder password(){return new BCryptPasswordEncoder();
    }
}

step2: 编写实现类,返回 User 对象,User 对象有用户名明码和操作权限

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println(usersMapper.selectById(1));
        // 调用 usersMapper 办法,依据用户名查询数据库
        QueryWrapper<Users> wrapper = new QueryWrapper();
        // where username=?
        wrapper.eq("username",username);
        Users users = usersMapper.selectOne(wrapper);
        // 判断
        if(users == null) {// 数据库没有用户名,认证失败
            throw  new UsernameNotFoundException("用户名不存在!");
        }
        List<GrantedAuthority> auths =
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
        // 从查询数据库返回 users 对象,失去用户名和明码,返回
        return new User(users.getUsername(),
                new BCryptPasswordEncoder().encode(users.getPassword()),auths);
    }
}

5.SpringSecurity 实现记住我的性能

只须要配置一个 bean,而后注入数据源,而后在配置中配置记住我就好,残缺的配置:

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 注入数据源
     */

    @Autowired
    private DataSource dataSource;

    /**
     * 配置对象,实现记住我性能
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 能够在启动的时候就创立表,也能够本人创立,建表语句在 JdbcTokenRepositoryImpl 的实现类中
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

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

    /**
     * 这里应用的时候如果不申明这个 bean,下面应用加密的中央会报错
     * 所以这里要注册一个这个 bean
     * @return
     */
    @Bean
    PasswordEncoder password(){return new BCryptPasswordEncoder();
    }

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

        // 配置没有权限拜访跳转自定义页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");

        http.formLogin()   // 自定义本人编写的登录页面
                .loginPage("/on.html")  // 登录页面设置
                .loginProcessingUrl("/user/login")   // 登录拜访门路
                .defaultSuccessUrl("/success.html").permitAll()  // 登录胜利之后,跳转门路
                .failureUrl("/unauth.html")
                .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll() // 设置哪些门路能够间接拜访,不须要认证
                // 以后登录用户,只有具备 admins 权限才能够拜访这个门路
                //1 hasAuthority 办法
                // .antMatchers("/test/index").hasAuthority("admins")
                //2 hasAnyAuthority 办法,有其中的一个权限
                // .antMatchers("/test/index").hasAnyAuthority("admins,manager")
                //3 hasRole 办法   ROLE_sale, 点进去看源码能够看到,会给咱们加一个 ROLE_的前缀
                .antMatchers("/test/index").hasRole("sale")
                .anyRequest().authenticated()
                // 设置记住我的性能
                .and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60)// 设置无效时长,单位秒
                .userDetailsService(userDetailsService);
        // .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
        // .and().csrf().disable();  // 敞开 csrf 防护,就是跨站伪造}
}

6.Spring Security 中罕用的注解

6.1 @Secured

判断是否具备角色,另外须要留神的是这里匹配的字符串须要增加前缀“ROLE_“。
应用注解先要开启注解性能!
@EnableGlobalMethodSecurity(securedEnabled=true)

在控制器办法上增加注解

// 测试注解:@RequestMapping("testSecured")
@ResponseBody
@Secured({"ROLE_normal","ROLE_admin"})
public String helloUser() {return "hello,user";}

6.2@PreAuthorize

先开启注解性能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PreAuthorize:注解适宜进入办法前的权限验证,@PreAuthorize 能够将登录用
户的 roles/permissions 参数传到办法中。

@RequestMapping("/preAuthorize")
@ResponseBody
//@PreAuthorize("hasRole('ROLE_管理员 ')")
@PreAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){System.out.println("preAuthorize");
return "preAuthorize";
}

6.3@PostAuthorize

先开启注解性能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PostAuthorize 注解应用并不多,在办法执行后再进行权限验证,适宜验证带有返回值
的权限.

@RequestMapping("/testPostAuthorize")
@ResponseBody
@PostAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){System.out.println("test--PostAuthorize");
return "PostAuthorize";
}

6.4@PostFilter

@PostFilter:权限验证之后对数据进行过滤 留下用户名是 admin1 的数据
表达式中的 filterObject 援用的是办法返回值 List 中的某一个元素

RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员 ')")
@PostFilter("filterObject.username =='admin1'")
@ResponseBody
public List<UserInfo> getAllUser(){ArrayList<UserInfo> list = new ArrayList<>();
list.add(new UserInfo(1l,"admin1","6666"));
list.add(new UserInfo(2l,"admin2","888"));
return list;
}

6.5@PreFilter

@PreFilter: 进入控制器之前对数据进行过滤

@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员 ')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> list){
list.forEach(t-> {System.out.println(t.getId()+"\t"+t.getUsername());
});
return list;
}

参考文档

正文完
 0