You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Spring Security OAuth2资源服务器自定义令牌校验与认证逻辑?

Migrating to Spring Security OAuth2 Resource Server for Your Custom Auth Flow

Let’s break down how to replace your interceptor-based resource protection with Spring Security’s OAuth2 Resource Server support, while preserving all your original logic. I’ll walk through each component and how to map your requirements to Spring Security’s built-in tools.


1. Dependency Setup

First, add the necessary Spring Security dependencies to your project (Maven example):

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2. Core Security Configuration

This is the main setup that ties all components together. We’ll configure the resource server to use opaque tokens (since you need to call your auth server for validation), handle redirects, and integrate custom filters.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableWebSecurity
public class ResourceServerSecurityConfig {

    private final String AUTH_SERVER_INTROSPECTION_URL = "https://your-auth-server.com/api/token/validate";
    private final String RESOURCE_SERVER_CLIENT_ID = "your-resource-client-id";
    private final String RESOURCE_SERVER_CLIENT_SECRET = "your-resource-client-secret";
    private final UserInfoService userInfoService;

    public ResourceServerSecurityConfig(UserInfoService userInfoService) {
        this.userInfoService = userInfoService;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2
                .opaqueToken(opaqueToken -> opaqueToken
                    .bearerTokenResolver(new CookieBearerTokenResolver())
                    .introspectionUri(AUTH_SERVER_INTROSPECTION_URL)
                    .introspectionClientCredentials(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_SECRET)
                    .introspector(customOpaqueTokenIntrospector())
                )
            )
            .exceptionHandling(exceptions -> exceptions
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
            )
            .sessionManagement(session -> session
                .sessionFixation().migrateSession()
                .maximumSessions(1)
            )
            .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
            .addFilterAfter(sessionGuidValidationFilter(), OAuth2AuthenticationProcessingFilter.class)
            .addFilterAfter(customAuthenticationSuccessHandler(), SessionGuidValidationFilter.class);

        return http.build();
    }

    @Bean
    public OpaqueTokenIntrospector customOpaqueTokenIntrospector() {
        NimbusOpaqueTokenIntrospector delegate = new NimbusOpaqueTokenIntrospector(
            AUTH_SERVER_INTROSPECTION_URL,
            RESOURCE_SERVER_CLIENT_ID,
            RESOURCE_SERVER_CLIENT_SECRET
        );
        return new CustomOpaqueTokenIntrospector(delegate);
    }

    @Bean
    public SessionGuidValidationFilter sessionGuidValidationFilter() {
        return new SessionGuidValidationFilter();
    }

    @Bean
    public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
        return new CustomAuthenticationSuccessHandler(userInfoService);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Your original logic uses a cookie to store the access token. This resolver tells Spring Security to look for the token in the cookie instead of the default Authorization header.

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;

public class CookieBearerTokenResolver implements BearerTokenResolver {

    private String cookieName = "access_token";

    @Override
    public String resolve(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookieName.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    public void setCookieName(String cookieName) {
        this.cookieName = cookieName;
    }
}

4. Custom Token Introspector

This component validates the token against your auth server’s API and ensures the user’s uniqueId (GUID) is present in the response.

import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;

public class CustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {

    private final OpaqueTokenIntrospector delegate;

    public CustomOpaqueTokenIntrospector(OpaqueTokenIntrospector delegate) {
        this.delegate = delegate;
    }

    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        OAuth2AuthenticatedPrincipal principal = delegate.introspect(token);
        String uniqueId = principal.getAttribute("uniqueId");
        if (uniqueId == null) {
            throw new IllegalArgumentException("Token missing required uniqueId attribute");
        }
        return principal;
    }
}

5. Authentication Entry Point (Redirect to Auth Server Login)

When no valid token is found, this redirects users to your auth server’s login page with the required clientId and returnURL parameters.

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final String AUTH_SERVER_LOGIN_URL = "https://your-auth-server.com/login";
    private final String CLIENT_ID = "your-resource-client-id";

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        String returnUrl = UriComponentsBuilder.fromRequest(request).build().toUriString();
        String redirectUrl = UriComponentsBuilder.fromUriString(AUTH_SERVER_LOGIN_URL)
                .queryParam("clientId", CLIENT_ID)
                .queryParam("returnURL", returnUrl)
                .build().toUriString();
        response.sendRedirect(redirectUrl);
    }
}

6. Session GUID Validation Filter

This filter checks if the session’s stored uniqueId matches the token’s uniqueId. If there’s a mismatch, it redirects to the login page.

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.OAuth2AuthenticationToken;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class SessionGuidValidationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication instanceof OAuth2AuthenticationToken) {
            OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
            String tokenUniqueId = oauthToken.getPrincipal().getAttribute("uniqueId");
            HttpSession session = request.getSession(false);

            if (session != null) {
                String sessionUniqueId = (String) session.getAttribute("userUniqueId");
                if (sessionUniqueId != null && !sessionUniqueId.equals(tokenUniqueId)) {
                    new CustomAuthenticationEntryPoint().commence(request, response, null);
                    return;
                }
            }

            // Update session with current GUID if missing
            if (session == null) {
                session = request.getSession(true);
            }
            session.setAttribute("userUniqueId", tokenUniqueId);
        }

        filterChain.doFilter(request, response);
    }
}

7. User Info Service with Caching

This service fetches user info from your auth server’s API and caches it to avoid repeated calls, matching your original logic.

First, create a model for user info:

public class UserInfo {
    private String uniqueId;
    private String username;
    private String email;
    // Add other fields, getters, and setters
}

Then the service:

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class UserInfoService {

    private final RestTemplate restTemplate;
    private final CacheManager cacheManager;
    private final String AUTH_SERVER_USER_INFO_URL = "https://your-auth-server.com/api/userinfo";

    public UserInfoService(RestTemplate restTemplate, CacheManager cacheManager) {
        this.restTemplate = restTemplate;
        this.cacheManager = cacheManager;
    }

    public UserInfo getUserInfo(String accessToken) {
        Cache cache = cacheManager.getCache("userInfoCache");
        if (cache != null) {
            UserInfo cachedUserInfo = cache.get(accessToken, UserInfo.class);
            if (cachedUserInfo != null) {
                return cachedUserInfo;
            }
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(accessToken);
        HttpEntity<Void> requestEntity = new HttpEntity<>(headers);

        ResponseEntity<UserInfo> response = restTemplate.exchange(
                AUTH_SERVER_USER_INFO_URL,
                HttpMethod.GET,
                requestEntity,
                UserInfo.class
        );

        UserInfo userInfo = response.getBody();
        if (cache != null && userInfo != null) {
            cache.put(accessToken, userInfo);
        }

        return userInfo;
    }
}

8. Authentication Success Handler

After successful token validation, this handler fetches user info (if not cached) and stores it in the session.

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.resource.authentication.OAuth2AuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final UserInfoService userInfoService;

    public CustomAuthenticationSuccessHandler(UserInfoService userInfoService) {
        this.userInfoService = userInfoService;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        if (authentication instanceof OAuth2AuthenticationToken) {
            OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
            String accessToken = oauthToken.getToken().getTokenValue();
            UserInfo userInfo = userInfoService.getUserInfo(accessToken);

            HttpSession session = request.getSession();
            session.setAttribute("userInfo", userInfo);
        }

        response.sendRedirect(request.getRequestURI());
    }
}

Key Notes

  • Cache Configuration: Add @EnableCaching to your main application class and configure a cache provider (like Caffeine or Redis) to enable user info caching.
  • Auth Server Integration: Ensure your auth server sets the access_token cookie when redirecting back to the resource server, and that the introspection/user info APIs return the uniqueId attribute.
  • Client Credentials: The resource server needs valid credentials to call your auth server’s introspection endpoint—keep these secure (use environment variables or a secrets manager).

Content of this question originates from stack exchange, asked by Ishank Gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:42:20