Spring Cloud集成OAuth2

Spring Cloud集成OAuth2

什么是OAuth2

OAuth2 是一個授權(quán)框架橙凳,或稱授權(quán)標準,它可以使第三方應用程序或客戶端獲得對HTTP服務上(例如 Google笑撞,GitHub )用戶帳戶信息的有限訪問權(quán)限岛啸。OAuth 2 通過將用戶身份驗證委派給托管用戶帳戶的服務以及授權(quán)客戶端訪問用戶帳戶進行工作

具體介紹請參考大牛文章:阮一峰的《理解OAuth 2.0》

OAuth2能做什么

OAuth2 允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數(shù)據(jù)茴肥。每一個令牌授權(quán)一個特定的網(wǎng)站(例如坚踩,視頻編輯網(wǎng)站)在特定的時段(例如,接下來的2小時內(nèi))內(nèi)訪問特定的資源(例如僅僅是某一相冊中的視頻)瓤狐。這樣瞬铸,OAuth允許用戶授權(quán)第三方網(wǎng)站訪問他們存儲在另外的服務提供者上的信息,而不需要分享他們的訪問許可或他們數(shù)據(jù)的所有內(nèi)容

舉個栗子:比如我們常用的微信公眾號芬首,當我們第一次打開公眾號中網(wǎng)頁的時候會彈出是否允許授權(quán)赴捞,當我們點擊授權(quán)的時候,公眾號網(wǎng)站就能獲取到我們的頭像和昵稱等信息郁稍。這個過程就是通過OAuth2 來實現(xiàn)的

怎么去使用OAuth2

OAuth2是一套協(xié)議,我們可以根據(jù)協(xié)議來自己編寫程序來實現(xiàn)OAuth2的功能胜宇,當然我們也可以通過一些框架來實現(xiàn)耀怜。由于我們的技術(shù)棧是Spring Cloud,那我們就開看看Spring Cloud怎么集成OAuth2桐愉。

Spring Cloud集成OAuth2

引入POM依賴

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

編寫登錄服務

因為授權(quán)是用戶的授權(quán)财破,所以必須有用戶登錄才能授權(quán),這里我們使用spring security來實現(xiàn)登錄功能

建表語句

CREATE TABLE `ts_users` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用戶ID',
  `user_name` varchar(50) NOT NULL COMMENT '用戶名',
  `user_pwd` varchar(100) NOT NULL COMMENT '用戶密碼',
  PRIMARY KEY (`user_id`) USING BTREE,
  UNIQUE KEY `idx_user_name` (`user_name`) USING BTREE,
  KEY `idx_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用戶信息表';

Security配置:SecurityConfig

package com.walle.gatewayserver.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author zhangjiapeng
 * @Package com.walle.gatewayserver.config
 * @Description: ${todo}
 * @date 2019/1/10 16:05
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 用戶驗證服務
    @Autowired
    @Qualifier("userDetailServiceImpl")
    private UserDetailsService userDetailsService;

    // 加密方式 security2.0以后 密碼無法明文保存从诲,必須要經(jīng)過加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/hello");
    }


    // 配置攔截規(guī)則
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().formLogin()
                .and().csrf().disable()
                .httpBasic();
    }
}

登錄實現(xiàn):UserDetailServiceImpl

package com.walle.gatewayserver.service.impl;

import com.walle.common.entity.UserInfo;
import com.walle.gatewayserver.dao.UserInfoDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhangjiapeng
 * @Package com.walle.gatewayserver.service.impl
 * @Description: ${todo}
 * @date 2019/1/10 15:54
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {


    @Autowired
    private UserInfoDao userInfoDao;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 根據(jù)用戶名查找用戶
        UserInfo userInfo = userInfoDao.getByUserName(username);
        if(userInfo == null){
            throw  new  UsernameNotFoundException("用戶不存在");
        }
        // 權(quán)限
        GrantedAuthority authority = new SimpleGrantedAuthority("admin");
        List<GrantedAuthority> authorities = new ArrayList<>(1);
        authorities.add(authority);

        UserDetails userDetails = new User(userInfo.getUsername(),passwordEncoder.encode(userInfo.getPassword()),authorities);
        // 返回用戶信息左痢,注意加密
        return userDetails;
    }
}

暴露接口,這里有兩個接口系洛,一個開放給web俊性,一個開放給android

@RestController
@Slf4j
public class UserInfoController {

    @GetMapping("/user/web")
    public String web(){
        return  "hello web";
    }

    @GetMapping("/user/android")
    public String android(){
        return  "hello android";
    }
}

啟動服務:訪問http://localhost:9001/user/web,然后會看到登錄界面

1547452036997.png

輸入賬號密碼后看到

1547452089041.png

訪問http://localhost:9001/user/android

1547452126286.png

這時候我們的登錄用戶可以訪問到所有的資源描扯,但是我們想讓web登錄的用戶只能看到web定页,android的用戶只能看到android。我們通過OAuth2來實現(xiàn)這個功能

編寫授權(quán)服務

package com.walle.gatewayserver.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

import java.util.concurrent.TimeUnit;

/**
 * @author zhangjiapeng
 * @Package com.walle.gatewayserver.config
 * @Description: ${todo}
 * @date 2019/1/10 16:39
 */

@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("userDetailServiceImpl")
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;


    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public InMemoryTokenStore tokenStore(){
        return new InMemoryTokenStore();
    }


    @Bean
    public InMemoryClientDetailsService clientDetails() {
        return new InMemoryClientDetailsService();
    }

    // 配置token
    @Bean
    @Primary
    public DefaultTokenServices tokenService(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetails());
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
        return tokenServices;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                .userDetailsService(userDetailsService)
                .authenticationManager(authenticationManager)
                .tokenServices(tokenService());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    // 設置客戶端信息
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("web")
                .scopes("web")
                .secret(passwordEncoder.encode("web"))
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .redirectUris("http://www.baidu.com")
                .and().withClient("android")
                .scopes("android")
                .secret(passwordEncoder.encode("android"))
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .redirectUris("http://www.baidu.com");
    }
}

這里是通過內(nèi)存模式配置了兩個客戶端

客戶端:web 密碼: web scopes: web

客戶端:android 密碼: web scopes: android

配置資源服務

package com.walle.gatewayserver.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;

/**
 * @author zhangjiapeng
 * @Package com.walle.gatewayserver.config
 * @Description: ${todo}
 * @date 2019/1/10 16:29
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                    .requestMatchers().antMatchers("/user/**")
                .and()
                    .authorizeRequests()
                        .antMatchers("/user/web").access("#oauth2.hasScope('web')")
                                       .antMatchers("/user/android").access("#oauth2.hasScope('android')")
                        .anyRequest().permitAll();

    }
}

這里我們可以看到绽诚,我們通過配置資源服務攔截所有的/user/**的請求典徊,然后請求/user/android必須有scope=android

這時候在登錄訪問下http://localhost:9001/user/android

1547452947310.png

這時候我們看到報錯頁面杭煎,需要驗證才能訪問。

獲取授權(quán)

訪問http://localhost:9001/oauth/authorize?client_id=android&response_type=code&redirect_uri=http://www.baidu.com

然后跳轉(zhuǎn)到一個授權(quán)頁面

1547453423313.png

是不是跟微信很像卒落,這里說是否授權(quán)web羡铲,我們選擇Approve ,然后頁面跳轉(zhuǎn)到了

https://www.baidu.com/?code=XOCtGr

我們拿到了一個code儡毕,然后我們通過code去獲取access_token

POST訪問http://localhost:9001/oauth/token?clientId=android&grant_type=authorization_code&code=A9bCN5&redirect_uri=http://www.baidu.com

1547453517990.png

這樣我們就獲得了access_token

這時候我們訪問http://localhost:9001/user/android?access_token=59cf521c-026f-4df1-974e-3e4bfc42e432

看到


1547453588529.png

OK犀勒,android可以訪問了,我們試試web能不能訪問呢妥曲,訪問http://localhost:9001/user/web?access_token=59cf521c-026f-4df1-974e-3e4bfc42e432

1547453650333.png

提示我們不合適的scope贾费,這樣我們就實現(xiàn)了不同客戶端訪問不同資源的權(quán)限控制

完!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末檐盟,一起剝皮案震驚了整個濱河市褂萧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌葵萎,老刑警劉巖导犹,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異羡忘,居然都是意外死亡谎痢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門卷雕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來节猿,“玉大人,你說我怎么就攤上這事漫雕”踔觯” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵浸间,是天一觀的道長太雨。 經(jīng)常有香客問我,道長魁蒜,這世上最難降的妖魔是什么囊扳? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮兜看,結(jié)果婚禮上锥咸,老公的妹妹穿的比我還像新娘。我一直安慰自己铣减,他們只是感情好她君,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著葫哗,像睡著了一般缔刹。 火紅的嫁衣襯著肌膚如雪球涛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天校镐,我揣著相機與錄音亿扁,去河邊找鬼。 笑死鸟廓,一個胖子當著我的面吹牛从祝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播引谜,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼牍陌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了员咽?” 一聲冷哼從身側(cè)響起毒涧,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贝室,沒想到半個月后契讲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡滑频,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年捡偏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峡迷。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡银伟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凉当,到底是詐尸還是另有隱情枣申,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布看杭,位于F島的核電站,受9級特大地震影響挟伙,放射性物質(zhì)發(fā)生泄漏楼雹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一尖阔、第九天 我趴在偏房一處隱蔽的房頂上張望贮缅。 院中可真熱鬧,春花似錦介却、人聲如沸谴供。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桂肌。三九已至数焊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間崎场,已是汗流浹背佩耳。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谭跨,地道東北人干厚。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像螃宙,于是被迫代替她去往敵國和親蛮瞄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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