SpringSecurity6配置requestMatchers().permitAll() 无效问题

版本

<spring-boot.version>3.0.2</spring-boot.version>
<jjwt.version>0.12.5</jjwt.version>

问题描述

题主在写 SpringSecurity6 + JWT 做登录认证开发。一路跟着教程叭叭的敲。等到接口验证的时候,发现我的登录接口虽然在SecurityConfig中配置了免验证,但是访问登录接口的时候还是被拦截了。

这里先直接点题说明题主出错的原因:
因为我配置的URL是全路径的,也就是携带着server.servlet.context-path的配置内容的,类似这样/bees/API/login/wechat,其中/bees就是我配置的context-path

我出问题的时候已经在网上找了很多解决方式。但是都没有解决我的问题。
当我在访问我的login接口的时候,系统依然进入了我的自定义验证过滤器中。
后面在通义千问中,AI给出的建议,配置Security的日志输出级别到DEBUG这样能有效地排查问题。

logging.level.org.springframework.security=DEBUG

这样将SpringSecurity的log级别调到DEBUG,在启动服务的时候发现了两行有用的日志
在这里插入图片描述
这是在遇到问题,根据各种文章修改完依然没能解决问题之后的唯一一点曙光。

解决:去掉/bees之后,再次请求登录接口,就不会再过我们自定义的认证拦截器了。

这就是题主的遇到的问题症结所在。

总结

三点。

  1. 检查你自定义的认证拦截器是不是交给Spring管理了(如果是可能会有问题)。
  2. 检查你的SecurityConfig配置类中是否有配置WebSecurityCustomizer这个Bean(没有需要加上)。
  3. 最后检查你需要免验证的URL是否跟我一样加了context-path(或者使用了通配符,题主试过通配符但是不太行,可能是我使用方式有问题)。

最后贴一个比较完整的代码片段

包括 SecurityConfig JwtAuthenticationFilter JwtTokenUtil

代码片段如下:

SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserServiceImpl userService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //关闭csrf和frameOptions,如果不关闭会影响前端请求接口
        http.csrf(AbstractHttpConfigurer::disable).headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));
        //开启跨域以便前端调用接口
        http.cors();
        //这里是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
        // 配置访问控制规则
        http.authorizeHttpRequests(request ->
            // 指定特定接口无需验证即可访问,如微信登录
            request.requestMatchers(HttpMethod.POST, "/API/login/wechat").permitAll()
            // 其他所有以 "/bees/" 开头的接口需要认证才能访问
            .requestMatchers("/bees/**").authenticated()
        );
       //禁用session
       http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //将我们自定义的认证过滤器替换掉默认的认证过滤器,指定将自定义的Filter添加到某个指定的Filter之前或者之后
        http.addFilterBefore(new WeChatLoginFilter(jwtTokenUtil, userService), UsernamePasswordAuthenticationFilter.class);
        //指定认证错误处理器
        http.exceptionHandling().authenticationEntryPoint(new BeesAuthenticationFailEntryPoint())
                .accessDeniedHandler(new BeesDeniedHandler());
        return http.build();
    }

    /**
     * 指定加密器
     *
     * @return BCryptPasswordEncoder
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        // 提供自定义loadUserByUsername
        authProvider.setUserDetailsService(userService);
        // 指定密码编辑器
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public WebSecurityCustomizer ignoringCustomizer() {
        return (web) -> web.ignoring().requestMatchers(HttpMethod.POST, "/API/login/wechat");
    }
}

JwtAuthenticationFilter

/**
* 自定义的认证过滤器不交给Spring管理
*/
public class WeChatLoginFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(WeChatLoginFilter.class);

    private final JwtTokenUtil jwtTokenUtil;
    private final UserServiceImpl userService;

    public WeChatLoginFilter(JwtTokenUtil jwtTokenUtil, UserServiceImpl userService) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.userService = userService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        log.info("---WeChatLoginFilter---");
        String authorizationS = request.getHeader("Authorization");
        if (!StringUtils.hasLength(authorizationS)) {
            chain.doFilter(request, response);
            return;
        }

        Claims claims = jwtTokenUtil.getAllClaimsFromToken(authorizationS);
        //说明解析失败了
        if (claims == null) {
            throw new BadCredentialsException("token异常或已过期");
        }
        String authorization = claims.getSubject();
        TokenInfo tokenInfo = JSON.parseObject(authorization, TokenInfo.class);
        //通过loginType的区分,避免每次都查数据库
        if (LoginType.WX.getLoginType().equals(tokenInfo.getLoginType())) {
            User user = userService.loadUserByUserId(tokenInfo.getUserId());
            //用户不存在 todo

            BeesCommonAuthenticationToken authentication = new BeesCommonAuthenticationToken(JSON.toJSONString(user), "");
            //UserDetail user = userService.loadUserByUserId(tokenInfo.getUserId());
//                BeesCommonAuthenticationToken authentication = new BeesCommonAuthenticationToken(user.getUser().getWxOpenId(), "", user.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }
}

JwtTokenUtil

@Component
public class JwtTokenUtil {

    private static final long JWT_EXPIRATION_TIME_MS = 86400000; // 1 day
    /**
    * 加密用的盐
    */
    @Value("${security.jwt.secretKey}")
    private String secretKey;

    // 生成JWT令牌
    public String generateToken(Integer loginType, Long userId) {
        TokenInfo tokenInfo = new TokenInfo();
        tokenInfo.setUserId(userId);
        tokenInfo.setLoginType(loginType);
        String subject = JSON.toJSONString(tokenInfo);
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION_TIME_MS))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    // 解析JWT令牌并获取claims
    public Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    // 从JWT令牌中提取用户名
    public String getUsernameFromToken(String token) {
        return getAllClaimsFromToken(token).getSubject();
    }

    // 验证JWT令牌是否有效
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    // 检查JWT令牌是否过期
    private Boolean isTokenExpired(String token) {
        final Date expiration = getAllClaimsFromToken(token).getExpiration();
        return expiration.before(new Date());
    }

    // 生成签名密钥
    private SecretKey getSigningKey() {
        byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}

——————————————以上————————————————————
希望对各位有帮助。

参考文章:
SpringSecurity6解决requestMatchers().permitAll()后依然执行自定义过滤器的问题

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值