Spring Security
一、入门案例
当前案例pom.xml配置
org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE -- lookup parent from repository -->
org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test
测试Controller
@RestController @RequestMapping("/test") public class TestController { @GetMapping("/hello") public String hello(){ return "Spring Security"; } }
演示效果:
当引入了security时,spring会自动实现认证功能,此时访问 http://localhost:8080/test/hello时,会自动跳转到 http://localhost:8080/login处;
默认用户名:user
默认密码:当前项目运行控制台打印出来的uuid
此时通过用户名和密码可以正常进行认证;
二、基本原理
Spring Security本质是一个过滤器链;
1、过滤器:以下列举其中三个过滤器
FilterSecurityInterceptor:是一个方法级别的权限过滤器,位于过滤器链的最底部;
ExceptionTranslationFilter:是一个异常过滤器,用来处理认证授权过程中抛出的异常;
UsernamePasswordAuthenticationFilter:对/login的post请求做拦截,校验表单中的用户名、密码;
2、过滤器加载过程:
使用SpringSecurity配置过滤器(DelegatingFilterProxy);
public class DelegatingFilterProxy extends GenericFilterBean { public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { Filter delegateToUse = this.delegate; if (delegateToUse == null) { synchronized(this.delegateMonitor) { delegateToUse = this.delegate; if (delegateToUse == null) { WebApplicationContext wac = this.findWebApplicationContext(); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?"); } //进行初始化 delegateToUse = this.initDelegate(wac); } this.delegate = delegateToUse; } } this.invokeDelegate(delegateToUse, request, response, filterChain); } //初始化方法 protected Filter initDelegate(WebApplicationContext wac) throws ServletException { //FilterChainProxy String targetBeanName = this.getTargetBeanName(); Assert.state(targetBeanName != null, "No target bean name set"); Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class); if (this.isTargetFilterLifecycle()) { delegate.init(this.getFilterConfig()); } return delegate; } }
FilterChainProxy
public class FilterChainProxy extends GenericFilterBean { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean clearContext = request.getAttribute(FILTER_APPLIED) == null; if (clearContext) { try { request.setAttribute(FILTER_APPLIED, Boolean.TRUE); this.doFilterInternal(request, response, chain); } finally { SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); } } else { this.doFilterInternal(request, response, chain); } } private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request); HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response); Listfilters = this.getFilters((HttpServletRequest)fwRequest); if (filters != null && filters.size() != 0) { FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters); vfc.doFilter(fwRequest, fwResponse); } else { if (logger.isDebugEnabled()) { logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list")); } fwRequest.reset(); chain.doFilter(fwRequest, fwResponse); } } private List getFilters(HttpServletRequest request) { Iterator var2 = this.filterChains.iterator(); SecurityFilterChain chain; do { if (!var2.hasNext()) { return null; } chain = (SecurityFilterChain)var2.next(); } while(!chain.matches(request)); return chain.getFilters(); } }
3、两个重要接口
UserDetailsService接口
当没有配置时,用户名和密码由spring security定义生成,我们需要自定义逻辑控制认证;
1、创建一个类 继承 UsernamePasswordAuthenticationFilter
2、实现 UserDetailsService接口,编写查询数据库过程,返回User对象(安全框架提供的对象)
3、重写 attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
4、成功调用successfulAuthentication,失败调用unsuccessfulAuthentication
PasswordEncoder接口
通过encode();对数据进行加密;
三、web权限方案
1、设置用户名、密码
方法一:通过配置文件设置;
application.properties文件如下所示:
spring.security.user.name=ithailin
spring.security.user.password=123456
方法二:通过配置类设置;
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //Security加密对象 BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); //密码加密 String password = passwordEncoder.encode("123456"); //设置用户名,密码,权限 auth.inMemoryAuthentication().withUser("ithailin").password(password).roles("admin"); } //创建一个BCryptPasswordEncoder对象,否则使用BCryptPasswordEncoder对象会报错 java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" @Bean public PasswordEncoder password(){ return new BCryptPasswordEncoder(); } }
方法三:自定义实现类设置
1、创建配置类,设置使用哪个UserDetailsService实现类
@Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter { @Autowired MyUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); } //创建一个BCryptPasswordEncoder对象,否则使用BCryptPasswordEncoder对象会报错 java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" @Bean public PasswordEncoder password(){ return new BCryptPasswordEncoder(); } }
2、编写配置类,返回User对象,User对象包含用户名、密码、权限
@Service public class MyUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //权限集合 Listauthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); //权限参数不能为null return new User("ithailin",new BCryptPasswordEncoder().encode("123456"),authorities); } }
拓展
设置自定义登录页面、不进行拦截的请求等相关配置:重写 protected void configure(HttpSecurity http){};修改配置类如下所示:
@Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter { @Autowired MyUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); } //创建一个BCryptPasswordEncoder对象,否则使用BCryptPasswordEncoder对象会报错 java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" @Bean public PasswordEncoder password(){ return new BCryptPasswordEncoder(); } //设置自定义登录表单 @Override protected void configure(HttpSecurity http) throws Exception { //自定义登录页面 http.formLogin() //登录页面设置 .loginPage("/login.html") //登录访问路径 .loginProcessingUrl("/user/login") //登录成功之后跳转路径 .defaultSuccessUrl("/test/index").permitAll() .and().authorizeRequests() //设置哪些路径可以直接访问,不需要认证 .antMatchers("/","/test/hello","/user/login").permitAll() .anyRequest().authenticated() //关闭csrf防护 .and().csrf().disable(); } }
2、基于角色或权限进行访问控制
1、hasAuthority:当前主体若具有指定的权限(单个权限),返回true,否则返回false;
1、在配置类设置当前访问路径需要的权限
新增
//当前登录用户,只有具有admin权限才可以访问这个路径 .antMatchers("/test/index").hasAuthority("admin")
新增后如下所示:
@Configuration public class SecurityConfigTest extends WebSecurityConfigurerAdapter { @Autowired MyUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); } //创建一个BCryptPasswordEncoder对象,否则使用BCryptPasswordEncoder对象会报错 java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" @Bean public PasswordEncoder password(){ return new BCryptPasswordEncoder(); } //设置自定义登录表单 @Override protected void configure(HttpSecurity http) throws Exception { //自定义登录页面 http.formLogin() //登录页面设置 .loginPage("/login.html") //登录访问路径 .loginProcessingUrl("/user/login") //登录成功之后跳转路径 .defaultSuccessUrl("/test/index").permitAll() .and().authorizeRequests() //设置哪些路径可以直接访问,不需要认证 .antMatchers("/","/test/hello","/user/login").permitAll() //当前登录用户,只有具有admin权限才可以访问这个路径 .antMatchers("/test/index").hasAuthority("admin") .anyRequest().authenticated() //关闭csrf防护 .and().csrf().disable(); } }
2、在UserDetailsService中loadUserByUsername给用户新增权限
//给用户设置权限 Listauthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
UserDetailsService如下所示:
@Service public class MyUserDetailsService implements UserDetailsService { @Autowired UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //调用userMapper方法,根据用户名查询数据库 QueryWrapperwrapper = new QueryWrapper<>(); //where username = ? wrapper.eq("username",username); com.ithailin.pojo.User user = userMapper.selectOne(wrapper); if (user == null){ //数据库没有用户名,认证失败 throw new UsernameNotFoundException("用户名不存在!"); } //给用户设置权限 List authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); //权限参数不能为null return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),authorities); } }
2、hasAnyAuthority:
未完待续。。。