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);
        List filters = 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 {
        //权限集合
        List authorities = 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给用户新增权限

//给用户设置权限
List authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");

UserDetailsService如下所示:

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //调用userMapper方法,根据用户名查询数据库
        QueryWrapper wrapper = 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:

未完待续。。。

相关