使用spring-security-oauth2作為client實現(xiàn)

本文主要講一下如何使用spring security oauth2作為一個client來使用

四種模式

OAuth 2.0定義了四種授權(quán)方式选酗。

  • 授權(quán)碼模式(authorization code)
  • 簡化模式(implicit)(client為瀏覽器/前端應(yīng)用)
  • 密碼模式(resource owner password credentials)(用戶密碼暴露給client端不安全)
  • 客戶端模式(client credentials)(主要用于api認證桃序,跟用戶無關(guān))

這里以authorization code模式為例

實現(xiàn)client的主要思路

  • 需要新建一個處理redirectUri的controller或者filter進行處理
  • 根據(jù)authentication code去請求token
  • 獲取token之后將token與用戶綁定
  • 之后就可以使用token去獲取授權(quán)的資源

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配置中創(chuàng)建的
這個是給resource server用的,因而client要使用的話啥辨,需要自己創(chuàng)建

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);
    }
    //......
}    

它的構(gòu)造器需要傳入defaultFilterProcessesUrl是偷,用于指定這個filter攔截哪個url淌实。
它依賴OAuth2RestTemplate來獲取token
還依賴ResourceServerTokenServices進行校驗token

oauth client config

經(jīng)過上面的分析,這個config主要是配置3個

  • OAuth2RestTemplate(獲取token)
  • ResourceServerTokenServices(校驗token)
  • OAuth2ClientAuthenticationProcessingFilter(攔截redirectUri,根據(jù)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);


        //設(shè)置回調(diào)成功的頁面
        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服務(wù)
     * @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的順序究飞,如果配置不當(dāng)則前功盡棄置谦。

這里需要配置在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

有的是說本地開發(fā)堂鲤,auth server與client都是localhost,造成JSESSIONID相互影響問題媒峡∥疗埽可以通過配置client的context-path或者session名稱來解決

這里配置了session

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

不過貌似沒解決,最后先臨時關(guān)閉AuthorizationCodeAccessTokenProvider的stateMandatory屬性

client相關(guān)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

之后就是登陸谅阿,然后授權(quán)半哟,然后就成功回調(diào),然后跳轉(zhuǎn)到設(shè)置的/home

回調(diào)之后签餐,會將token與當(dāng)前session綁定寓涨,之后利用OAuth2RestTemplate可以透明訪問授權(quán)資源

@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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氯檐,一起剝皮案震驚了整個濱河市戒良,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冠摄,老刑警劉巖糯崎,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異河泳,居然都是意外死亡沃呢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門乔询,熙熙樓的掌柜王于貴愁眉苦臉地迎上來樟插,“玉大人,你說我怎么就攤上這事竿刁』拼福” “怎么了?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵食拜,是天一觀的道長鸵熟。 經(jīng)常有香客問我,道長负甸,這世上最難降的妖魔是什么流强? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮呻待,結(jié)果婚禮上打月,老公的妹妹穿的比我還像新娘。我一直安慰自己蚕捉,他們只是感情好奏篙,可當(dāng)我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般秘通。 火紅的嫁衣襯著肌膚如雪为严。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天肺稀,我揣著相機與錄音第股,去河邊找鬼。 笑死话原,一個胖子當(dāng)著我的面吹牛夕吻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播稿静,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼梭冠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了改备?” 一聲冷哼從身側(cè)響起控漠,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悬钳,沒想到半個月后盐捷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡默勾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年碉渡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片母剥。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡滞诺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出环疼,到底是詐尸還是另有隱情习霹,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布炫隶,位于F島的核電站淋叶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏伪阶。R本人自食惡果不足惜煞檩,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望栅贴。 院中可真熱鬧斟湃,春花似錦、人聲如沸檐薯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哄酝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祷膳,已是汗流浹背陶衅。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留直晨,地道東北人搀军。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像勇皇,于是被迫代替她去往敵國和親罩句。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內(nèi)容