前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用spring-security-oauth2作为client实现

使用spring-security-oauth2作为client实现

作者头像
code4it
发布2018-09-17 15:26:23
5K0
发布2018-09-17 15:26:23
举报
文章被收录于专栏:码匠的流水账码匠的流水账

本文主要讲一下如何使用spring security oauth2作为一个client来使用

四种模式

OAuth 2.0定义了四种授权方式。

  • 授权码模式(authorization code)
  • 简化模式(implicit)(client为浏览器/前端应用)
  • 密码模式(resource owner password credentials)(用户密码暴露给client端不安全)
  • 客户端模式(client credentials)(主要用于api认证,跟用户无关)

这里以authorization code模式为例

实现client的主要思路

  • 需要新建一个处理redirectUri的controller或者filter进行处理
  • 根据authentication code去请求token
  • 获取token之后将token与用户绑定
  • 之后就可以使用token去获取授权的资源保持下来

OAuth2RestTemplate(封装获取token方法)

对rest template的封装,为获取token等提供便捷方法 DefaultUserInfoRestTemplateFactory实例了OAuth2RestTemplate

DefaultUserInfoRestTemplateFactory

spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/security/oauth2/resource/DefaultUserInfoRestTemplateFactory.java

/**
 * Factory used to create the {@link OAuth2RestTemplate} used for extracting user info
 * during authentication if none is available.
 *
 * @author Dave Syer
 * @author Stephane Nicoll
 * @since 1.5.0
 */
public class DefaultUserInfoRestTemplateFactory implements UserInfoRestTemplateFactory {

    private static final AuthorizationCodeResourceDetails DEFAULT_RESOURCE_DETAILS;

    static {
        AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
        details.setClientId("<N/A>");
        details.setUserAuthorizationUri("Not a URI because there is no client");
        details.setAccessTokenUri("Not a URI because there is no client");
        DEFAULT_RESOURCE_DETAILS = details;
    }

    private final List<UserInfoRestTemplateCustomizer> customizers;

    private final OAuth2ProtectedResourceDetails details;

    private final OAuth2ClientContext oauth2ClientContext;

    private OAuth2RestTemplate oauth2RestTemplate;

    public DefaultUserInfoRestTemplateFactory(
            ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
            ObjectProvider<OAuth2ProtectedResourceDetails> details,
            ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
        this.customizers = customizers.getIfAvailable();
        this.details = details.getIfAvailable();
        this.oauth2ClientContext = oauth2ClientContext.getIfAvailable();
    }

    @Override
    public OAuth2RestTemplate getUserInfoRestTemplate() {
        if (this.oauth2RestTemplate == null) {
            this.oauth2RestTemplate = createOAuth2RestTemplate(
                    this.details == null ? DEFAULT_RESOURCE_DETAILS : this.details);
            this.oauth2RestTemplate.getInterceptors()
                    .add(new AcceptJsonRequestInterceptor());
            AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
            accessTokenProvider.setTokenRequestEnhancer(new AcceptJsonRequestEnhancer());
            this.oauth2RestTemplate.setAccessTokenProvider(accessTokenProvider);
            if (!CollectionUtils.isEmpty(this.customizers)) {
                AnnotationAwareOrderComparator.sort(this.customizers);
                for (UserInfoRestTemplateCustomizer customizer : this.customizers) {
                    customizer.customize(this.oauth2RestTemplate);
                }
            }
        }
        return this.oauth2RestTemplate;
    }

    private OAuth2RestTemplate createOAuth2RestTemplate(
            OAuth2ProtectedResourceDetails details) {
        if (this.oauth2ClientContext == null) {
            return new OAuth2RestTemplate(details);
        }
        return new OAuth2RestTemplate(details, this.oauth2ClientContext);
    }

}

这个提供了OAuth2RestTemplate

ResourceServerTokenServicesConfiguration

spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java

/**
 * Configuration for an OAuth2 resource server.
 *
 * @author Dave Syer
 * @author Madhura Bhave
 * @author Eddú Meléndez
 * @since 1.3.0
 */
@Configuration
@ConditionalOnMissingBean(AuthorizationServerEndpointsConfiguration.class)
public class ResourceServerTokenServicesConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public UserInfoRestTemplateFactory userInfoRestTemplateFactory(
            ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
            ObjectProvider<OAuth2ProtectedResourceDetails> details,
            ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
        return new DefaultUserInfoRestTemplateFactory(customizers, details,
                oauth2ClientContext);
    }

    //......
}

而DefaultUserInfoRestTemplateFactory主要是在ResourceServerTokenServicesConfiguration配置中创建的 这个是给resource server用的,因而client要使用的话,需要自己创建

redirectUri的处理(OAuth2ClientAuthenticationProcessingFilter)

spring security oauth2 照样提供了便利的类可供处理: spring-security-oauth2-2.0.14.RELEASE-sources.jar!/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilter.java

/**
 * An OAuth2 client filter that can be used to acquire an OAuth2 access token from an authorization server, and load an
 * authentication object into the SecurityContext
 * 
 * @author Vidya Valmikinathan
 * 
 */
public class OAuth2ClientAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {

    public OAuth2RestOperations restTemplate;

    private ResourceServerTokenServices tokenServices;

    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();

    private ApplicationEventPublisher eventPublisher;

    public OAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        setAuthenticationManager(new NoopAuthenticationManager());
        setAuthenticationDetailsSource(authenticationDetailsSource);
    }
    //......
}

它的构造器需要传入defaultFilterProcessesUrl,用于指定这个filter拦截哪个url。 它依赖OAuth2RestTemplate来获取token 还依赖ResourceServerTokenServices进行校验token

oauth client config

经过上面的分析,这个config主要是配置3个

  • OAuth2RestTemplate(获取token)
  • ResourceServerTokenServices(校验token)
  • OAuth2ClientAuthenticationProcessingFilter(拦截redirectUri,根据authentication code获取token,依赖前面两个对象)
@Configuration
@EnableOAuth2Client
public class Oauth2ClientConfig {

    @Bean
    public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext context, OAuth2ProtectedResourceDetails details) {
        OAuth2RestTemplate template = new OAuth2RestTemplate(details, context);

        AuthorizationCodeAccessTokenProvider authCodeProvider = new AuthorizationCodeAccessTokenProvider();
        authCodeProvider.setStateMandatory(false);
        AccessTokenProviderChain provider = new AccessTokenProviderChain(
                Arrays.asList(authCodeProvider));
        template.setAccessTokenProvider(provider);
    }

    /**
     * 注册处理redirect uri的filter
     * @param oauth2RestTemplate
     * @param tokenService
     * @return
     */
    @Bean
    public OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter(
            OAuth2RestTemplate oauth2RestTemplate,
            RemoteTokenServices tokenService) {
        OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(redirectUri);
        filter.setRestTemplate(oauth2RestTemplate);
        filter.setTokenServices(tokenService);

        //设置回调成功的页面
        filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler() {
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                this.setDefaultTargetUrl("/home");
                super.onAuthenticationSuccess(request, response, authentication);
            }
        });
        return filter;
    }

    /**
     * 注册check token服务
     * @param details
     * @return
     */
    @Bean
    public RemoteTokenServices tokenService(OAuth2ProtectedResourceDetails details) {
        RemoteTokenServices tokenService = new RemoteTokenServices();
        tokenService.setCheckTokenEndpointUrl(checkTokenUrl);
        tokenService.setClientId(details.getClientId());
        tokenService.setClientSecret(details.getClientSecret());
        return tokenService;
    }
}

security config

上面定义了OAuth2ClientAuthenticationProcessingFilter,还有最重要的一步,就是配置filter的顺序,如果配置不当则前功尽弃。 这里需要配置在BasicAuthenticationFilter之前

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll()
        .and()
        .addFilterBefore(oauth2ClientAuthenticationProcessingFilter,BasicAuthenticationFilter.class)
        .csrf().disable();
    }
}

异常

Possible CSRF detected - state parameter was required but no state could be found

有的是说本地开发,auth server与client都是localhost,造成JSESSIONID相互影响问题。可以通过配置client的context-path或者session名称来解决

这里配置了session

server:
  port: 8081
  session:
    cookie:
      name: OAUTH2SESSION

不过貌似没解决,最后先临时关闭AuthorizationCodeAccessTokenProvider的stateMandatory属性

client相关yml配置

security:
  oauth2:
    client:
      clientId: demoApp
      clientSecret: demoAppSecret
      accessTokenUri: ${TOKEN_URL:http://localhost:8080}/oauth/token
      userAuthorizationUri: ${USER_AUTH_URL:http://localhost:8080}/oauth/authorize
      pre-established-redirect-uri: http://localhost:8081/callback

验证

http://localhost:8080/oauth/authorize?response_type=code&client_id=demoApp&redirect_uri=http://localhost:8081/callback

之后就是登陆,然后授权,然后就成功回调,然后跳转到设置的/home

回调之后,会将token与当前session绑定,之后利用OAuth2RestTemplate可以透明访问授权资源

@RequestMapping("")
@RestController
public class DemoController {

    @Autowired
    OAuth2RestTemplate oAuth2RestTemplate;

    @Value("${client.resourceServerUrl}")
    String resourceServerUrl;

    @GetMapping("/demo/{id}")
    public String getDemoAuthResource(@PathVariable Long id){
        ResponseEntity<String> responseEntity = oAuth2RestTemplate.getForEntity(resourceServerUrl+"/demo/"+id, String.class);
        return responseEntity.getBody();
    }
}

这样就大功告成了。

doc

  • Possible CSRF detected - state parameter was present but no state could be found #322
  • Possible CSRF detected - state parameter was required but no state could be found #822
  • oauth-security使用时常见错误
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-12-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码匠的流水账 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 四种模式
  • 实现client的主要思路
  • OAuth2RestTemplate(封装获取token方法)
    • DefaultUserInfoRestTemplateFactory
      • ResourceServerTokenServicesConfiguration
      • redirectUri的处理(OAuth2ClientAuthenticationProcessingFilter)
      • oauth client config
      • security config
      • 异常
      • client相关yml配置
      • 验证
      • doc
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档