Spring Security 的学习与应用
本文的
1.Spring Security 简介
Spring Security实质是一个过滤器链,有很多过滤器
2.SpringSecurity的过滤器加载过程
应用SpringSecurity配置过滤器
外围类:DelegatingFilterProxy
- 首先进入
DelegatingFilterProxy
的doFilter()
办法中 - 而后在这个办法中有这样一个
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;}
参考文档