序
本文主要研究一下几种自定义spring security的方式
主要方式
- 自定义UserDetailsService
- 自定义passwordEncoder
- 自定义filter
- 自定义AuthenticationProvider
- 自定义AccessDecisionManager
- 自定义securityMetadataSource
- 自定义access访问控制
- 自定义authenticationEntryPoint
- 自定义多个WebSecurityConfigurerAdapter
自定义UserDetailsService
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//......
@Bean
@Override
protected 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 {
<span class="hljs-comment">//......</span>
<span class="hljs-meta">@Bean</span>
public <span class="hljs-type">DaoAuthenticationProvider</span> authenticationProvider() {
<span class="hljs-keyword">final</span> <span class="hljs-type">DaoAuthenticationProvider</span> authProvider = <span class="hljs-keyword">new</span> <span class="hljs-type">DaoAuthenticationProvider</span>();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
<span class="hljs-keyword">return</span> authProvider;
}
<span class="hljs-meta">@Bean</span>
public <span class="hljs-type">PasswordEncoder</span> encoder() {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-type">BCryptPasswordEncoder</span>(<span class="hljs-number">11</span>);
}
}
自定义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 {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">AuthenticationManager</span> authenticationManager;
public <span class="hljs-type">DemoAuthFilter</span>(<span class="hljs-type">AuthenticationManager</span> authenticationManager) {
<span class="hljs-keyword">this</span>.authenticationManager = authenticationManager;
}
<span class="hljs-meta">@Override</span>
public void doFilter(<span class="hljs-type">ServletRequest</span> servletRequest, <span class="hljs-type">ServletResponse</span> servletResponse, <span class="hljs-type">FilterChain</span> filterChain) <span class="hljs-keyword">throws</span> <span class="hljs-type">IOException</span>, <span class="hljs-type">ServletException</span> {
<span class="hljs-type">HttpServletRequest</span> httpServletRequest = (<span class="hljs-type">HttpServletRequest</span>) servletRequest;
<span class="hljs-type">HttpServletResponse</span> httpServletResponse = (<span class="hljs-type">HttpServletResponse</span>) servletResponse;
<span class="hljs-type">String</span> token = httpServletRequest.getHeader(<span class="hljs-string">"app_token"</span>);
<span class="hljs-keyword">if</span>(<span class="hljs-type">StringUtils</span>.isEmpty(token)){
httpServletResponse.sendError(<span class="hljs-type">HttpServletResponse</span>.<span class="hljs-type">SC_UNAUTHORIZED</span>, <span class="hljs-string">"invalid token"</span>);
<span class="hljs-keyword">return</span> ;
}
<span class="hljs-keyword">try</span> {
<span class="hljs-type">Authentication</span> auth = authenticationManager.authenticate(<span class="hljs-keyword">new</span> <span class="hljs-type">WebToken</span>(token));
<span class="hljs-type">SecurityContextHolder</span>.getContext().setAuthentication(auth);
filterChain.doFilter(servletRequest, servletResponse);
} <span class="hljs-keyword">catch</span> (<span class="hljs-type">AuthenticationException</span> e) {
httpServletResponse.sendError(<span class="hljs-type">HttpServletResponse</span>.<span class="hljs-type">SC_UNAUTHORIZED</span>, e.getMessage());
}
}
}
设置filter顺序
上面定义完filter之后,然后就要将它放置到filterChain中
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//......
@Override
protected 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<AuthenticationProvider> providers,通过provider来实现认证。
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 implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
//......
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
像上面我们自定义了WebToken,其实例如下:
可以实现Authentication接口,或者继承AbstractAuthenticationToken
public class WebToken extends AbstractAuthenticationToken {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> token;
public <span class="hljs-type">WebToken</span>(<span class="hljs-type">String</span> token) {
<span class="hljs-keyword">super</span>(<span class="hljs-literal">null</span>);
<span class="hljs-keyword">this</span>.token = token;
}
<span class="hljs-meta">@Override</span>
public <span class="hljs-type">Object</span> getCredentials() {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.token;
}
<span class="hljs-meta">@Override</span>
public <span class="hljs-type">Object</span> getPrincipal() {
<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}
}
这里就自定义一下支持这类WebToken的AuthenticationProvider
AuthenticationProvider要实现的功能就是根据参数来校验是否可以登录通过,不通过则抛出异常;通过则获取其GrantedAuthority填充到authentication中
如果是继承了AbstractAuthenticationToken,则是填充其authorities属性
前面自定义的DemoAuthFilter会在登陆成功之后,将authentication写入到SecurityContextHolder的context中
可以实现AuthenticationProvider接口,或者继承AbstractUserDetailsAuthenticationProvider(默认集成了preAuthenticationChecks以及postAuthenticationChecks)
@Service
public class MyAuthProvider implements AuthenticationProvider {
//...
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//......
}
@Override
public 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 {
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">protected</span> void configure(<span class="hljs-type">HttpSecurity</span> http) <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> {
http
.authorizeRequests()
.antMatchers(<span class="hljs-string">"/login/**"</span>,<span class="hljs-string">"/logout/**"</span>)
.permitAll()
.anyRequest().access(<span class="hljs-string">"@authService.canAccess(request,authentication)"</span>);
}
}
这个就有点像使用spring EL表达式,实现实例如下
@Component
public class AuthService {
<span class="hljs-keyword">public</span> <span class="hljs-built_in">boolean</span> canAccess(HttpServletRequest request, Authentication authentication) {
<span class="hljs-built_in">Object</span> principal = authentication.getPrincipal();
<span class="hljs-keyword">if</span>(principal == <span class="hljs-literal">null</span>){
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-keyword">if</span>(authentication <span class="hljs-keyword">instanceof</span> AnonymousAuthenticationToken){
<span class="hljs-comment">//check if this uri can be access by anonymous</span>
<span class="hljs-comment">//return</span>
}
Set<<span class="hljs-built_in">String</span>> roles = authentication.getAuthorities()
.stream()
.map(e -> e.getAuthority())
.collect(Collectors.toSet());
<span class="hljs-built_in">String</span> uri = request.getRequestURI();
<span class="hljs-comment">//check this uri can be access by this role</span>
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
}
自定义authenticationEntryPoint
比如你想给basic认证换个realmName,除了再spring security配置中指定
security.basic.realm=myrealm
也可以这样
spring security使用antMatchers不支持not的情况,因此可以自定义多个WebSecurityConfigurerAdapter,利用order优先级来实现匹配的覆盖,具体可以参考这篇文章Multiple Entry Points in Spring Security
小结
还有其他自定义的方式,等后续有发现再补上。
doc
原文地址:https://segmentfault.com/a/1190000012560773(动态规划、栈)leetcode 84. Largest Rectangle in Histogram, 85. Maximal Rectangle
tmux 常见命令汇总
leetcode 221
leetcode 319 29
(贪心)leetcode 392. Is Subsequence, 771. Jewels and Stones, 463. Island Perimeter
leetcode 982 668
Python import 同文件夹下的py文件的函数,pycharm报错
Windows里Anaconda-Navigator无法打开的解决方案
Windows下 gpu版 Tensorflow 安装