Spring Security OAuth2.0访问/oauth/authorize遇403错误排查
我是Spring Security OAuth2.0新手,访问/oauth/authorize端点时始终返回403错误,已做如下配置:
依赖配置(pom.xml)
<!-- SpringBoot Security dependency--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure --> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.5.14</version> </dependency>
Spring Security配置类(WebSecurityConfig.java)
package com.byhuang.config; import com.byhuang.filter.TokenFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenFilter tokenFilter; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/user/login", "/oauth/*").anonymous() .anyRequest().authenticated(); http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); } }
授权服务配置类(AuthorizationServerConfig.java)
package com.byhuang.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private TokenStore tokenStore; @Autowired private AuthenticationManager authenticationManager; @Autowired private ClientDetailsService clientDetailsService; @Autowired private AuthorizationCodeServices authorizationCodeServices; @Autowired private PasswordEncoder passwordEncoder; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("c1") .secret(passwordEncoder.encode("secret")) .scopes("all") .authorizedGrantTypes("authorization_code", "password") .autoApprove(false) .redirectUris("www.baidu.com"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .authorizationCodeServices(authorizationCodeServices) .tokenServices(tokenServices()) .allowedTokenEndpointRequestMethods(HttpMethod.POST); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll") .checkTokenAccess("permitAll") .allowFormAuthenticationForClients(); } @Bean public AuthorizationServerTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore); defaultTokenServices.setClientDetailsService(clientDetailsService); defaultTokenServices.setSupportRefreshToken(true); defaultTokenServices.setAccessTokenValiditySeconds(7200); defaultTokenServices.setRefreshTokenValiditySeconds(259200); return defaultTokenServices; } }
TokenFilter代码
package com.byhuang.filter; import com.alibaba.fastjson.JSON; import com.byhuang.LoginDTO; import com.byhuang.utils.JWTUtils; import io.jsonwebtoken.Claims; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class TokenFilter extends OncePerRequestFilter { @Autowired private RedisTemplate<String, String> redisTemplate; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader("token"); if (!StringUtils.hasText(token)) { filterChain.doFilter(request, response); return; } Claims claims = JWTUtils.parseToken(token); String username = (String) claims.get("username"); String userString = redisTemplate.opsForValue().get(username); if (!StringUtils.hasText(userString)) { throw new RuntimeException("尚未登录!"); } LoginDTO user = JSON.parseObject(userString, LoginDTO.class); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, null, null); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); filterChain.doFilter(request, response); } }
全局Bean配置(GlobalBeanConfig.java)
package com.byhuang.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; import javax.sql.DataSource; /** * @author huangbingyi * @version 1.0 * @date 2023/6/18 18:35 * @description TODO */ @Configuration public class GlobalBeanConfig { @Autowired private DataSource dataSource; @Bean public JdbcTemplate jdbcTemplate() { JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } @Bean public TokenStore tokenStore() { return new InMemoryTokenStore(); } @Bean public AuthorizationCodeServices authorizationCodeServices() { return new InMemoryAuthorizationCodeServices(); } }
我已经在WebSecurityConfig中设置/oauth/*允许匿名访问,但访问http://localhost:8080/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=www.baidu.com仍然返回403,不确定是否和TokenFilter有关,请求并未携带token,请问哪里配置有问题?
1. 核心问题:无状态会话配置与授权码模式冲突
授权码模式需要会话存储用户认证状态和授权请求信息,但你在WebSecurityConfig中设置了SessionCreationPolicy.STATELESS完全禁用会话,导致授权端点无法正常处理请求,直接返回403。
修改WebSecurityConfig中的会话策略,针对/oauth/authorize端点启用会话:
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement() // 全局默认无状态,针对授权端点例外 .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/user/login", "/oauth/*").anonymous() .anyRequest().authenticated() // 针对授权码模式的端点,强制启用会话 .and() .requestMatchers().antMatchers("/oauth/authorize") .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); }
2. TokenFilter的潜在隐患
虽然当前TokenFilter在无token时会放行请求,但建议对/oauth/*路径直接跳过过滤,避免后续意外拦截:
@Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { String path = request.getRequestURI(); return path.startsWith("/oauth/") || path.equals("/user/login"); }
添加该方法后,TokenFilter不会处理授权相关请求,减少干扰。
3. 重定向URI格式问题
你配置的redirectUris("www.baidu.com")缺少协议头(http/https),规范的重定向URI应为完整URL,比如https://www.baidu.com,否则会被OAuth2框架判定为无效URI,导致授权请求被拒绝。
修改AuthorizationServerConfig中的客户端配置:
.redirectUris("https://www.baidu.com")
4. 验证步骤
完成以上修改后,按以下步骤验证:
- 重启应用
- 访问授权端点:
http://localhost:8080/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=https://www.baidu.com - 此时应跳转到Spring Security默认登录页面,登录后即可获取授权码
内容的提问来源于stack exchange,提问作者byhuang1998

