springsecurity自定义指南(代码片段)

jpfss jpfss     2022-12-19     502

关键词:

本文主要研究一下几种自定义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 

    //......

    @Bean
    public DaoAuthenticationProvider authenticationProvider() 
        final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(encoder());
        return authProvider;
    

    @Bean
    public 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/[email protected]
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;
    

    @Override
    public 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 
    //......
    @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 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 status
                throw 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 implements
        AuthenticationProvider, 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;
    

    @Override
    public Object getCredentials() 
        return this.token;
    

    @Override
    public 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 
    //...
    @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 

    @Override
    protected 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 role

        return 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

原文地址:https://segmentfault.com/a/1190000012560773

《springsecurity框架专题》-07自定义加密(代码片段)

...1.MD5加密工具类2.自定义加密器3.修改security配置文件虽然springsecurity已经提供了比较完善的加密机制,但是有时根据业务需求需要定制自己的加密方式。springsecurity提供了加密扩充的接口,下文主要介绍如何在springsecurity中添加自... 查看详情

springsecurity:自定义配置(代码片段)

接着上节的讲,在添加了@EnableWebSecurity注解后,如果需要自定义一些配置,则需要和继承WebSecurityConfigurerAdapter后,覆盖某些方法。我们来看一下WebSecurityConfigurerAdapter中哪些方法可以重写,需要重写。(1)WebSecurity默认是一个空... 查看详情

springsecurity认证详解(代码片段)

文章目录自定义认证逻辑案例相关API介绍UserDetailsService自定义逻辑PasswordEncoder密码编码器接口代码实现编写配置类编写认证服务实现类代码测试自定义页面案例自定义登录页面编写登录页面修改配置类编写控制器认证过程其他常... 查看详情

四springsecurity使用自定义认证页面(代码片段)

一、SpringSecurity使用自定义认证页面1.1、在SpringSecurity主配置文件中指定认证页面配置信息spring-security.xml<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/ 查看详情

四springsecurity使用自定义认证页面(代码片段)

一、SpringSecurity使用自定义认证页面1.1、在SpringSecurity主配置文件中指定认证页面配置信息spring-security.xml<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/ 查看详情

springboot整合springsecurity之自定义退出(代码片段)

一security默认的退出Springsecurity默认实现了logout退出,访问/logout: 实现逻辑:点击“LogOut”退出成功。退出后访问其它url判断是否成功退出。二自定义退出2.1配置文件中配置在WebSecurityConfifig的protectedvoidconfifigure(HttpSe... 查看详情

《springsecurity框架专题》-03实现自定义登录界面(代码片段)

...目录1.页面准备1.1.login.jsp页面1.2.home.jsp页面1.3.其他页面2.SpringSecurity相关配置2.1.配置认证信息3.登录测试4.关闭csrf拦截5.csrf防护5.1.CsrfFilter源码查看5.2.在认证页面携带token请求6.注销前面通过入门案例介绍,我们发现在SpringSecurity... 查看详情

springboot整合springsecurity之自定义认证(代码片段)

...义认证页面1.1说明1.如果用户没有自定义登录页面,springsecurity默认会启动自身内部的登录页面,尽管自动生成的登录页面很方便快速启动和运行,但大多数应用程序都希望定义自己的登录页面。1.2自定义登录页面在... 查看详情

springsecurity调用过程;及自定义改造(代码片段)

...就可以带着这个token来请求,代表自己是合法请求。springsecurity责任链请求->UsernamePasswordAuthenticationFilter(判断用户名密码是否正确)->ExceptionTra 查看详情

springsecurity基础功能详解(代码片段)

...、Session管理  八、退出操作  首先说明本文所用的SpringSecurity版本是2.0.4.RELEASE。下面逐个功能介绍。  一、默认情况  1、构建与配置   1)pom.xml<dependency>< 查看详情

[springsecurity]web权限方案_用户认证_自定义用户登录页面(代码片段)

在配置类中实现相关的配置@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsExceptionhttp.formLogin()//自定义自己编写的登陆页面.loginPage("/login.html")//登陆页面设置.loginProcessingUrl("/user/login")//登陆访问路 查看详情

java——springsecurity——自定义配置的一些补充:anonymous匿名用户重写loaduserbyusername()方法自定义websecurityconfig配置等(代码片段)

JAVA——springSecurity——自定义配置的一些补充:Anonymous匿名用户、重写loadUserByUsername()方法、自定义WebSecurityConfig配置等三、一些细节补充(1)Anonymous匿名用户(2)重写loadUserByUsername()方法ÿ 查看详情

java——springsecurity自定义配置的一些补充:anonymous匿名用户重写loaduserbyusername()方法自定义websecurityconfig配置等(代码片段)

JAVA——springSecurity自定义配置的一些补充:Anonymous匿名用户、重写loadUserByUsername()方法、自定义WebSecurityConfig配置等三、一些细节补充(1)Anonymous匿名用户(2)重写loadUserByUsername()方法( 查看详情

springsecurity实现自定义登录和认证:使用自定义的用户进行认证(代码片段)

1SpringSecurity1.1导入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>1.2编写配置类在spring最新版中禁用了WebSecurityConfigurerAdapter类,官方推荐采用配置... 查看详情

如何在springsecurity中自定义权限表达式(代码片段)

在前面的文章中,松哥已经和小伙伴们聊了SpringSecurity中的权限表达式了,还没看过的小伙伴们可以先看下,本文将在前文的基础上继续完善:SpringSecurity中,想在权限中使用通配符,怎么做?1.SpEL回顾... 查看详情

php[约会]-其他自定义字段演示。有关如何在appointmentslite中添加自定义字段的指南(代码片段)

查看详情

springsecurity自定义资源服务器实践(代码片段)

相关文章:OAuth2的定义和运行流程SpringSecurityOAuth实现Gitee快捷登录SpringSecurityOAuth实现GitHub快捷登录SpringSecurity的过滤器链机制SpringSecurityOAuthClient配置加载源码分析SpringSecurity内置过滤器详解为什么加载了两个OAuth2AuthorizationReq... 查看详情

gateway整合springsecurity鉴权(代码片段)

...理5.自定义鉴权管理2.springsecruity密码判断3.流程3.尚硅谷springsecurity3.3两个重要接口 3.3.1认证 3.3.2自定义登入 403设计​编辑 3.5用户注销3.6免登陆4过滤器方式4.1maven---直接引用--配置4.2entity4.3filter  4.3.1 访问过滤器 获取request--... 查看详情