SpringSecurity自定义多Provider时提示No AuthenticationProvider found for问题的解决方案与原理(四)

上篇文章已经大致把SpringSecurity的设计思路捋出来了,这次就开始着手改造一下。

总体思路

大体的设计思路和之前比较一致,只是在配置方面做了调整,重新梳理如下:

  1. 构建一个特定的Token类,例如PasswordAuthenticationToken,这个类需要继承AbstractAuthenticationToken,在需要做认证的地方把他new出来;
  2. 构建认证处理器类PasswordAuthenticationProvider类,实现AuthenticationProvider接口,并重写其中的authenticate()supports()方法;
  3. 构造PasswordAuthenticationFilter类继承自AbstractAuthenticationProcessingFilter并重写其中的attemptAuthentication()方法,同时重写AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher,AuthenticationManager authenticationManager)方法用于注入AuthenticationManager
  4. 构造PasswordAuthenticationConfigurer类,继承自AbstractHttpConfigurer并重写configure方法,他可是提供了一个非常宝贵的HttpSecurity入参。
  5. 构造相应的成功及异常处理器。

结果

思路清楚了就直接上代码吧,关键位置都做了注释,一遍看不懂就多看几遍。先看下改造后的整体结构。
在这里插入图片描述

先来PasswordAuthenticationToken

/**
 * 基于用户名(手机号)、密码、验证码登录的认证实体
 */
public class PasswordAuthenticationToken extends AbstractAuthenticationToken {

    private final String loginId;
    private final String captchaId;
    private final String captchaValue;
    private final LoginUserPojo principal;
    private final String credentials;

    /**
     * 登录验证
     *
     * @param loginId      用户名或手机号
     * @param credentials  MD5+SM3密码
     * @param captchaId    图形验证码id
     * @param captchaValue 输入的图形验证码值
     */
    public PasswordAuthenticationToken(String loginId, String credentials, String captchaId, String captchaValue) {
        super(null);
        this.loginId = loginId;
        this.credentials = credentials;
        this.captchaId = captchaId;
        this.captchaValue = captchaValue;
        this.principal = null;
        this.setAuthenticated(false);
    }

    /**
     * 授权信息
     *
     * @param principal   LoginUserPojo
     * @param credentials token
     * @param authorities 角色清单
     */
    public PasswordAuthenticationToken(LoginUserPojo principal, String credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        this.loginId = null;
        this.captchaId = null;
        this.captchaValue = null;
        this.setAuthenticated(true);
    }

    public String getLoginId() {
        return loginId;
    }

    public String getCaptchaId() {
        return captchaId;
    }

    public String getCaptchaValue() {
        return captchaValue;
    }

    @Override
    public LoginUserPojo getPrincipal() {
        return principal;
    }

    @Override
    public String getCredentials() {
        return credentials;
    }
}

PasswordAuthenticationProvider

/**
 * 基于用户名(手机号)、密码、验证码的认证处理器
 */
public class PasswordAuthenticationProvider implements AuthenticationProvider {

    private static final String IMG_CAPTCHA_REDIS_PREFIX = "SK:CAPTCHA:IMG:";

    private final UserDetailServiceImpl userDetailService;
    private final RedisCacheUtil redisCacheUtil;

    public PasswordAuthenticationProvider(UserDetailServiceImpl userDetailService, RedisCacheUtil redisCacheUtil) {
        this.userDetailService = userDetailService;
        this.redisCacheUtil = redisCacheUtil;
    }

    /**
     * 验证主逻辑
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        PasswordAuthenticationToken authToken = (PasswordAuthenticationToken) authentication;

        // 验证码校验
        if (!checkImgCaptcha(authToken.getCaptchaId(), authToken.getCaptchaValue())) {
            throw new BadCaptchaException("验证码有误或已过期,请重新输入");
        }

        // 密码校验
        LoginUserPojo userDetails = (LoginUserPojo) userDetailService.loadUserByUsername(authToken.getLoginId());
        if (!new BCryptPasswordEncoder().matches(authToken.getCredentials(), userDetails.getPassword())) {
            throw new BadCredentialsException("用户名或密码错误,请重新输入");
        }

        // 用户状态校验
        if (!userDetails.isEnabled() || !userDetails.isAccountNonLocked() || !userDetails.isAccountNonExpired()) {
            throw new LockedException("用户已禁用,请联系管理员启用");
        }

        return new PasswordAuthenticationToken(userDetails, authToken.getCredentials(), userDetails.getAuthorities());
    }

    /**
     * 当类型为PasswordAuthenticationToken的认证实体进入时才走此Provider
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return PasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    /**
     * 校验验证码正确与否,验证完成后删除当前码值
     *
     * @param id    验证码对应的id
     * @param value 用户输入的验证码结果
     * @return true or false
     */
    private boolean checkImgCaptcha(String id, String value) {
        if (StringUtils.isBlank(id) || StringUtils.isBlank(value)) {
            return false;
        }

        CaptchaCodePojo captchaCode = redisCacheUtil.getObject(IMG_CAPTCHA_REDIS_PREFIX + id);
        redisCacheUtil.deleteObject(IMG_CAPTCHA_REDIS_PREFIX + id);

        return !Objects.isNull(captchaCode) && value.equals(captchaCode.getResult());
    }
}

PasswordAuthenticationConfigurer

/**
 * 基于用户名(手机号)、密码、验证码的登录拦截器配置类
 */
public class PasswordAuthenticationConfigurer extends AbstractHttpConfigurer<PasswordAuthenticationConfigurer, HttpSecurity> {
    @Override
    public void configure(HttpSecurity builder) {
        // 拦截 POST /login 请求
        RequestMatcher matcher = new AntPathRequestMatcher("/login", "POST", true);

        UserDetailServiceImpl userDetailService = builder.getSharedObject(ApplicationContext.class).getBean(UserDetailServiceImpl.class);
        RedisCacheUtil redisCacheUtil = builder.getSharedObject(ApplicationContext.class).getBean(RedisCacheUtil.class);
        AuthenticationManager localAuthManager = builder.getSharedObject(AuthenticationManager.class);

        PasswordAuthenticationFilter filter = new PasswordAuthenticationFilter(matcher, localAuthManager);
        filter.setAuthenticationSuccessHandler(new LoginSuccessHandler(userDetailService));
        filter.setAuthenticationFailureHandler(new LoginFailHandler());

        // 务必注意这里与配置类中声明的先后顺序
        builder.authenticationProvider(new PasswordAuthenticationProvider(userDetailService, redisCacheUtil))
                .addFilterBefore(filter, AuthenticationTokenFilter.class);
    }
}

PasswordAuthenticationFilter

/**
 * 用户名密码登录拦截器
 */
public class PasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public PasswordAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher, AuthenticationManager authenticationManager) {
        super(requiresAuthenticationRequestMatcher, authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        JSONObject params = HttpRequestUtil.getBodyJson(request);
        Authentication authentication = new PasswordAuthenticationToken(
                params.getString("loginKey"), params.getString("password"), params.getString("id"), params.getString("value")
        );

        return this.getAuthenticationManager().authenticate(authentication);
    }
}

Token拦截器配置类TokenAuthenticationConfigurer

/**
 * Token拦截器配置类
 */
public class TokenAuthenticationConfigurer extends AbstractHttpConfigurer<TokenAuthenticationConfigurer, HttpSecurity> {
    @Override
    public void configure(HttpSecurity builder) {
        RedisCacheUtil redisCacheUtil = builder.getSharedObject(ApplicationContext.class).getBean(RedisCacheUtil.class);
        builder.addFilterBefore(new AuthenticationTokenFilter(redisCacheUtil), UsernamePasswordAuthenticationFilter.class);
    }
}

Token认证拦截器AuthenticationTokenFilter

/**
 * Token认证拦截器
 */
public class AuthenticationTokenFilter extends OncePerRequestFilter {

    private final RedisCacheUtil redisCacheUtil;

    public AuthenticationTokenFilter(RedisCacheUtil redisCacheUtil) {
        this.redisCacheUtil = redisCacheUtil;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = JwtTokenUtil.getToken(request);
        if (Objects.nonNull(token) && JwtTokenUtil.checkToken(token) && redisCacheUtil.hasKey(TokenConstant.TOKEN_REDIS_PREFIX + token)) {
            // 从redis中获取数据
            LoginUserPojo userPojo = redisCacheUtil.getObject(TokenConstant.TOKEN_REDIS_PREFIX + token);

            // 写入上下文
            PasswordAuthenticationToken authenticationToken = new PasswordAuthenticationToken(userPojo, token, userPojo.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            // 刷新ttl
            redisCacheUtil.setExpire(TokenConstant.TOKEN_REDIS_PREFIX + token, TokenConstant.TOKEN_EXPIRE_TIME, TokenConstant.TOKEN_EXPIRE_TIME_UNIT);
        }
        filterChain.doFilter(request, response);
    }
}

LoginFailHandler

/**
 * 密码认证失败处理器
 */
public class LoginFailHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        JSONObject res = new JSONObject();
        res.put("success", false);
        res.put("msg", e.getMessage());

        response.setStatus(HttpStatus.SC_FORBIDDEN);
        response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().print(res.toJSONString());
    }
}

LoginSuccessHandler

/**
 * 登录成功后处理器
 */
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    private final UserDetailServiceImpl userDetailsService;

    public LoginSuccessHandler(UserDetailServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        LoginUserPojo loginUserPojo = (LoginUserPojo) authentication.getPrincipal();

        // 更新登陆时间
        userDetailsService.updateLoginTime(loginUserPojo.getUserId());

        // 构建token并缓存
        String token = userDetailsService.buildToken(loginUserPojo);

        JSONObject res = new JSONObject();
        res.put("success", true);
        res.put("msg", "OK");
        res.put("data", token);

        response.setStatus(HttpStatus.SC_OK);
        response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().write(res.toString());
    }
}

UserDetailServiceImpl

/**
 * SpringSecurity登录处理类
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private RedisCacheUtil redisCacheUtil;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Map<String, Object> userData = sysUserMapper.getByUserNameOrUserPhone(username);
        if (MapUtils.isEmpty(userData)) {
            throw new UsernameNotFoundException("用户名或密码错误,请重新输入");
        }

        // 封装基础数据
        LoginUserPojo userPojo = new LoginUserPojo();
        userPojo.setUserId(MapUtils.getString(userData, "user_id"));
        userPojo.setUserName(MapUtils.getString(userData, "user_name"));
        userPojo.setUserPhone(MapUtils.getString(userData, "user_phone"));
        userPojo.setNickName(MapUtils.getString(userData, "nick_name"));
        userPojo.setPassword(MapUtils.getString(userData, "password"));
        userPojo.setUserStatus(MapUtils.getIntValue(userData, "user_status"));
        userPojo.setLastLogin(MapUtils.getString(userData, "last_login"));
        userPojo.setLastUpdatePassword(MapUtils.getString(userData, "last_update_password"));
        userPojo.setCreateUser(MapUtils.getString(userData, "create_user"));
        userPojo.setCreateTime(MapUtils.getString(userData, "create_time"));
        userPojo.setUpdateUser(MapUtils.getString(userData, "update_user"));
        userPojo.setUpdateTime(MapUtils.getString(userData, "update_time"));

        // 封装角色信息
        if (StringUtils.isNotBlank(MapUtils.getString(userData, "role_code"))) {
            List<UserGrantedAuthority> grantedAuthorityList = new ArrayList<>();
            String[] roleCodes = MapUtils.getString(userData, "role_code").split(",");
            String[] roleNames = MapUtils.getString(userData, "role_name").split(",");
            String[] roleTypes = MapUtils.getString(userData, "role_type").split(",");
            for (int i = 0; i < roleCodes.length; i++) {
                grantedAuthorityList.add(new UserGrantedAuthority(roleCodes[i], roleNames[i], Integer.valueOf(roleTypes[i])));
            }
            userPojo.setAuthorities(grantedAuthorityList);
        }

        return userPojo;
    }

    /**
     * 更新用户登录时间
     *
     * @param userId 用户ID
     */
    public void updateLoginTime(String userId) {
        SysUserEntity userEntity = new SysUserEntity();
        userEntity.setUserId(userId);
        userEntity.setLastLogin(DateTimeUtil.getCurrentDate("yyyy-MM-dd HH:mm:ss"));
        sysUserMapper.updateByUserId(userEntity);
    }

    /**
     * 根据用户信息构造token并写入redis
     *
     * @param loginUserPojo LoginUserPojo
     * @return token
     */
    public String buildToken(LoginUserPojo loginUserPojo) {
        JSONObject user = new JSONObject();
        user.put("userId", loginUserPojo.getUserId());
        user.put("userName", loginUserPojo.getUserName());
        user.put("roleCode", loginUserPojo.getAuthorities().stream().map(UserGrantedAuthority::getRoleCode).collect(Collectors.joining(",")));

        // 生成token
        String token = JwtTokenUtil.createJwtToken(user);
        redisCacheUtil.setObject(TokenConstant.TOKEN_REDIS_PREFIX + token, loginUserPojo, TokenConstant.TOKEN_EXPIRE_TIME, TokenConstant.TOKEN_EXPIRE_TIME_UNIT);
        return token;
    }
}

还有最后的Security配置类SpringSecurityConfig

/**
 * SpringSecurity配置类
 */
@EnableWebSecurity
@PropertySource("classpath:authfilter.properties")
public class SpringSecurityConfig {

    @Value("${exclude_urls}")
    private String excludeUrls;

    @Autowired
    private AuthenticationLogoutHandler logoutHandler;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .csrf().disable()
                .formLogin().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeHttpRequests().antMatchers(StringUtils.split(excludeUrls, ",")).permitAll()
                .anyRequest().authenticated().and()
                .exceptionHandling().authenticationEntryPoint(new AuthenticationFailHandler()).and()
                .logout().logoutUrl("/logout").logoutSuccessHandler(logoutHandler).and()
                // 务必注意这里的先后顺序,否则会报NULL异常
                .apply(new TokenAuthenticationConfigurer()).and()
                .apply(new PasswordAuthenticationConfigurer()).and()
                .apply(new OAuthAuthenticationConfigurer()).and()
                .build();
    }
}

经过测试完美执行,自定义Provider可以仅在Local作用域中生效,不干扰全局AuthenticationManager
在这里插入图片描述

另外我想尽可能避免将所有类都标记为@Component,所以部分业务依赖项我是通过上下文ApplicationContext获取的,而这个实例正好也在httpSecuritysharedObject中。


后记

当我完成所有修改后已经过了三天多了,这段时间Security简直就是我的梦魇。
在这里插入图片描述

但我也和大多数人一样,看之前骂spring一个过滤器的事设计的这么复杂干什么,看完之后才发现给我上了一课:什么叫设计模式,什么叫开闭原则。跟各位写框架的大佬比,我还是太年轻了。

不过倒也发现了一些问题,比如在他推荐使用SecurityFilterChain的时候,javadoc里面却仍然是传统方式的例子。满心欢喜的以为能水一个issue呢结果已经被人修复好了。。。。。
在这里插入图片描述

附用到的一些比较有价值的资料(还是推荐spring官方文档,要一字一句的读才能体会到设计者的思路):
一文搞懂SpringSecurity+JWT前后端分离
避坑指南(三):Spring Security Oauth2框架如何初始化AuthenticationManager
Spring Security 实战干货:你不了解的Spring Security架构
Spring Security 实战干货:WebSecurityConfigurerAdapter即将被移除
SpringSecurity之ProviderManager是如何产生的,如何与UsernamePasswordAuthenticationFilter光联在一起
Spring Security小教程 Vol 3. 身份验证的入口-AbstractAuthenticationProcessingFilter
Spring Security without the WebSecurityConfigurerAdapter
ProviderManager


附用到的一些配置类

Redis

Redis操作工具类
@Component
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class RedisCacheUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功;false=设置失败
     */
    public Boolean setExpire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取TTL,单位秒
     *
     * @param key Redis键
     * @return TTL
     */
    public Long getExpire(final String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public <T> void setObject(String key, T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setObject(String key, T value, long timeout, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 获得缓存的基本对象
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getObject(String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key 缓存键值
     */
    public Boolean deleteObject(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setList(String key, List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getList(String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Map
     *
     * @param key     缓存键值
     * @param dataMap 要缓存的Map
     */
    public <T> void setMap(String key, Map<String, T> dataMap) {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key 缓存键值
     * @return 缓存的Map
     */
    public <T> Map<String, T> getMap(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key   缓存键值
     * @param hKey  Hash键
     * @param value 值
     */
    public <T> void setMapValue(String key, String hKey, T value) {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key  缓存键值
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getMapValue(String key, String hKey) {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 删除Hash中的数据
     *
     * @param key  缓存键值
     * @param hKey Hash键
     */
    public void deleteMapValue(String key, String hKey) {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hKey);
    }

}
RedisTemplate配置类
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new RedisFastJsonSerializer<>(Object.class));

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new RedisFastJsonSerializer<>(Object.class));

        template.setConnectionFactory(connectionFactory);
        template.afterPropertiesSet();
        return template;
    }

}
Redis使用FastJson2序列化配置类
public class RedisFastJsonSerializer<T> implements RedisSerializer<T> {

    private final Class<T> clazz;

    public RedisFastJsonSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        String str = new String(bytes, StandardCharsets.UTF_8);
        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }

}

JWT

此jwt使用的是以下版本的类库,注意不同实现库的加解密方式可能不同

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.2.2</version>
</dependency>
public class JwtTokenUtil {

    private static final Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);

    private static final String JWT_KEY = "替换成你自己的key";

    private static final String ISSUER = "替换成你自己的署名者";

    /**
     * 生成JWT Token
     *
     * @param paramMap 需要写在token中的参数
     * @return token
     */
    public static String createJwtToken(Map<String, Object> paramMap) {
        Algorithm algorithm = Algorithm.HMAC256(JWT_KEY);
        return JWT.create().withIssuer(ISSUER).withClaim("user", paramMap).withIssuedAt(new Date()).sign(algorithm);
    }

    /**
     * token校验
     *
     * @param token token
     * @return true or false
     */
    public static boolean checkToken(String token) {
        Algorithm algorithm = Algorithm.HMAC256(JWT_KEY);
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
        try {
            verifier.verify(token);
            return true;
        } catch (Exception e) {
            logger.error("token校验失败: " + e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取token
     *
     * @param request HttpServletRequest
     * @return token
     */
    public static String getToken(HttpServletRequest request) {
        String token = request.getHeader(TokenConstant.TOKEN_HEADER);
        return Objects.isNull(token) || "null".equalsIgnoreCase(token) || StringUtils.isBlank(token) ? null : token;
    }

}
  • 18
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 44
    评论
这个错误通常是由于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`方法。 通过检查以上步骤,你应该能够解决这个错误并成功进行身份验证。如果问题仍然存在,请提供更多的相关代码和配置信息,以便更好地帮助你。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值