No AuthenticationProvider found for org.***.PreAuthenticatedAuthenticationToken

框架:Spring security oauth2+MmongoDB

问题描述:请求自定义登陆接口和使用默认接口都都能正常返回token信息,但携带refresh_token换取token时,则报错。自定义登陆接口相关配置和错误信息如下。

ResourceConfig配置如下:

@EnableResourceServer
@Configuration
public class ResourceServerConfig  extends ResourceServerConfigurerAdapter {
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
    @Autowired
    private CustomAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private CustomAccessDeineHandler accessDeniedHandler;
    @Autowired
    private CustomLogoutSuccessHandler logoutSuccessHandler;
    @Autowired
    private TheCustomAuthenticationProvider theCustomAuthenticationProvider;
    @Value("${custom.app.formLogin.uri}")
    private String formLoginUri;
    @Autowired
    private PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()
                    .loginProcessingUrl(formLoginUri)//自定义登陆接口,输入相关信息,返回token
                    .successHandler(customAuthenticationSuccessHandler) //成功处理器 返回Token
                    .failureHandler(customAuthenticationFailureHandler)//失败处理器
                .and()
                    .csrf().disable() //模拟请求
                    .cors()
                .and()
                    .authorizeRequests()
                        .antMatchers("/sysUser/register","logout","/sysUser/restPwd").permitAll()
                        .anyRequest().authenticated()
                .and()
                    .exceptionHandling() // 配置被拦截时的处理
                    .authenticationEntryPoint(authenticationEntryPoint)// token效或者没有携带token时的处理
                    .accessDeniedHandler(accessDeniedHandler)
                .and()
                    .logout()
                    .logoutSuccessHandler(logoutSuccessHandler)
                    .deleteCookies("JSESSIONID")
                .and()
                    .sessionManagement()
                    .maximumSessions(1)//单个用户所允许同时在线的最大会话数量
                    .maxSessionsPreventsLogin(true)//会话达到最大数量时,创建新的会话而不是踢掉旧会话
        ;
    }
    @Bean("passwordEncoder")    //密码加密
    public static BCryptPasswordEncoder passwordEncoder(){
        final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher(){
        return new HttpSessionEventPublisher();
    }
}

 WebSecurityConfigurer配置如下

/**
 * @descript security基础配置,若ResourceServerConfig中亦配置,则此文件http配置被覆盖
 * */
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private Log logger = LogFactory.getLog(this.getClass());

    @Override /*认证管理使用默认*/
    @Bean("authenticationManagerBean")
    public AuthenticationManager authenticationManagerBean() throws Exception {
        logger.info("===============配置WebSecurityConfig的Bean:authenticationManagerBean=====================");
        return super.authenticationManagerBean();
    }
}

 自定义认证程序如下:

/**@author 离染
 * @description 框架未默认在authenticationManager中对authenticationProvider进行初始化,导致token无法刷新,需手动添加。
 * @main function 校验用户名和密码,表单登录自定义认证器*/
@Component
@ApiOperation(value="根据用户名判断密码是否正确")
public class TheCustomAuthenticationProvider implements AuthenticationProvider , Serializable {
    private static final Log logger = LogFactory.getLog(TheCustomAuthenticationProvider.class);
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Value("${custom.app.client.isCiphertext}")
    private boolean isCiphertxt;

    @Override
    @ApiOperation("作用:验证用户是否存在,判断输入密码是否正确")
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (!supports(authentication.getClass())) {
            return null;
        }
        String textUsername = authentication.getName();
        String textPassword = authentication.getCredentials().toString();
        logger.info("解密前的报文:username:"+textUsername+", password:"+textPassword);
        //如果是秘文,则先解密
        if(isCiphertxt){
            String[] usernamePwd = decryptionTheCiphertext(textUsername,textPassword);
            textPassword = usernamePwd[0];
            textUsername = usernamePwd[1];
        }
        logger.info("解密后的报文:username:"+textUsername+", password:"+textPassword);
        UserDetails userDetails = validateUsernamePassword(textUsername,textPassword);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails,userDetails.getPassword(),userDetails.getAuthorities());
        return usernamePasswordAuthenticationToken;
    }
    @Override
    public final boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }
    /**解密DES*/
    private String[] decryptionTheCiphertext(String ciphertxtUsername,String ciphertextPassword){
        logger.info("开始解密用户名和密码......");
        Map<String,String> usernaAndKey = DESEnDeUtils.getUsernameDESEncode(ciphertxtUsername);
        String textUsername = usernaAndKey.get("username");
        String desKey = usernaAndKey.get("desKey");
        String textPassword = DESEnDeUtils.getPasswordDESEncode(ciphertextPassword,desKey);
        logger.info("解密用户名和密码成功,用户名:" + textUsername + ",密码:" + null + ",秘钥:" + desKey);
        return new String[]{textUsername,textPassword};
    }
    /**校验用户名和密码*/
    private UserDetails validateUsernamePassword(String textUsername,String textPassword){
        //判断用户是否存在
        UserDetails userDetails = sysUserService.loadUserByUsername(textUsername);
        if (userDetails == null) { throw new UsernameNotFoundException("用户不存在."); }
        boolean isEquals = bCryptPasswordEncoder.matches(textPassword,userDetails.getPassword());
        //判断密码是否正确
        if(!isEquals) { throw new BadCredentialsException("密码错误");}
        return userDetails;
    }
}

 错误信息如下:

o.s.s.o.provider.endpoint.TokenEndpoint  : Handling error: ProviderNotFoundException, No AuthenticationProvider found for org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken

错误背景:获取token时(/oauth/token或自定义接口),走的是自定义认证程序(TheCustomAuthenticationProvider ),一切正常,但刷新token时报上述错误。访问链接如下:

 1,自定义接口登陆,获取token:
POST http://127.0.0.1:8090/api/sso/form/login?username=root&password=root&grant_type=password
Authorization: Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0  //Basic client id : client secret

2,默认接口获取token

### 默认接口获取token
POST http://127.0.0.1:8090/api/sso/oauth/token?username=root&password=root&grant_type=password
Authorization: Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0  //Basic client id : client secret

返回报文为:

 3,刷新token访问接口

POST http://localhost:8090/api/sso/oauth/token?client_id=clientId&client_secret=clientSecret&grant_type=refresh_token&refresh_token=9fc71604-dfbf-4817-9e88-e100adcbeda2

 即报错!

经多方查阅资料,查看源码,经过数小时爆肝,记不得多少次的尝试之下,终于找到原因。请求token和刷新token调用不同的认证处理器。查看DefaultTokenService源码,当刷新时默认使用PreAuthenticatedAuthenticationToken类,如图所示。

 至此,已经很明显,spring默认没有初始化PreAuthenticatedAuthenticationToken类,故需创建此类的Bean,PreAuthenticatedAuthenticationToken又依靠PreAuthenticatedAuthenticationProvider而存在,故需建一个PreAuthenticatedAuthenticationProvider类型的bean,更改WebSecurityConfig后,代码如下。

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private Log logger = LogFactory.getLog(this.getClass());
    @Autowired
    private PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider;
    @Autowired
    private TheCustomAuthenticationProvider theCustomAuthenticationProvider;

    @Override /*认证管理使用默认,认证管理器默认也未初始化,故需创建Bean,否则亦会报错*/
    @Bean("authenticationManagerBean")
    public AuthenticationManager authenticationManagerBean() throws Exception {
        logger.info("===============配置WebSecurityConfig的Bean:authenticationManagerBean=====================");
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .authenticationProvider(preAuthenticatedAuthenticationProvider)
                .authenticationProvider(theCustomAuthenticationProvider)
        ;
    }
    @Bean//刷新token时自动调用,不能用TheCustomAuthenticationProvider替代
    public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider(){
        PreAuthenticatedAuthenticationProvider PAAP = new PreAuthenticatedAuthenticationProvider();
        PAAP.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(new SysUserService()));
        return PAAP;
    }
}

至此,问题得以解决。但又出现新的错误(mongoTemplate is NULL),为方便查阅,每篇文章记录一个错误,故留在下一章述说。

若此文对您有帮助,我不胜荣幸,能赞一个,不胜感激。

这个错误通常是由于Spring Security配置不正确引起的。要解决这个问题,你可以按照以下步骤进行操作: 1. 确保你的Spring Security配置文件正确。检查是否正确引入了Spring Security依赖,并在配置文件中配置了正确的命名空间和schema。 2. 确保在配置文件中添加了AuthenticationProvider的Bean定义。你可以使用`DaoAuthenticationProvider`作为默认的AuthenticationProvider。示例配置如下: ```xml <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager"> <property name="providers"> <list> <ref bean="daoAuthenticationProvider"/> </list> </property> </bean> <bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="yourUserDetailsService"/> <!-- 如果需要密码加密,则需配置密码加密器 --> <property name="passwordEncoder" ref="yourPasswordEncoder"/> </bean> <bean id="yourUserDetailsService" class="com.example.YourUserDetailsService"/> <bean id="yourPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/> ``` 3. 确保你的自定义UserDetailsService实现了`org.springframework.security.core.userdetails.UserDetailsService`接口,并正确实现了`loadUserByUsername`方法。 通过检查以上步骤,你应该能够解决这个错误并成功进行身份验证。如果问题仍然存在,请提供更多的相关代码和配置信息,以便更好地帮助你。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值