本文主要研究一下几种自定义spring security的方式

主要方式

  • 自定义UserDetailsService
  • 自定义passwordEncoder
  • 自定义filter
  • 自定义AuthenticationProvider
  • 自定义AccessDecisionManager
  • 自定义securityMetadataSource
  • 自定义access访问控制
  • 自定义authenticationEntryPoint
  • 自定义多个WebSecurityConfigurerAdapter

自定义UserDetailsService

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {//......@Bean@Overrideprotected UserDetailsService userDetailsService(){InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser(User.withUsername("demoUser1").password("123456").authorities("ROLE_USER","read_x").build());manager.createUser(User.withUsername("admin").password("123456").authorities("ROLE_ADMIN").build());return manager;}
}
复制代码

通过重写userDetailsService()方法自定义userDetailsService。这里展示的是InMemoryUserDetailsManager。 spring security内置了JdbcUserDetailsManager,可以自行扩展

自定义passwordEncoder

自定义密码的加密方式,实例如下

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {//......@Beanpublic DaoAuthenticationProvider authenticationProvider() {final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();authProvider.setUserDetailsService(userDetailsService);authProvider.setPasswordEncoder(encoder());return authProvider;}@Beanpublic PasswordEncoder encoder() {return new BCryptPasswordEncoder(11);}
}
复制代码

自定义filter

自定义filter离不开对spring security内置filter的顺序的认知:

Standard Filter Aliases and Ordering

spring security内置的各种filter顺序如下:

Alias Filter Class Namespace Element or Attribute
CHANNEL_FILTER ChannelProcessingFilter http/intercept-url@requires-channel
SECURITY_CONTEXT_FILTER SecurityContextPersistenceFilter http
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter session-management/concurrency-control
HEADERS_FILTER HeaderWriterFilter http/headers
CSRF_FILTER CsrfFilter http/csrf
LOGOUT_FILTER LogoutFilter http/logout
X509_FILTER X509AuthenticationFilter http/x509
PRE_AUTH_FILTER AbstractPreAuthenticatedProcessingFilter Subclasses N/A
CAS_FILTER CasAuthenticationFilter N/A
FORM_LOGIN_FILTER UsernamePasswordAuthenticationFilter http/form-login
BASIC_AUTH_FILTER BasicAuthenticationFilter http/http-basic
SERVLET_API_SUPPORT_FILTER SecurityContextHolderAwareRequestFilter http/@servlet-api-provision
JAAS_API_SUPPORT_FILTER JaasApiIntegrationFilter http/@jaas-api-provision
REMEMBER_ME_FILTER RememberMeAuthenticationFilter http/remember-me
ANONYMOUS_FILTER AnonymousAuthenticationFilter http/anonymous
SESSION_MANAGEMENT_FILTER SessionManagementFilter session-management
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter http
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor http
SWITCH_USER_FILTER SwitchUserFilter N/A

内置的认证filter

  • UsernamePasswordAuthenticationFilter

参数有username,password的,走UsernamePasswordAuthenticationFilter,提取参数构造UsernamePasswordAuthenticationToken进行认证,成功则填充SecurityContextHolder的Authentication

  • BasicAuthenticationFilter

header里头有Authorization,而且value是以Basic开头的,则走BasicAuthenticationFilter,提取参数构造UsernamePasswordAuthenticationToken进行认证,成功则填充SecurityContextHolder的Authentication

  • AnonymousAuthenticationFilter

给没有登陆的用户,填充AnonymousAuthenticationToken到SecurityContextHolder的Authentication

定义自己的filter

可以像UsernamePasswordAuthenticationFilter或者AnonymousAuthenticationFilter继承GenericFilterBean,或者像BasicAuthenticationFilter继承OncePerRequestFilter。 关于GenericFilterBean与OncePerRequestFilter的区别可以见这篇spring mvc中的几类拦截器对比

自定义filter主要完成功能如下:

  • 提取认证参数
  • 调用认证,成功则填充SecurityContextHolder的Authentication,失败则抛出异常

实例

public class DemoAuthFilter extends GenericFilterBean {private final AuthenticationManager authenticationManager;public DemoAuthFilter(AuthenticationManager authenticationManager) {this.authenticationManager = authenticationManager;}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;String token = httpServletRequest.getHeader("app_token");if(StringUtils.isEmpty(token)){httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "invalid token");return ;}try {Authentication auth = authenticationManager.authenticate(new WebToken(token));SecurityContextHolder.getContext().setAuthentication(auth);filterChain.doFilter(servletRequest, servletResponse);} catch (AuthenticationException e) {httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());}}
}
复制代码

设置filter顺序

上面定义完filter之后,然后就要将它放置到filterChain中

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {//......@Overrideprotected void configure(HttpSecurity http) throws Exception {http.addFilterBefore(new DemoAuthFilter(authenticationManager()), BasicAuthenticationFilter.class);http.csrf().disable();http.logout().disable();http.sessionManagement().disable();}
}
复制代码

这里把他添加在BasicAuthenticationFilter之前,当然可以根据情况直接替换UsernamePasswordAuthenticationFilter

http.addFilterAt(new DemoAuthFilter(authenticationManager()),UsernamePasswordAuthenticationFilter.class);
复制代码

自定义AuthenticationProvider

AuthenticationManager接口有个实现ProviderManager相当于一个provider chain,它里头有个List providers,通过provider来实现认证。

public Authentication authenticate(Authentication authentication)throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;Authentication result = null;boolean debug = logger.isDebugEnabled();for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}//......try {result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException e) {prepareException(e, authentication);// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow e;}catch (InternalAuthenticationServiceException e) {prepareException(e, authentication);throw e;}catch (AuthenticationException e) {lastException = e;}}//......}
复制代码

AuthenticationProvider通过supports方法来标识它是否能够处理这个类型的Authentication。 AnonymousAuthenticationFilter构造的是AnonymousAuthenticationToken,由AnonymousAuthenticationProvider来处理

public class AnonymousAuthenticationProvider implements AuthenticationProvider,MessageSourceAware {//......public boolean supports(Class<?> authentication) {return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));}
}
复制代码

UsernamePasswordAuthenticationFilter,BasicAuthenticationFilter构造的是UsernamePasswordAuthenticationToken,由DaoAuthenticationProvider(其父类为AbstractUserDetailsAuthenticationProvider)来处理

public abstract class AbstractUserDetailsAuthenticationProvider implementsAuthenticationProvider, InitializingBean, MessageSourceAware {//......public boolean supports(Class<?> authentication) {return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));}
}
复制代码

像上面我们自定义了WebToken,其实例如下:

可以实现Authentication接口,或者继承AbstractAuthenticationToken

public class WebToken extends AbstractAuthenticationToken {private final String token;public WebToken(String token) {super(null);this.token = token;}@Overridepublic Object getCredentials() {return this.token;}@Overridepublic Object getPrincipal() {return null;}
}
复制代码

这里就自定义一下支持这类WebToken的AuthenticationProvider

AuthenticationProvider要实现的功能就是根据参数来校验是否可以登录通过,不通过则抛出异常;通过则获取其GrantedAuthority填充到authentication中 如果是继承了AbstractAuthenticationToken,则是填充其authorities属性 前面自定义的DemoAuthFilter会在登陆成功之后,将authentication写入到SecurityContextHolder的context中 可以实现AuthenticationProvider接口,或者继承AbstractUserDetailsAuthenticationProvider(默认集成了preAuthenticationChecks以及postAuthenticationChecks)

@Service
public class MyAuthProvider implements AuthenticationProvider {//...@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {//......}@Overridepublic boolean supports(Class<?> authenticationClass) {return return (WebToken.class.isAssignableFrom(authenticationClass));}
}
复制代码

自定义AccessDecisionManager

前面有filter处理了登录问题,接下来是否可访问指定资源的问题就由FilterSecurityInterceptor来处理了。而FilterSecurityInterceptor是用了AccessDecisionManager来进行鉴权。

AccessDecisionManager的几个实现:

  • AffirmativeBased(spring security默认使用)

只要有投通过(ACCESS_GRANTED)票,则直接判为通过。如果没有投通过票且反对(ACCESS_DENIED)票在1个及其以上的,则直接判为不通过。

  • ConsensusBased(少数服从多数)

通过的票数大于反对的票数则判为通过;通过的票数小于反对的票数则判为不通过;通过的票数和反对的票数相等,则可根据配置allowIfEqualGrantedDeniedDecisions(默认为true)进行判断是否通过。

  • UnanimousBased(反对票优先)

无论多少投票者投了多少通过(ACCESS_GRANTED)票,只要有反对票(ACCESS_DENIED),那都判为不通过;如果没有反对票且有投票者投了通过票,那么就判为通过.

实例

其自定义方式之一可以参考聊聊spring security的role hierarchy,展示了如何自定义AccessDecisionVoter。

自定义securityMetadataSource

主要是通过ObjectPostProcessor来实现自定义,具体实例可参考spring security动态配置url权限

自定义access访问控制

对authorizeRequests的控制,可以使用permitAll,anonymous,authenticated,hasAuthority,hasRole等等

                .antMatchers("/login","/css/**", "/js/**","/fonts/**","/file/**").permitAll().antMatchers("/anonymous*").anonymous().antMatchers("/session").authenticated().antMatchers("/login/impersonate").hasAuthority("ROLE_ADMIN").antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/auth/*").hasAnyRole("ADMIN","USER")
复制代码

这些都是利用spring security内置的表达式。像hasAuthority等,他们内部还是使用access方法来实现的。因此我们也可以直接使用access,来实现最大限度的自定义。

实例

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login/**","/logout/**").permitAll().anyRequest().access("@authService.canAccess(request,authentication)");}
}
复制代码

这个就有点像使用spring EL表达式,实现实例如下

@Component
public class AuthService {public boolean canAccess(HttpServletRequest request, Authentication authentication) {Object principal = authentication.getPrincipal();if(principal == null){return false;}if(authentication instanceof AnonymousAuthenticationToken){//check if this uri can be access by anonymous//return}Set<String> roles = authentication.getAuthorities().stream().map(e -> e.getAuthority()).collect(Collectors.toSet());String uri = request.getRequestURI();//check this uri can be access by this rolereturn true;}
}
复制代码

自定义authenticationEntryPoint

比如你想给basic认证换个realmName,除了再spring security配置中指定

security.basic.realm=myrealm
复制代码

也可以这样

   httpBasic().authenticationEntryPoint(createBasicAuthEntryPoint("myrealm"))public static BasicAuthenticationEntryPoint createBasicAuthEntryPoint(String realmName){BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();entryPoint.setRealmName(realmName);return entryPoint;}
复制代码

自定义多个WebSecurityConfigurerAdapter

spring security使用antMatchers不支持not的情况,因此可以自定义多个WebSecurityConfigurerAdapter,利用order优先级来实现匹配的覆盖,具体可以参考这篇文章Multiple Entry Points in Spring Security

小结

还有其他自定义的方式,等后续有发现再补上。

doc

  • Spring Security password hashing example
  • spring mvc中的几类拦截器对比
  • spring security 自定义认证
  • Spring Security Tutorial
  • Security with Spring
  • 话说Spring Security权限管理(源码)

spring security自定义指南相关推荐

  1. 3.Spring Security 自定义用户认证

    Spring Security自定义用户认证 自定义认证过程 自定义认证的过程需要实现Spring Security提供的UserDetailService接口,该接口只有一个抽象方法loadUser ...

  2. spring security 自定义认证登录

    spring security 自定义认证登录 1.概要 1.1.简介 spring security是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,以此来管理权限认证等. 1 ...

  3. Spring Security——自定义认证错误提示信息及自适应返回格式解决方案

    解决方案 package com.hailiu.web.handler;import com.hailiu.model.Log; import com.hailiu.web.bean.Response ...

  4. Spring Security 自定义资源服务器实践

    相关文章: OAuth2的定义和运行流程 Spring Security OAuth实现Gitee快捷登录 Spring Security OAuth实现GitHub快捷登录 Spring Secur ...

  5. Spring Security 自定义接口登出

    Spring Security 自定义接口登出 使用Security提供的工具,在接口内登出用户. 工具类代码 // 注入 tokenStore@Autowiredprivate final Toke ...

  6. Spring Security自定义登录验证及登录返回结果

    Spring Security自定义登录验证及登录返回结果 一.功能描述 二.处理逻辑 简单流程 自定义UserDetails 自定义UserDetailsDAO 自定义UserDetailsServ ...

  7. (二)Spring Security自定义登录成功或失败处理器

    目录 一:创建登录成功处理器 二:创建登录失败处理器 三:添加处理器 三. 项目地址 我们接着上一章 Spring Security最简单的搭建,进行开发 LoginSuccessHandler 和L ...

  8. spring security:自定义认证成功处理器

    使用spring认证登录,登录之后,一般还需要进行其他处理,例如:保存登录时间.登录ip到数据库,缓存用户信息到redis数据库等等,这些操作可以通过自定义一个登录成功处理器来处理. 自定义认证成功处 ...

  9. Spring Security:自定义登录页面

    本文来说下Spring Security中如何自定义登录页面 文章目录 准备工作 自定义登录界面 本文小结 准备工作 添加模板引擎 这里使用了thymeleaf模板引擎,在pom.xml进行添加: & ...

最新文章

  1. 【OpenCV】内核的形状函数使用记录
  2. Libevent实现TCP服务循环监听
  3. 检测到目标URL存在http host头攻击漏洞
  4. uvc音频传输协议_干货|太全了!常见的音频接口,你知道几种?
  5. 向日葵远程使用备忘录
  6. 成功,要“借力”,不要“尽力”(深刻!)
  7. tga文件怎么打开_教你win10系统怎么打开stp文件
  8. c语言闰年的判断条件DS1302,DS1302驱动程序(平年和闰年天数自动调整)
  9. JAVA遇见HTML——JSP篇(JavaBeans)
  10. S5PV210体系结构与接口03:GPIO编程
  11. 一次百万长连接压测 Nginx OOM 的问题排查分析
  12. Win10预览版已经支持WSL的带GUI的应用
  13. win7计算机里桌面菜单没有反应,win7系统桌面任务栏假死无反应的设置办法
  14. AIX平台安装python
  15. torch.optim.lr_scheduler源码和cosine学习率策略学习
  16. 高通MSM8916后面的0VV 1VV 3VV 等代表什么?
  17. 快门速度,光圈,感光度
  18. python编号/排序/翻转/并行迭代使用场景及作用
  19. 计算机毕业设计Node.js+Vue交通违章举报平台(程序+源码+LW+部署)
  20. 深入浅出计算机组成原理04-穿越功耗墙,我们该从哪些方面提升“性能”?

热门文章

  1. android 8.0 l2tp问题,【Win】使用L2TP出現809錯誤
  2. 计算机藏文论文,计算机论文:藏文陈述句复述生成之计算机研究.docx
  3. 9550电机_电机转矩与性能的关系
  4. c语言sprt的程序怎么用,sqrt函数在c语言中怎么用?
  5. 频率计c语言程序,数字频率计中C语言编程的研究
  6. 全国大学生智能汽车竞赛介绍-2020
  7. 今天诞生了智能车竞赛华南赛区三项最好成绩
  8. pointcloud 转ptr_ROS学习笔记(三)sensor_msgs::LaserScan转pcl::PointCloud
  9. microsoft mysql下载_Microsoft SQL Server 2018
  10. stm32硬件消抖_在 STM32 上使用 C++ 指南