Springsecurity-oauth2之RemoteTokenServices

本文深入探讨了Spring Security OAuth2中RemoteTokenServices类的功能与使用,包括其如何验证远程服务器的token,获取用户信息,以及在OAuth2AuthenticationProcessingFilter中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    Spring-security-oauth2的版本是2.2.3。

    RemoteTokenServices是用于向远程认证服务器验证token,同时获取token对应的用户的信息。

                                                                 图1

    RemoteTokenServices会通过RestTemplate调用远程服务,我们在使用这个类时,要设置checkTokenEndpointUrl、clientId、clientSecret等。

    RemoteTokenServices的源码如下,

    List-1


public class RemoteTokenServices implements ResourceServerTokenServices {
    protected final Log logger = LogFactory.getLog(this.getClass());
    private RestOperations restTemplate = new RestTemplate();
    private String checkTokenEndpointUrl;
    private String clientId;
    private String clientSecret;
    private String tokenName = "token";
    private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();

    public RemoteTokenServices() {
        ((RestTemplate)this.restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400) {
                    super.handleError(response);
                }

            }
        });
    }

    public void setRestTemplate(RestOperations restTemplate) {
        this.restTemplate = restTemplate;
    }

    public void setCheckTokenEndpointUrl(String checkTokenEndpointUrl) {
        this.checkTokenEndpointUrl = checkTokenEndpointUrl;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
        this.tokenConverter = accessTokenConverter;
    }

    public void setTokenName(String tokenName) {
        this.tokenName = tokenName;
    }

    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap();
        formData.add(this.tokenName, accessToken);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret));
        Map<String, Object> map = this.postForMap(this.checkTokenEndpointUrl, formData, headers);
        if (map.containsKey("error")) {
            this.logger.debug("check_token returned error: " + map.get("error"));
            throw new InvalidTokenException(accessToken);
        } else if (!Boolean.TRUE.equals(map.get("active"))) {
            this.logger.debug("check_token returned active attribute: " + map.get("active"));
            throw new InvalidTokenException(accessToken);
        } else {
            return this.tokenConverter.extractAuthentication(map);
        }
    }

    public OAuth2AccessToken readAccessToken(String accessToken) {
        throw new UnsupportedOperationException("Not supported: read access token");
    }

    private Stri』g getAuthorizationHeader(String clientId, String clientSecret) {
        if (clientId == null || clientSecret == null) {
            this.logger.warn("Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.");
        }

        String creds = String.format("%s:%s", clientId, clientSecret);

        try {
            return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException var5) {
            throw new IllegalStateException("Could not convert String");
        }
    }

    private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
        if (headers.getContentType() == null) {
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        }

        Map map = (Map)this.restTemplate.exchange(path, HttpMethod.POST, new HttpEntity(formData, headers), Map.class, new Object[0]).getBody();
        return map;
    }
}

    图1的步骤4中,请求/oauth/check_token,如下List-2,查询token的合法性,之后返回其信息。

    List-2

package org.springframework.security.oauth2.provider.endpoint;

@FrameworkEndpoint
public class CheckTokenEndpoint {
    private ResourceServerTokenServices resourceServerTokenServices;
    private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
    protected final Log logger = LogFactory.getLog(this.getClass());
    private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();

    public CheckTokenEndpoint(ResourceServerTokenServices resourceServerTokenServices) {
        this.resourceServerTokenServices = resourceServerTokenServices;
    }

    public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) {
        this.exceptionTranslator = exceptionTranslator;
    }

    public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
        this.accessTokenConverter = accessTokenConverter;
    }

    @RequestMapping({"/oauth/check_token"})
    @ResponseBody
    public Map<String, ?> checkToken(@RequestParam("token") String value) {
        OAuth2AccessToken token = this.resourceServerTokenServices.readAccessToken(value);
        if (token == null) {
            throw new InvalidTokenException("Token was not recognised");
        } else if (token.isExpired()) {
            throw new InvalidTokenException("Token has expired");
        } else {
            OAuth2Authentication authentication = this.resourceServerTokenServices.loadAuthentication(token.getValue());
            Map<String, ?> response = this.accessTokenConverter.convertAccessToken(token, authentication);
            return response;
        }
    }
......

    图1中的步骤8中,如果UserDetailsService不为空,那么会调用它的loadUserByUsername方法来获取用户信息,

    List-3


public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter {
    ......

    public Authentication extractAuthentication(Map<String, ?> map) {
        if (map.containsKey("user_name")) {
            Object principal = map.get("user_name");
            Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
            if (this.userDetailsService != null) {
                UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get("user_name"));
                authorities = user.getAuthorities();
                principal = user;
            }

            return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
        } else {
            return null;
        }
    }
......

    建议读者阅读源码。

    在哪里使用到RemoteTokenServices呢,看如下图2,OAuth2AuthenticationProcessingFilter会拦截请求,将access_token转换为用户信息。

                                                                  图2

 

Reference:

  1. http://springcloud.cn/view/431
  2. Spring-security-oauth2的源码

转载于:https://my.oschina.net/u/2518341/blog/3021642

<think>嗯,用户现在问的是关于Spring Security OAuth2如何验证Token的关键代码。之前他们问过如何用JwtHelper解析由JwtAccessTokenConverter生成的Token,现在可能是想更深入了解整个验证流程,特别是关键部分的代码实现。 首先,我需要回顾一下用户之前的对话。之前他们的问题集中在使用JwtHelper和RSA公钥来解析和验证Token,而这次的问题更广泛,可能涉及整个OAuth2的Token验证机制,而不仅仅是JWT的部分。用户可能是在开发资源服务器,需要配置Token验证,或者在调试时遇到问题,需要查看关键代码来排查错误。 接下来,我需要确定用户的具体需求。他们提到“关键代码”,可能希望看到核心的配置类或方法,比如资源服务器的配置、Token服务的配置,以及如何从JWT中提取权限信息。同时,用户可能想了解不同的验证方式,比如远程验证(CheckToken端点)和本地验证(JWT),以及它们的代码实现。 还要考虑用户可能的背景。他们可能在使用Spring Security OAuth2的旧项目,或者正在迁移到Spring Security 5的新版本。如果是旧项目,可能需要使用@EnableResourceServer和ResourceServerConfigurerAdapter,而新版本可能更依赖SecurityFilterChain的配置。需要明确说明不同版本的差异,并提供对应的代码示例。 另外,用户之前的问题涉及JWT的解析,这次可能会涉及到JwtDecoder的配置,如何从公钥或JWK Set URI创建解码器,以及如何自定义权限提取。这部分需要详细说明,包括依赖项、配置类和方法,以及可能遇到的常见问题,比如公钥不匹配、Token过期等。 需要分步骤回答,结构清晰。比如先介绍资源服务器的配置,再分远程验证和本地验证两种情况,分别给出代码示例。然后讲解权限提取的关键点,如自定义转换,最后总结常见问题和注意事项。这样用户可以根据自己的情况选择合适的验证方式,并了解如何实现。 还要注意代码的正确性,比如在Spring Security 5.2+中,ResourceServerConfigurerAdapter已经被弃用,应该使用SecurityFilterChain来配置资源服务器。需要指出这一点,并提供新旧版本的配置示例,避免用户在使用时遇到兼容性问题。 另外,用户可能需要知道如何配置JWK Set端点,比如使用Auth0或Okta的情况,这时候需要提供对应的JwtDecoder配置方法,并说明需要添加的依赖,如spring-security-oauth2-jose。 最后,总结常见问题,帮助用户快速排查可能遇到的错误,比如依赖缺失、签名不匹配、Token过期等,给出对应的解决方法,这样用户在遇到问题时可以快速参考。</think>以下是 **Spring Security OAuth2 验证 Token 的核心流程与关键代码**解析,涵盖**远程验证**(CheckToken 端点)和**本地验证**(JWT)两种场景: --- ### **1. 资源服务器配置(核心入口)** 无论是远程验证还是本地 JWT 验证,均需配置资源服务器: ```java @Configuration @EnableResourceServer // 启用资源服务器 public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated(); } // 其他配置见下文 } ``` --- ### **2. Token 验证的两种模式** #### **(1) 远程验证(CheckToken 端点)** **适用场景**:Token 存储在授权服务器,资源服务器需调用 `/oauth/check_token` 验证有效性。 ##### **关键配置代码**: ```java @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Bean public RemoteTokenServices remoteTokenService() { RemoteTokenServices service = new RemoteTokenServices(); service.setCheckTokenEndpointUrl("http://auth-server/oauth/check_token"); // 授权服务器端点 service.setClientId("client-id"); // 客户端ID service.setClientSecret("client-secret"); // 客户端密钥 return service; } @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.tokenServices(remoteTokenService()); // 注入验证服务 } } ``` --- #### **(2) 本地验证(JWT 验签)** **适用场景**:Token 使用 JWT 格式,资源服务器直接通过公钥验证签名(无需调用远程端点)。 ##### **关键配置代码**: ```java @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setVerifierKey(getPublicKey()); // 加载公钥(见下文) return converter; } @Bean public DefaultTokenServices tokenServices() { DefaultTokenServices services = new DefaultTokenServices(); services.setTokenStore(tokenStore()); return services; } @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.tokenServices(tokenServices()); } // 加载公钥方法(示例) private String getPublicKey() { return "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv\n" + "...(公钥内容)...\n" + "-----END PUBLIC KEY-----"; } } ``` --- ### **3. 权限提取关键点** 解析 JWT 后需从 Claims 中提取权限(如 `authorities` 字段): ```java @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setVerifierKey(publicKey); // 自定义权限转换 converter.setAccessTokenConverter(new DefaultAccessTokenConverter() { @Override public OAuth2Authentication extractAuthentication(Map<String, ?> claims) { OAuth2Authentication auth = super.extractAuthentication(claims); Collection<?> authorities = (Collection<?>) claims.get("authorities"); // 提取权限 auth.setAuthorities(authorities.stream() .map(a -> new SimpleGrantedAuthority(a.toString())) .collect(Collectors.toList())); return auth; } }); return converter; } ``` --- ### **4. 新版 Spring Security 配置(5.2+)** 若使用 **Spring Security 5.2+**,推荐使用 `SecurityFilterChain` 替代旧版配置: ```java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .antMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .decoder(jwtDecoder()) // 配置 JWT 解码器 ) ); return http.build(); } // 配置 JwtDecoder(支持公钥或 JWK Set URI) @Bean public JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withPublicKey(publicKey).build(); // 公钥方式 // 或使用 JWK Set 端点:NimbusJwtDecoder.withJwkSetUri(jwkSetUrl).build(); } } ``` --- ### **5. 常见问题与排查** | **问题** | **解决方法** | |--------------------------|-----------------------------------------------------------------------------| | `Invalid token` 错误 | 检查公钥是否匹配、Token 是否过期、签名算法是否一致(如 RS256 vs HS256) | | 权限未正确加载 | 检查 JWT 中是否包含 `authorities` 字段,或自定义 `JwtAccessTokenConverter` | | 依赖冲突 | 确保使用兼容版本(如 `spring-security-oauth2` 与 `spring-boot` 版本匹配) | | JWK Set 端点无法访问 | 验证网络连通性,检查授权服务器是否暴露 `/oauth/jwks` 端点 | --- ### **6. 关键依赖** 确保 `pom.xml` 包含以下依赖: ```xml <!-- OAuth2 核心 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.5.2.RELEASE</version> </dependency> <!-- JWT 支持(本地验证需添加) --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.1.1.RELEASE</version> </dependency> <!-- 新版 Spring Security 6+ 使用 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency> ``` --- ### **总结** - **核心逻辑**:根据 Token 类型(不透明Token/JWT)选择远程或本地验证。 - **关键类**: - `RemoteTokenServices`:远程验证 Token。 - `JwtTokenStore` + `JwtAccessTokenConverter`:本地 JWT 验签。 - `JwtDecoder`(新版):直接解析 JWT。 - **结果**:验证通过后,Spring Security 自动将权限绑定到 `SecurityContext`,供后续鉴权使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值