基于Spring Security的身份验证与授权框架构建指南

安全 应用安全
Spring Security是一个强大的安全认证框架,它提供了丰富的安全功能来保护您的应用程序。通过本文的基础配置示例,你可以轻松地开始使用Spring Security来保护你的应用程序并实现身份验证和授权功能。

环境:SpringBoot2.7.12

1. 简介

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,用于保护基于Spring的应用程序。它采用AOP思想,基于servlet过滤器实现安全框架。

Spring Security具有以下优势:

  • 丰富的功能:Spring Security提供了完善的认证机制和方法级的授权功能,可以轻松地扩展以满足自定义需求。
  • 强大的社区支持:与所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求。此外,它拥有一个活跃的社区,提供了丰富的资源和支持。
  • 与Spring生态系统的集成:Spring Security与Spring生态系统中的其他组件紧密集成,如Spring MVC、Spring Boot等,使得在构建安全应用程序时更加便捷。
  • 高度可定制:Spring Security提供了大量的配置选项和扩展点,可以根据具体需求进行定制。

本篇文章将会介绍常用的配置及相应的扩展点。

2. 实战案例

2.1 自定义配置

在Spring Security5.7之前版本通过继承WebSecurityConfigurerAdapter类

public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

5.7之后版本

@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
  // ...
}

在这里每定义一个SecurityFilterChain所注入的HttpSecurity都是唯一的实例对象。

后续所有的配置都是基于Spring Security5.7.8版本

2.2 自定义验证器

@Component
public class MemeryAuthticationProvider implements AuthenticationProvider {


  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication ;
    Object principal = token.getPrincipal() ;
    Object credentials = token.getCredentials() ;
    User user = users.get(principal) ;
    // notNull(user, "用户名或密码错误") ;
    if (user == null) {
      return null ;
    }
    
    if (!user.getPassword().equals(credentials)) {
      throw new RuntimeException("密码错误") ;
    }
    return new UsernamePasswordAuthenticationToken(principal, credentials, user.getAuthorities()) ;
  }


  @Override
  public boolean supports(Class<?> authentication) {
    return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication) ;
  }


}

通过上面自定义认证器可以实现自己的验证逻辑。

2.2 自定义UserDetailsService

通过自定义UserDetailsService也可以实现对应的逻辑,只不过这种方式你还需要提供一个PasswordEncoder

@Bean
public UserDetailsService userDetailsService() {
  return new UserDetailsService() {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      return users.get(username) ;
    }
  };
}


@Bean
public PasswordEncoder passwordEncoder() {
  return new PasswordEncoder() {
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
      return rawPassword.equals(encodedPassword) ;
    }
    @Override
    public String encode(CharSequence rawPassword) {
      return rawPassword.toString() ;
    }
  };
}

2.3 拦截指定路径的请求

@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
  http.csrf().disable() ;
  // 该过滤器链,只匹配/api/**路径的请求
    http.requestMatcher(new AntPathRequestMatcher("/api/**")) ;
    // 也可以这样配置多个
    // http.requestMatchers().antMatchers("/api/**", "/admin/**") ;
  // ...
  DefaultSecurityFilterChain chain = http.build();
  return chain ;
}

2.4 拦截指定路径及权限

@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
  http.csrf().disable() ;
  http.authorizeHttpRequests().requestMatchers(new AntPathRequestMatcher("/api/save")).hasAnyRole("C") ;
  http.authorizeHttpRequests().requestMatchers(new AntPathRequestMatcher("/api/find")).hasAuthority("ROLE_U") ;
  DefaultSecurityFilterChain chain = http.build();
  return chain ;
}

2.5 自定义授权决定

http.authorizeHttpRequests(registry -> {
  registry.antMatchers("/api/{id}").access(new AuthorizationManager<RequestAuthorizationContext>() {
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication,
        RequestAuthorizationContext object) {
      Map<String, String> variables = object.getVariables() ;
      // 返回的路径是/api/666则进行拦截并且指定具有'D'的权限
      return new AuthorityAuthorizationDecision(variables.get("id").equals("666"), Arrays.asList(new SimpleGrantedAuthority("D"))) ;
    }
  }) ;
}) ;

2.6 自定义异常处理

http.exceptionHandling(customizer -> {
  customizer.accessDeniedHandler(new AccessDeniedHandler() {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
        AccessDeniedException accessDeniedException) throws IOException, ServletException {
      Map<String, Object> errors = new HashMap<>() ;
      response.setContentType("application/json;charset=utf-8") ;
      errors.put("code", -1) ;
      errors.put("status", response.getStatus()) ;
      errors.put("message", accessDeniedException.getMessage()) ;
      errors.put("details", ExceptionUtils.getMessage(accessDeniedException)) ;
      response.getWriter().println(new ObjectMapper().writeValueAsString(errors)) ;
    }
  }) ;
}) ;

2.7 自定义角色继承

@Bean
public RoleHierarchy hierarchyVoter() {
  RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
  // ADMIN自动拥有MANAGER的权限
  hierarchy.setHierarchy("ROLE_ADMIN > ROLE_MANAGER");
  return hierarchy ;
}

2.8 自定义退出登录逻辑

http.logout().logoutUrl("/logout").addLogoutHandler(new LogoutHandler() {
  @Override
  public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
    System.out.println("退出登录") ;
  }
}).logoutSuccessHandler(new LogoutSuccessHandler() {
  @Override
  public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
      throws IOException, ServletException {
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter() ;
    out.println("<h2>退出登录成功</h2>") ;
    out.close() ; 
  }
}) ;

2.9 自定义登录失败逻辑

http
  .formLogin()
  .failureHandler(new AuthenticationFailureHandler() {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception
      response.setContentType("application/json;charset=UTF-8") ;
      PrintWriter out = response.getWriter() ;
      out.println("{\"code\": -1, \"message\": \"" + getRootCause(exception).getMessage() + "\"}") ;
      out.close();
    }
  });

2.10 自定义过滤器

@Bean
public PackAuthenticationFilter packAuthenticationFilter() {
  return new PackAuthenticationFilter() ;
}
// 添加自定义过滤器到Security Filter Chain中
http.addFilterBefore(packAuthenticationFilter(), RequestCacheAwareFilter.class) ;

2.11 配置多个过滤器链

@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
  // 拦截/api/**
  http.requestMatcher(new AntPathRequestMatcher("/api/**")) ;
}
@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
  // 拦截/admin/**
  http.requestMatcher(new AntPathRequestMatcher("/admin/**")) ;
}

2.12 开启全局方法拦截

@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true)
public class SecurityConfig {}
// 使用
@GetMapping("/find")
@PreAuthorize("hasRole('GUEST')")
public Object find(HttpServletResponse response) throws Exception {
  return "find method invoke..." ;
}

2.13 国际化支持

@Bean
public ReloadableResourceBundleMessageSource messageSource() {
  ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
  // 这里会按照顺序查找的
  messageSource.addBasenames(
      "classpath:org/springframework/security/messages",
      "classpath:messages/messages"
  ) ;
  return messageSource ;
}

2.14 防止重复登录

http.sessionManagement().maximumSessions(1).expiredSessionStrategy(new SessionInformationExpiredStrategy() {
  @Override
  public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
    HttpServletResponse response = event.getResponse() ;
    response.setContentType("application/json;charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.println("{\"code\": -1, \"message\": \"会话已过期,或重复登录\"}");
    out.close();
  }
}) ;

注意:你的UserDetails必须重写equals和hashCode方法

总结:以上是在实际开发中经常会应用到的一些配置及相应功能的使用。Spring Security是一个强大的安全认证框架,它提供了丰富的安全功能来保护您的应用程序。通过本文的基础配置示例,你可以轻松地开始使用Spring Security来保护你的应用程序并实现身份验证和授权功能。

完毕!!!

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2024-05-06 00:00:00

ASP.NET授权机制

2021-08-27 10:40:49

GitHubGitLinux

2020-12-17 08:10:19

身份验证授权微服务

2012-10-23 16:12:35

2013-12-05 13:46:51

2022-03-14 13:53:01

基于风险的身份验证RBA身份验证

2014-06-27 10:31:52

2010-09-06 11:24:47

CHAP验证PPP身份验证

2022-03-23 12:02:48

身份验证RBAMFA

2010-07-17 00:57:52

Telnet身份验证

2012-04-10 09:36:58

2024-10-30 12:30:28

2011-02-21 10:54:45

2013-07-21 18:32:13

iOS开发ASIHTTPRequ

2013-12-06 09:18:44

2022-10-31 10:00:00

2009-04-09 23:44:08

软件身份验证用户

2010-07-19 17:30:47

2024-08-07 12:14:39

2010-11-30 15:31:38

SharePoint Kerberos
点赞
收藏

51CTO技术栈公众号