环境: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来保护你的应用程序并实现身份验证和授权功能。
完毕!!!
原文地址:https://mp.weixin.qq.com/s/dr2wLp8u8nuY47wuAyBV-w