1.Spring Security讲解
2.Spring Security总结

1.1 权限管理中的相关概念

1.1.1 主体
英文单词:principal
使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系
统谁就是主体。
1.1.2 认证
英文单词:authentication
权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是“主体”证
明自己是谁。
笼统的认为就是以前所做的登录操作。
1.1.3 授权
英文单词:authorization
将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功
能的能力。
所以简单来说,授权就是给用户分配权限。

1.2 SpringSecurity 基本原理

1.2.1 SpringSecurity 本质是一个过滤器链
从启动是可以获取到过滤器链:

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFil
ter
org.springframework.security.web.context.SecurityContextPersistenceFilter 
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter 
org.springframework.security.web.session.SessionManagementFilter 
org.springframework.security.web.access.ExceptionTranslationFilter 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor

代码底层流程:重点看三个过滤器:
1.FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。

super.beforeInvocation(fi) 表示查看之前的 filter 是否通过。
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务。

2.ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常
3.UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户
名,密码。

1.2.2 UserDetailsService 接口讲解

当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中
账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。

UserDetails 接口类实现

public interface UserDetails extends Serializable {
        // 表示获取登录用户所有权限
        Collection<? extends GrantedAuthority> getAuthorities();
        // 表示获取密码
        String getPassword();
        // 表示获取用户名
        String getUsername();
        // 表示判断账户是否过期
        boolean isAccountNonExpired();
        // 表示判断账户是否被锁定
        boolean isAccountNonLocked();
        // 表示凭证{密码}是否过期
        boolean isCredentialsNonExpired();
        // 表示当前用户是否可用
        boolean isEnabled();
}

1.2.3 PasswordEncoder 接口讲解

public interface PasswordEncoder {
    // 表示把参数按照特定的解析规则进行解析
String encode(CharSequence rawPassword);
    // 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹
    配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个
    参数表示存储的密码。
    boolean matches(CharSequence rawPassword, String encodedPassword);
    // 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回
    false。默认返回 false。
    default boolean upgradeEncoding(String encodedPassword) {
    return false; }
}

WX20221010-102044.png
WX20221010-102044.png

BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析
器。
BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单
向加密。可以通过 strength 控制加密强度,默认 10.
使用passwordEncoder.encode() 方法进行加密

1.3 SpringSecurity Web 权限方案

1.3.1 设置登陆的用户名和密码

方法一:通过配置文件
在application.properties添加下列配置,账号密码换成自己定义的

    spring.security.user.name=www
    spring.security.user.password=wwwwww

方法二:通过配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //重写设置账号密码的方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //密码加个密
        BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
        String encode = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("www").password(encode).roles("admin");
    }
    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }
}

方法三:自定义编写实现类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;

@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
    @Resource
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }
    }

1.3.2 基于权限进行访问控制
此处使用艿艿开源项目代码
官方推荐 RuoYi-Vue 全新 Pro 版本,优化重构所有功能。基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台

  /**
     * 配置 URL 的安全配置
     *
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
@Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 开启跨域
                //.cors().and()
                // CSRF 禁用,因为不使用 Session
                .csrf().disable()
                // 基于 token 机制,所以不需要 Session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 一堆自定义的 Spring Security 处理器
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(accessDeniedHandler).and()
                // 设置每个请求的权限
                .authorizeRequests()
                    // 登陆的接口,可匿名访问
                    .antMatchers(api("/login")).anonymous()
                    //.antMatchers(api("/customer/drugstore/updatehdDrugstorePlatform")).anonymous()
                    // 通用的接口,可匿名访问
                    .antMatchers(api("/system/captcha/**")).anonymous()
                    // 静态资源,可匿名访问
                    .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
                    // 文件的获取接口,可匿名访问
                    .antMatchers(api("/infra/file/get/**")).anonymous()
                    // Swagger 接口文档
                    .antMatchers("/swagger-ui.html").anonymous()
                    .antMatchers("/swagger-resources/**").anonymous()
                    .antMatchers("/webjars/**").anonymous()
                    .antMatchers("/*/api-docs").anonymous()
                    // Spring Boot Admin Server 的安全配置
                    .antMatchers(adminSeverContextPath).anonymous()
                    .antMatchers(adminSeverContextPath + "/**").anonymous()
                    // Spring Boot Actuator 的安全配置
                    .antMatchers("/actuator").anonymous()
                    .antMatchers("/actuator/**").anonymous()
                    // Druid 监控 TODO 芋艿:需要抽象出去
                    .antMatchers("/druid/**").anonymous()
                    // 短信回调 API TODO 芋艿:需要抽象出去
                    .antMatchers(api("/system/sms/callback/**")).anonymous()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        httpSecurity.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler);
        // 添加 JWT Filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }