Spring Security 自定义配置 WebSecurityConfigurerAdapter

转 https://www.cnblogs.com/storml/p/10968797.html

在添加了@EnableWebSecurity注解后,如果需要自定义一些配置,则需要和继承WebSecurityConfigurerAdapter后,覆盖某些方法。

我们来看一下WebSecurityConfigurerAdapter中哪些方法可以重写,需要重写。

(1)WebSecurity

默认是一个空方法,一般也不会再重写。

    public void configure(WebSecurity web) throws Exception { }

(2)HttpSecurity

默认的父类代码默认任何request都需要认证,使用默认的login page基于表单认证,使用HTTP基本认证。

    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin().and()
            .httpBasic();
    }

下面是一些自定义写法

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //@formatter:off
        http.authorizeRequests()
            // all users have access to these urls
            .antMatchers("/resources/**", "/signup", "/about").permitAll()
            // Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN"
            .antMatchers("/admin/**").hasRole("ADMIN")
            // Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA"
            .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
            //  Any URL that starts with "/group_a/" requires the user to have both "ROLE_ADMIN" or "ROLE_GROUP_A"
            .antMatchers("/admin/**").hasAnyRole("ADMIN", "GROUP_A")
            // Any URL that has not already been matched on only requires that the user be authenticated
            .anyRequest().authenticated()
        .and().formLogin()
             // all users have access to custom login page
            .loginPage("/login").permitAll()
        .and().logout()
            // customize logout url
            .logoutUrl("/my/logout")
            // customize logout success url
            .logoutSuccessUrl("/my/index")
            // specify a custom LogoutSuccessHandler. If this is specified, logoutSuccessUrl() is ignored
            .logoutSuccessHandler(logoutSuccessHandler)
            // invalidate the HttpSession at the time of logout. This is true by default
            .invalidateHttpSession(true)
            // Adds a LogoutHandler. SecurityContextLogoutHandler is added as the last LogoutHandler by default
            .addLogoutHandler(logoutHandler)
            // Allows specifying the names of cookies to be removed on logout success
            .deleteCookies()
        .and().rememberMe()
            // Add remember me function and valid date.
            .key("uniqueAndSecret")
            .tokenValiditySeconds(60 * 60 * 24 * 7);
        //@formatter:on
    }

(3)AuthenticationManagerBuilder

默认是这样写的:

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        this.disableLocalConfigureAuthenticationBldr = true;
    }

它其实默认使用DefaultPasswordEncoderAuthenticationManagerBuilder这个Builder及自动配置的UserDetails和UserDetailsService。

    protected AuthenticationManager authenticationManager() throws Exception {
        if (!authenticationManagerInitialized) {
            // [1]如果覆盖configure()方法,则disableLocalConfigureAuthenticationBldr为false
            // [2]如果是默认的configure()方法,disableLocalConfigureAuthenticationBldr还是true
            configure(localConfigureAuthenticationBldr);
            if (disableLocalConfigureAuthenticationBldr) {
                authenticationManager = authenticationConfiguration
                        .getAuthenticationManager();  // [2]
            }
            else {
                authenticationManager = localConfigureAuthenticationBldr.build(); // [1]
            }
            authenticationManagerInitialized = true;
        }
        return authenticationManager;
    }

如果被覆盖,虽然还是使用的DefaultPasswordEncoderAuthenticationManagerBuilder,但是我们可以使用UserDetailsManagerConfigurer(的两个子类InMemoryUserDetailsManagerConfigurer,JdbcUserDetailsManagerConfigurer)来构建UserDetailsService及UserDetails。以InMemoryUserDetailsManagerConfigurer为例,下面是自定义的写法。

    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //@formatter:off
        // returns InMemoryUserDetailsManagerConfigurer
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        auth.inMemoryAuthentication()
            // create a UserDetailsBuilder and add to userBuilders
            .withUser("user").password("{bcrypt}" + encoder.encode("pass")).roles("USER")
            // returns InMemoryUserDetailsManagerConfigurer
            .and()
            // create a UserDetailsBuilder again and add to userBuilders
            .withUser("admin").password("{bcrypt}" + encoder.encode("pass")).roles("USER", "ADMIN");
        //@formatter:on
    }

框架要求密码必须加密,所以这里加了有关password encode的支持。

那么这段代码如何生成UserDetailsService及UserDetails的呢?流程如下:

[1] 调用AuthenticationManagerBuilder的inMemoryAuthentication()方法创建InMemoryUserDetailsManagerConfigurer,调用InMemoryUserDetailsManagerConfigurer的构造器时则会创建InMemoryUserDetailsManager(即UserDetailsService的实现类),最终经过层层父类(InMemoryUserDetailsManagerConfigurer -> UserDetailsManagerConfigurer -> UserDetailsServiceConfigurer -> AbstractDaoAuthenticationConfigurer)设定到AbstractDaoAuthenticationConfigurer中。

    public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
            throws Exception {
        return apply(new InMemoryUserDetailsManagerConfigurer<>());
    }
    public InMemoryUserDetailsManagerConfigurer() {
        super(new InMemoryUserDetailsManager(new ArrayList<>()));
    }
    protected AbstractDaoAuthenticationConfigurer(U userDetailsService) {
        this.userDetailsService = userDetailsService;
        provider.setUserDetailsService(userDetailsService);
        if (userDetailsService instanceof UserDetailsPasswordService) {
            this.provider.setUserDetailsPasswordService((UserDetailsPasswordService) userDetailsService);
        }
    }

[2] 调用AuthenticationManagerBuilder的apply()方法设定defaultUserDetailsService为[1]的InMemoryUserDetailsManager并且把[1]的InMemoryUserDetailsManagerConfigurer加到父类AbstractConfiguredSecurityBuilder的configurers list中

    private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder, ? extends UserDetailsService>> C apply(
            C configurer) throws Exception {
        this.defaultUserDetailsService = configurer.getUserDetailsService();
        return (C) super.apply(configurer);
    }

[3] 调用InMemoryUserDetailsManagerConfigurer的父类UserDetailsManagerConfigurer的withUser()方法生成多个UserDetailsBuilder放在userBuilders list中

    public final UserDetailsBuilder withUser(String username) {
        UserDetailsBuilder userBuilder = new UserDetailsBuilder((C) this);
        userBuilder.username(username);
        this.userBuilders.add(userBuilder);
        return userBuilder;
    }

[4] 当调用DefaultPasswordEncoderAuthenticationManagerBuilder的build()方法时,则会调用

    [4.1] 调用UserDetailsServiceConfigurer的configure()方法

    @Override
    public void configure(B builder) throws Exception {
        initUserDetailsService();
        super.configure(builder);
    }

    [4.2] 调用UserDetailsManagerConfigurer的initUserDetailsService()方法通过[3]的userBuilders创建User对象(UserDetails的实现类,并且从[1]中的AbstractDaoAuthenticationConfigurer获取UserDetailsService,并把UserDetails放到UserDetailsService中。

    @Override
    protected void initUserDetailsService() throws Exception {
        for (UserDetailsBuilder userBuilder : userBuilders) {
            getUserDetailsService().createUser(userBuilder.build());
        }
        for (UserDetails userDetails : this.users) {
            getUserDetailsService().createUser(userDetails);
        }
    }

下面是一些自定义写法:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //@formatter:off
        // returns InMemoryUserDetailsManagerConfigurer
        auth.inMemoryAuthentication()
            // create a UserBuilder and add to userBuilders
            .withUser("user").password("password").roles("USER")
            // returns InMemoryUserDetailsManagerConfigurer
            .and()
            // create a UserBuilder again and add to userBuilders
            .withUser("admin").password("password").roles("USER", "ADMIN");
        //@formatter:on
    }

(4)authenticationManagerBean()

我们覆盖了configure(AuthenticationManagerBuilder auth)后,我们使用了AuthenticationManagerBuilder 的实现类DefaultPasswordEncoderAuthenticationManagerBuilder,通过InMemoryUserDetailsManagerConfigurer创建自己的UserDetailsService的实现类InMemoryUserDetailsManager及User,系统还会默认给我们创建AuthenticationProvider的实现类DaoAuthenticationProvider。但是我们发现,这些对象并不是Spring Bean。所以我们可以通过覆盖该方法并且声明为一个Bean,这样就可以在项目中注入并使用这个Bean了。

    @Bean(name = "myAuthenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

通过父类的源码可以看到,实际上在调用时,创建了一个AuthenticationManager代理。

    public AuthenticationManager authenticationManagerBean() throws Exception {
        return new AuthenticationManagerDelegator(authenticationBuilder, context);
    }

(5)userDetailsServiceBean()

和(4)类似,Override this method to expose a UserDetailsService created from configure(AuthenticationManagerBuilder) as a bean. In general only thefollowing override should be done of this method:

    @Bean(name = "myUserDetailsService")
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }

(6) UserDetailsService

还记得第三章的UserDetailsService实现类是如何生成的吗?这里做一个简述:

[1] AuthenticationConfiguration中创建InitializeUserDetailsBeanManagerConfigurer Bean。

[2] build时调用InitializeUserDetailsBeanManagerConfigurer的内部类InitializeUserDetailsManagerConfigurer的configure()方法。

[3] 在ApplicationContext中获取UserDetailsService(by type),如果没有找到自定义的UserDetailsService Bean,则UserDetailsServiceAutoConfiguration生效,会lazy load一个InMemoryUserDetailsManager;反之,则使用我们自定义的UserDetailsService Bean。

 

在WebSecurityConfigurerAdapter中,userDetailsServiceBean()和userDetailsService()两个方法内容实际上都是一样的。都是获取当前环境中(自定义的或系统生成的InMemoryUserDetailsManager)的UserDetailsService的代理类。所以,该类一般不需要重写,如果想自定义自己的UserDetailsService,可以直接实现UserDetailsService接口,并且把该类声明为一个Spring Bean:

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // TODO Auto-generated method stub
        return null;
    }
}

 

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security 是一个基于 Spring 的安全框架,其支持多种认证方式、授权方式以及自定义安全过滤器等。在使用 Spring Security 进行开发时,自定义异常处理是非常重要的一部分。下面是 Spring Security 自定义异常处理的步骤: 1. 实现 AuthenticationEntryPoint 接口,该接口用于处理未认证用户的请求。通过实现该接口,可以自定义未认证用户的返回结果,例如返回自定义的 JSON 格式数据或者跳转到自定义的登录页面。 2. 实现 AccessDeniedHandler 接口,该接口用于处理已认证用户但没有权限访问资源的请求。同样可以通过实现该接口,自定义返回结果。 实现以上两个接口之后,需要在 Spring Security 配置中进行配置,将自定义实现的类添加到配置中即可生效。下面是一个示例配置: ``` @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyAuthenticationEntryPoint myAuthenticationEntryPoint; @Autowired private MyAccessDeniedHandler myAccessDeniedHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling() .authenticationEntryPoint(myAuthenticationEntryPoint) .accessDeniedHandler(myAccessDeniedHandler) .and() .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() .and() .formLogin().loginPage("/login").permitAll() .and() .logout().permitAll(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值