前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Security 系列(1)

Spring Security 系列(1)

作者头像
求和小熊猫
发布2022-06-12 17:23:19
9360
发布2022-06-12 17:23:19
举报

Spring Security

文章目录

什么是 Spring Security

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security 的核心组件

本节介绍Spring Security在Servlet身份验证中使用的主要架构组件。如果需要解释这些部分如何组合在一起的具体流程,请查看特定于身份验证机制的部分。

  • SecurityContextHolder - SecurityContextHolder是Spring Security存储被身份验证者的详细信息的地方。
  • SecurityContext - 从 SecurityContextHolder 获取,包含当前经过身份验证的用户的身份验证。
  • Authentication - 可以是 AuthenticationManager 的输入,以提供用户提供的用于身份验证的凭据(Token),也可以是 SecurityContext 中的当前用户。
  • GrantedAuthority - 授予身份验证主体的权限(即角色、作用域等)
  • AuthenticationManager - 定义Spring Security过滤器如何执行身份验证的API。
  • ProviderManager - AuthenticationManager 最常见的实现。
  • AuthenticationProvider - 提供程序管理器用于执行特定类型的身份验证。
  • Request Credentials with AuthenticationEntryPoint - 用于从客户端请求凭据(即重定向到登录页面、发送 WWW 身份验证响应等)
  • AbstractAuthenticationProcessingFilter - 用于身份验证的基本过滤器。这也很好地了解了身份验证的高级流程以及各个部分如何协同工作。

关系的简要示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sfrsO9wC-1654844540950)(https://app.yinxiang.com/FileSharing.action?hash=1/c73daea197b506c37fd4b8a220689411-31431)]

SecurityContextHolder 示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JIauoIE6-1654844540952)(https://app.yinxiang.com/FileSharing.action?hash=1/e5736a02d5ccb739ae0bc4698fa58f48-22163)]

Spring Security 的加密工具

  • BCryptPasswordEncoder(bcrypt 算法加密)
  • Argon2PasswordEncoder(argon2 算法加密)
  • Pbkdf2PasswordEncoder(pbkdf2 算法加密)
  • SCryptPasswordEncoder(scrypt 算法加密)

Spring Security 的架构

这是 WebSecurityConfigurerAdapter 的初始化加载流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6FcKHhY-1654844540952)(https://app.yinxiang.com/FileSharing.action?hash=1/ad820c1970141c44e14862330e49d6d5-73403)]

从上侧流程图我们可以大致了解 Spring Security 的架构应该如下

image
image

其中 FilterChain 中包含各种各样的 filter

登陆成功流程大致如下图

img
img
  1. 当用户提交他们的凭据时, AbstractAuthenticationProcessingFilter 将从被验证的 HttpServletRequest 处创建一个 AuthenticationAuthentication 类型的创建工作则依赖于 AbstractAuthenticationProcessingFilter 的子类。例如 UsernamePasswordAuthenticationFilter 从已提交的 HttpServletRequest 中获取用户名密码并创建 UsernamePasswordAuthenticationToken
  2. 接下来,将 Authentication 传递给 AuthenticationManager 进行身份验证。
  3. 如果身份验证失败,
    • 清除 SecurityContextHolder
    • 调用 RememberMeServices.loginFail。如果没有配置 remeberme ,则不会进行任何操作
    • 调用 AuthenticationFailureHandler
  4. 如果身份验证成功,则Success。
    • SessionAuthenticationStrategy 收到新登录通知。
    • SecurityContextHolder 设置 Authentication 。稍后 SecurityContextPersistenceFilterSecurityContext 保存到HttpSession 中.
    • 调用 RememberMeServices.loginSuccess 。如果没有配置 remeberme ,则不会进行任何操作
    • ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent 事件
    • 调用 AuthenticationSuccessHandler

Spring Security 的使用

引入 Spring Security

pom.xml

引入 Spring Security 时我们需要在 pom.xml 中添加

代码语言:javascript
复制
<!--Spring Security 包引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Spring Boot Web 包引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring Boot 测试包引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!--Spring Security 测试包引入-->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

application.yml

在 application.yml 中编写配置文件

代码语言:javascript
复制
server:
  port: 8088 # 服务器端口

配置 WebSecurityConfigurerAdapter

代码语言:javascript
复制
// Step1: 创建自己的 WebSecurityConfigurerAdapter
@Configuration
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    // Step2: 重写 configure 方法
    @Override
    public void configure(HttpSecurity http){
       
    }
}

添加密码加密器

配置类 WebSecurityConfigurerAdapter

代码语言:javascript
复制
// 注入密码加密器
@Bean
PasswordEncoder passwordEncoder(){
    // return new BCryptPasswordEncoder();
    // return new Argon2PasswordEncoder();
    // return new SCryptPasswordEncoder();
    return new Pbkdf2PasswordEncoder();
}

使用加密器对密码进行加密以及密码之间的匹配

代码语言:javascript
复制
String password = "RawPassword";
String pwdCrypt = passwordEncoder.encode(password);
passwordEncoder.matches(password,pwdCrypt);

配置安全策略

登陆成功的处理与配置

配置 WebSecurityConfigurerAdapter

代码语言:javascript
复制
    @Override
    public void configure(HttpSecurity http){
        try {
            http.formLogin()
                    .usernameParameter("usr")// 设定登陆时用户名的参数名,默认为 username
                    .passwordParameter("pwd")// 设定登陆时密码的参数名,默认为 password
                    .loginPage("login.html") // 设定登陆页面
                    .loginProcessingUrl("/login") // 处理登陆的 url
                    .successForwardUrl("/loginSuccess") // 登陆成功后跳转的 url
                    .successHandler(new AuthenticationSuccessHandler() { // 登陆成功的处理器
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        }
                    }).failureForwardUrl("/loginFailure") // 失败跳转的 url
                    .failureHandler(new AuthenticationFailureHandler() { // 失败的请求处理器
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                        }
                    });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
通过权限控制访问

配置 WebSecurityConfigurerAdapter

代码语言:javascript
复制
    @Override
    public void configure(HttpSecurity http){
        try {
            http.authorizeRequests()
                    .antMatchers(HttpMethod.POST,"/login").permitAll()
                    .antMatchers(HttpMethod.OPTIONS).permitAll()
                    .antMatchers("/status").permitAll() // 允许所有请求通过访问
                    .regexMatchers("*.png").permitAll() // 通过正则表达式进行匹配
                    .antMatchers("/userResource").hasAuthority("USER") // 具有某个权限后方可访问
                    .antMatchers("/userResource1").hasAnyAuthority("SYS","USER") // 具有任意一个权限后方可访问
                    .antMatchers("/roleResource").hasRole("role1") // 具有某个角色后方可访问,角色的权限名称为 “ROLE_” 前缀加上角色名称
                    .antMatchers("/roleResource1").hasAnyRole("role1","role2")// 具有任意一个角色后方可访问
                    .antMatchers("/accessAuthority").access("hasAnyAuthority(SYS,USER)")// 具有任意一个权限后方可访问
                    .antMatchers("/accessRoles").access("hasAnyRole(role1,role2)")// 具有任意一个角色后方可访问
                    .anyRequest().authenticated(); // 其余任何请求都需要经过访问
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
进行 Token 配置

配置 WebSecurityConfigurerAdapter

代码语言:javascript
复制
    @Override
    public void configure(HttpSecurity http){
        try {
            http.rememberMe().rememberMeParameter("remeberme")
                    .tokenRepository(new InMemoryTokenRepositoryImpl()) // 设置 Token 仓库,用于存储,更新,和获得 Token
                    .userDetailsService(username -> // 配置验证时的 UserDetailService
                            new User(username,"pwd", AuthorityUtils.commaSeparatedStringToAuthorityList("sys")))
                    .tokenValiditySeconds(1800);//token 有效时间
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

使用自定义的 UserDetailsService

UserDetailsService 接口

创建 MyUserDetailsService 类并且实现 UserDetailsService 接口

代码语言:javascript
复制
@Service
public class MyUserDetailsService implements UserDetailsService{
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = "pwd";
        String pwdCrypt = passwordEncoder.encode(password);// 模拟从数据中取出密码
        List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
        authorityList.add(new SimpleGrantedAuthority("ROLE_user"));
        // 这里只是模拟,实际可以与数据库进行交互获得用户权限
        UserDetails user = new User(username,pwdCrypt,authorityList);
        return user;
    }
}

配置 WebSecurityConfigurerAdapter

代码语言:javascript
复制
    // 注入创建的 UserService
    @Bean
    public MyUserDetailsService myUserDetailsService(){
        return new MyUserDetailsService();
    }
    
    // 重写 Configure 方法,使配置生效
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService()) // 注入 UserDetailsService
                .passwordEncoder(passwordEncoder());
    }
    // 将代理 AuthenticationManager 注册成 Bean,供直接使用
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

Ps: 一般来说 UserDetails 一般是由 UserDetailsService 来返回的。DaoAuthenticationProvider 验证 UserDetails 并返回一个 Authentication 对象

使用自定义的 AuthenticationEntryPoint

AuthenticationEntryPoint 他所建模的概念是:“认证入口点”。它在用户请求处理过程中遇到认证异常时,被ExceptionTranslationFilter用于开启特定认证方案(authentication schema)的认证流程。

这为登陆失败时的流程:

image
image
  1. 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的其余部分
  2. 如果用户未通过身份验证或它是一个AuthenticationException,则开始身份验证。
    • 清除 SecurityContextHolder
    • HttpServletRequest保存在RequestCache. 当用户成功认证后,RequestCache用于重放原始请求。
    • AuthenticationEntryPoint用于从客户端请求凭据。例如,它可能会重定向到登录页面或发送WWW-Authenticate标头。
  3. 否则,如果是AccessDeniedException,则拒绝访问。被AccessDeniedHandler调用来处理拒绝访问。

AuthenticationEntryPoint 接口

创建一个 MyAuthenticationEntryPoint 类,并实现 AuthenticationEntryPoint 接口

代码语言:javascript
复制
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); // 自定义处理返回信息
    }
}

配置 WebSecurityConfigurerAdapter

代码语言:javascript
复制
    @Autowired
    MyAuthenticationEntryPoint authenticationEntryPoint; // 注入自己实现的 EntryPoint
    // Step2: 重写 configure 方法
    @Override
    public void configure(HttpSecurity http){
        try {
            http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
Spring Security 自带的 AuthenticationEntryPoint
  • Http403ForbiddenEntryPoint 设置响应状态字为403,并非触发一个真正的认证流程。通常在一个预验证(pre-authenticated authentication)已经得出结论需要拒绝用户请求的情况被用于拒绝用户请求。
  • HttpStatusEntryPoint 设置特定的响应状态字,并非触发一个真正的认证流程。
  • LoginUrlAuthenticationEntryPoint 根据配置计算出登录页面url,将用户重定向到该登录页面从而开始一个认证流程。
  • BasicAuthenticationEntryPoint 对应标准Http Basic认证流程的触发动作,向响应写入状态字401和头部WWW-Authenticate:"Basic realm="xxx"触发标准Http Basic认证流程。
  • DigestAuthenticationEntryPoint 对应标准Http Digest认证流程的触发动作,向响应写入状态字401和头部WWW-Authenticate:"Digest realm="xxx"触发标准Http Digest认证流程。
  • DelegatingAuthenticationEntryPoint 这是一个代理,将认证任务委托给所代理的多个AuthenticationEntryPoint对象,其中一个被标记为缺省AuthenticationEntryPoint。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-06-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Security
    • 文章目录
      • 什么是 Spring Security
        • Spring Security 的核心组件
        • Spring Security 的加密工具
        • Spring Security 的架构
      • Spring Security 的使用
        • 引入 Spring Security
        • 添加密码加密器
        • 配置安全策略
        • 使用自定义的 UserDetailsService
        • 使用自定义的 AuthenticationEntryPoint
    相关产品与服务
    多因子身份认证
    多因子身份认证(Multi-factor Authentication Service,MFAS)的目的是建立一个多层次的防御体系,通过结合两种或三种认证因子(基于记忆的/基于持有物的/基于生物特征的认证因子)验证访问者的身份,使系统或资源更加安全。攻击者即使破解单一因子(如口令、人脸),应用的安全依然可以得到保障。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档