Spring Security中基于第三方服务令牌的自定义授权实现咨询
自定义第三方令牌授权的Spring Security实现方案
嗨,我来帮你搞定这个自定义授权逻辑的问题!既然你已经和第三方服务完成了认证并拿到了授权数据,咱们可以通过Spring Security的扩展点来实现基于这个第三方令牌的授权,完全不需要依赖本地存储的方案,具体步骤如下:
1. 先明确令牌的处理逻辑
首先你得确定第三方返回的授权数据是什么格式——比如是JWT字符串、自定义token,还是需要后续调用第三方接口校验的凭证。不管哪种,核心是要能从这个令牌里拿到用户身份、权限等信息,或者能验证这个令牌的有效性。
2. 自定义认证过滤器(TokenAuthenticationFilter)
咱们需要一个过滤器来拦截所有需要授权的请求,从请求头(比如Authorization: Bearer <token>)里提取令牌,然后触发认证流程:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import java.io.IOException; import java.util.List; public class TokenAuthenticationFilter extends BasicAuthenticationFilter { public TokenAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 从请求头获取令牌 String token = extractTokenFromRequest(request); if (token == null) { chain.doFilter(request, response); return; } // 调用自定义逻辑完成认证,构造认证对象 UsernamePasswordAuthenticationToken authentication = getAuthentication(token); if (authentication != null) { SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } private String extractTokenFromRequest(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { return authHeader.substring(7); } return null; } private UsernamePasswordAuthenticationToken getAuthentication(String token) { // 这里替换为你的实际逻辑:比如调用第三方接口校验令牌、解析令牌获取用户信息 String username = getUsernameFromThirdPartyToken(token); if (username == null) { return null; } // 构造认证对象,传入用户权限集合 return new UsernamePasswordAuthenticationToken(username, null, getUserAuthorities(token)); } // 自定义方法:从第三方令牌中获取用户名 private String getUsernameFromThirdPartyToken(String token) { // 示例:调用第三方校验接口或解析JWT的claim return "user123"; } // 自定义方法:从令牌中获取用户权限 private List<SimpleGrantedAuthority> getUserAuthorities(String token) { // 示例:从第三方返回的信息中提取权限,或映射成本地权限 return List.of(new SimpleGrantedAuthority("ROLE_USER")); } }
3. 实现自定义AuthenticationProvider(可选但推荐)
如果你的令牌需要更复杂的校验逻辑(比如调用第三方接口验证令牌有效性),可以单独实现AuthenticationProvider,让它来处理核心的认证逻辑:
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import java.util.List; public class ThirdPartyTokenAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String token = (String) authentication.getPrincipal(); // 调用第三方接口校验令牌有效性 boolean isValid = validateTokenWithThirdParty(token); if (!isValid) { throw new BadCredentialsException("Invalid third-party token"); } // 从第三方获取用户信息和权限 String username = getUsernameFromThirdParty(token); List<GrantedAuthority> authorities = getUserAuthoritiesFromThirdParty(username); // 返回认证成功的对象 return new PreAuthenticatedAuthenticationToken(username, null, authorities); } @Override public boolean supports(Class<?> authentication) { return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication); } // 自定义方法:调用第三方服务校验令牌 private boolean validateTokenWithThirdParty(String token) { // 这里替换为真实的第三方校验接口调用逻辑 return true; } private String getUsernameFromThirdParty(String token) { // 从第三方获取用户名 return "user123"; } private List<GrantedAuthority> getUserAuthoritiesFromThirdParty(String username) { // 从第三方获取用户权限 return List.of(new SimpleGrantedAuthority("ROLE_USER")); } }
4. 配置Spring Security,整合自定义组件
接下来在Security配置类里,把咱们的过滤器和Provider加进去,保护需要授权的端点,同时放开/login和/auth相关的路径:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { http .csrf(csrf -> csrf.disable()) // REST服务通常关闭CSRF .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态,不依赖session .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/login").permitAll() // 放开登录端点 .anyRequest().authenticated() // 其他所有端点需要认证 ) .addFilterBefore(new TokenAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class); // 添加自定义过滤器 return http.build(); } @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); authBuilder.authenticationProvider(new ThirdPartyTokenAuthenticationProvider()); // 注册自定义Provider return authBuilder.build(); } }
5. 在/login端点返回第三方令牌
最后,在你的/auth/login端点里,完成第三方认证后,把拿到的授权数据(令牌)返回给前端,前端后续请求就带着这个令牌:
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/auth") public class AuthController { // 假设你已经有调用第三方认证的客户端 private final ThirdPartyAuthClient thirdPartyAuthClient; public AuthController(ThirdPartyAuthClient thirdPartyAuthClient) { this.thirdPartyAuthClient = thirdPartyAuthClient; } @PostMapping("/login") public AuthResponse login(@RequestBody LoginRequest loginRequest) { // 调用第三方服务完成认证,获取授权令牌 String thirdPartyToken = thirdPartyAuthClient.authenticate(loginRequest.getUsername(), loginRequest.getPassword()); // 返回令牌给前端 return new AuthResponse(thirdPartyToken); } // 内部类示例 static class LoginRequest { private String username; private String password; // getter & setter } static class AuthResponse { private String token; public AuthResponse(String token) { this.token = token; } // getter } }
关键注意点
- 如果第三方令牌是JWT,你可以用
jjwt库直接解析,不用调用第三方接口校验,只要验证签名和过期时间即可 - 要处理令牌过期、无效的情况,在过滤器或Provider里抛出对应的
AuthenticationException,Spring Security会自动返回401或403响应 - 如果需要刷新令牌,可以在/auth下加一个刷新端点,调用第三方的刷新接口获取新令牌
内容的提问来源于stack exchange,提问作者user1126068
相关产品推荐
相关产品推荐

