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=hellospring.security.user.password=world
  • 通过配置类
@Configurationpublic 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 */@Configurationpublic 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对象有用户名明码和操作权限

@Servicepublic 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,而后注入数据源,而后在配置中配置记住我就好,残缺的配置:

@Configurationpublic 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'")@ResponseBodypublic 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")@ResponseBodypublic List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> list){list.forEach(t-> {System.out.println(t.getId()+"\t"+t.getUsername());});return list;}

参考文档