Spring Boot - 使用 JWT、OAuth 以及单独的资源和身份验证服务器

Posted

技术标签:

【中文标题】Spring Boot - 使用 JWT、OAuth 以及单独的资源和身份验证服务器【英文标题】:Spring Boot - Using JWT, OAuth, and Separate Resource and Auth Servers 【发布时间】:2018-06-14 08:32:36 【问题描述】:

我正在尝试构建一个使用 JWT 令牌和 OAuth2 协议的 Spring 应用程序。感谢this tutorial,我可以运行身份验证服务器。但是,我正在努力让资源服务器正常运行。关注这篇文章,感谢对prior question 的回复,这是我目前的尝试:

资源服务器的安全配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Value("$security.signing-key")
    private String signingKey;

    @Value("$security.encoding-strength")
    private Integer clientID;

    @Value("$security.security-realm")
    private String securityRealm;

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() 
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(signingKey);
        return converter;
    

    @Bean
    public TokenStore tokenStore() 
        return new JwtTokenStore(accessTokenConverter());
    

    @Bean ResourceServerTokenServices tokenService() 
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    
    @Override
    public AuthenticationManager authenticationManager() throws Exception 
        OAuth2AuthenticationManager authManager = new OAuth2AuthenticationManager();
        authManager.setTokenServices(tokenService());
        return authManager;
    


资源服务器配置:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter 
    @Autowired
    private ResourceServerTokenServices tokenServices;

@Value("$security.jwt.resource-ids")
private String resourceIds;

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception 
    resources.resourceId(resourceIds).tokenServices(tokenServices);


@Override
public void configure(HttpSecurity http) throws Exception 
    http.requestMatchers().and().authorizeRequests().antMatchers("/actuator/**", "/api-docs/**").permitAll()
            .antMatchers("/**").authenticated();

授权服务器的安全配置(来自注释教程):

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Value("$security.signing-key")
    private String signingKey;

    @Value("$security.encoding-strength")
    private Integer encodingStrength;

    @Value("$security.security-realm")
    private String securityRealm;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception 
        return super.authenticationManager();
    

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(new ShaPasswordEncoder(encodingStrength));
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .httpBasic()
                .realmName(securityRealm)
                .and()
                .csrf()
                .disable();

    

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() 
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signingKey);
        return converter;
    

    @Bean
    public TokenStore tokenStore() 
        return new JwtTokenStore(accessTokenConverter());
    

    @Bean
    @Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name
    public DefaultTokenServices tokenServices() 
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    

现在,当我尝试向资源服务器发出请求时,我收到如下错误:

"error":"invalid_token","error_description":"Invalid access token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiam9obi5kb2UiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXh wIjoxNTE1MTE3NTU4LCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF"

我有几个问题:

据我了解,该问题通常与令牌存储有关。使用 JwtTokenStore 时如何处理服务器分离? 其次,目前,我的资源应用程序依赖于对密钥的访问。根据我对 JWT 和 0Auth 规范的理解,这不是必需的。相反,我应该能够将验证委托给身份验证服务器本身。从 Spring 文档中,我认为以下属性可能适用 security.oauth2.resource.token-info-uri=http://localhost:8080/oauth/check_token。但是,如果我尝试不依赖资源服务器的密钥,那么我在设置 ResourceServerTokenService 时会遇到困难。该服务需要一个令牌存储,JWTTokenStore 使用一个 JwtAccessTokenConverter,而转换器使用一个密钥(删除密钥会导致我之前遇到的同样的 invalid token 错误)。

我真的很难找到展示如何分离 Auth 和 Resource 服务器的文章。任何建议将不胜感激。

编辑: 实际上,资源服务器的代码现在无法编译并显示以下消息:

Caused by: java.lang.IllegalStateException: For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key

【问题讨论】:

嗨,Kelly,我正在尝试同样的事情,我需要将哪个类拆分为身份验证和资源服务器?谢谢 @seanie_oc,这取决于您的应用程序及其当前的结构。我有两个独立的 Spring Boot 应用程序。一个有一个@Configuration 类,用@EnableResourceServer 注释并扩展ResourceServerConfigurerAdapter。另一个有一个@Configuration 类,用@EnableAuthorizationServer 注释并扩展AuthorizationServerConfigurerAdapter。开篇文章中提到的教程提供了一些非常有用的入门信息。希望这会有所帮助。 感谢您提供的提示,我遵循了您在开始时提到的相同教程,但对去哪里有点困惑 【参考方案1】:

当我的公钥格式如下时,我遇到了这个问题:

"-----BEGIN RSA PUBLIC KEY-----\n$encoder.encodeToString(keyPair.public.encoded)\n-----END RSA PUBLIC KEY-----\n"

当我将其更改为:

"-----BEGIN PUBLIC KEY-----\n$encoder.encodeToString(keyPair.public.encoded)\n-----END PUBLIC KEY-----\n"

密钥已被接受。

【讨论】:

这根本没有帮助。你是如何改变你的代码的?我的 JWT 代码中没有公钥,但出现此错误【参考方案2】:

我尝试了 spring oauth,但遇到了同样的错误:

Caused by: java.lang.IllegalStateException: For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key

我的错误是我的公共证书是:

-----BEGIN PUBLIC KEY-----
tadadada
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
tadadada
-----END CERTIFICATE-----

这是不允许的。删除证书,只需让这个文件中的公钥:

-----BEGIN PUBLIC KEY-----
tadadada
-----END PUBLIC KEY-----

然后启动错误就会消失。

关于你的第二个问题,这就是我的理解:

身份验证服务器为您提供加密令牌(使用私钥加密),其中包含您用户的所有权限。

资源服务器使用公钥解密令牌,并假设令牌中包含的权限为真。

希望得到帮助。

【讨论】:

非常感谢。我记得我实际上犯了一个类似的错误,除了我删除了 --BEGIN PUBLIC KEY -- 和 --END PUBLIC KEY -- 行。对于任何偶然发现此问题的人,正如@Oreste Viron 所指出的,您需要删除证书部分,但实际上保留包含公钥的 -- --- 行。 谢谢。我省略了私钥和公钥的 -- 标签。我认为他们不需要

以上是关于Spring Boot - 使用 JWT、OAuth 以及单独的资源和身份验证服务器的主要内容,如果未能解决你的问题,请参考以下文章

如何使用spring boot jwt注销

如何使用 Spring Boot 测试 jwt.*?

使用 Spring Boot 进行 JWT 身份验证

使用 Spring Boot 和 JWT 保护 REST Api

带有 spring-boot 和 spring-security 的 JWT

使用 Stomp 在 Spring Boot 中访问 JWT 令牌