自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

實(shí)戰(zhàn):畫了幾張圖,終于把OAuth2搞清楚了

開發(fā) 前端
本文從原理、應(yīng)用場景、認(rèn)證流程出發(fā),對oauth2進(jìn)行了基本的講解,并且手把手帶大家完成了項(xiàng)目的搭建。大家在對授權(quán)碼模式、簡化模式、密碼模式、客戶端模式進(jìn)行測試的同時(shí)要將重點(diǎn)放到授權(quán)碼模式上。

理論

OAuth?是一個(gè)關(guān)于授權(quán)(authorization)的開放網(wǎng)絡(luò)標(biāo)準(zhǔn),用來授權(quán)第三方應(yīng)用獲取用戶數(shù)據(jù),是目前最流行的授權(quán)機(jī)制,它當(dāng)前的版本是2.0。

應(yīng)用場景

假如你正在“網(wǎng)站A”上沖浪,看到一篇帖子表示非常喜歡,當(dāng)你情不自禁的想要點(diǎn)贊時(shí),它會(huì)提示你進(jìn)行登錄操作。

圖片

打開登錄頁面你會(huì)發(fā)現(xiàn),除了最簡單的賬戶密碼登錄外,還為我們提供了微博、微信、QQ等快捷登錄方式。假設(shè)選擇了快捷登錄,它會(huì)提示我們掃碼或者輸入賬號密碼進(jìn)行登錄。

圖片

登錄成功之后便會(huì)將QQ/微信的昵稱和頭像等信息回填到“網(wǎng)站A”中,此時(shí)你就可以進(jìn)行點(diǎn)贊操作了。

名詞定義

在詳細(xì)講解oauth2之前,我們先來了解一下它里邊用到的名詞定義吧:

Client:客戶端,它本身不會(huì)存儲(chǔ)用戶快捷登錄的賬號和密碼,只是通過資源擁有者的授權(quán)去請求資源服務(wù)器的資源,即例子中的網(wǎng)站A;

Resource Owner:資源擁有者,通常是用戶,即例子中擁有QQ/微信賬號的用戶;

Authorization Server:認(rèn)證服務(wù)器,可以提供身份認(rèn)證和用戶授權(quán)的服務(wù)器,即給客戶端頒發(fā)token和校驗(yàn)token;

Resource Server:資源服務(wù)器,存儲(chǔ)用戶資源的服務(wù)器,即例子中的QQ/微信存儲(chǔ)的用戶信息;

認(rèn)證流程

圖片

如圖是oauth2官網(wǎng)的認(rèn)證流程圖,我們來分析一下:

  • A客戶端向資源擁有者發(fā)送授權(quán)申請;
  • B資源擁有者同意客戶端的授權(quán),返回授權(quán)碼;
  • C客戶端使用授權(quán)碼向認(rèn)證服務(wù)器申請令牌token;
  • D認(rèn)證服務(wù)器對客戶端進(jìn)行身份校驗(yàn),認(rèn)證通過后發(fā)放令牌;
  • E客戶端拿著認(rèn)證服務(wù)器頒發(fā)的令牌去資源服務(wù)器請求資源;
  • F資源服務(wù)器校驗(yàn)令牌的有效性,返回給客戶端資源信息;

為了大家更好的理解,阿Q特地畫了一張圖:

圖片

到這兒,相信大家對理論知識(shí)已經(jīng)掌握的差不多了,接下來我們就進(jìn)入實(shí)戰(zhàn)訓(xùn)練吧。

實(shí)戰(zhàn)

在正式開始搭建項(xiàng)目之前我們先來做一些準(zhǔn)備工作:要想使用oauth2的服務(wù),我們得先創(chuàng)建幾張表。

數(shù)據(jù)庫

oauth2相關(guān)的建表語句可以參考官方初始化sql,也可以查看阿Q項(xiàng)目中的init.sql文件,回復(fù)“oauth2”獲取源碼。

圖片

至于表結(jié)構(gòu),大家可以先大體了解下,其中字段的含義,在init.sql文件中阿Q已經(jīng)做了說明。

  • oauth_client_details:存儲(chǔ)客戶端的配置信息,操作該表的類主要是JdbcClientDetailsService.java;
  • oauth_access_token:存儲(chǔ)生成的令牌信息,操作該表的類主要是JdbcTokenStore.java;
  • oauth_client_token:在客戶端系統(tǒng)中存儲(chǔ)從服務(wù)端獲取的令牌數(shù)據(jù),操作該表的類主要是JdbcClientDetailsService.java;
  • oauth_code:存儲(chǔ)授權(quán)碼信息與認(rèn)證信息,即只有g(shù)rant_type?為authorization_code?時(shí),該表才會(huì)有數(shù)據(jù),操作該表的類主要是JdbcAuthorizationCodeServices.java;
  • oauth_approvals:存儲(chǔ)用戶的授權(quán)信息;
  • oauth_refresh_token:存儲(chǔ)刷新令牌的refresh_token?,如果客戶端的grant_type?不支持refresh_token?,那么不會(huì)用到這張表,操作該表的類主要是JdbcTokenStore;

在oauth_client_details表中添加一條數(shù)據(jù)

client_id:cheetah_one //客戶端名稱,必須唯一
resource_ids:product_api //客戶端所能訪問的資源id集合,多個(gè)資源時(shí)用逗號(,)分隔
client_secret:$2a$10$h/TmLPvXozJJHXDyJEN22ensJgaciomfpOc9js9OonwWIdAnRQeoi //客戶端的訪問密碼
scope:read,write //客戶端申請的權(quán)限范圍,可選值包括read,write,trust。若有多個(gè)權(quán)限范圍用逗號(,)分隔
authorized_grant_types:client_credentials,implicit,authorization_code,refresh_token,password //指定客戶端支持的grant_type,可選值包括authorization_code,password,refresh_token,implicit,client_credentials, 若支持多個(gè)grant_type用逗號(,)分隔
web_server_redirect_uri:http://www.baidu.com //客戶端的重定向URI,可為空, 當(dāng)grant_type為authorization_code或implicit時(shí), 在Oauth的流程中會(huì)使用并檢查與注冊時(shí)填寫的redirect_uri是否一致
access_token_validity:43200 //設(shè)定客戶端的access_token的有效時(shí)間值(單位:),可選, 若不設(shè)定值則使用默認(rèn)的有效時(shí)間值(60 * 60 * 12, 12小時(shí))
autoapprove:false //設(shè)置用戶是否自動(dòng)Approval操作, 默認(rèn)值為 'false', 可選值包括 'true','false', 'read','write'

數(shù)據(jù)庫中對密碼進(jìn)行了加密處理,大家可以在此路徑下自行生成

圖片

用戶角色相關(guān)的表也在init.sql文件中,表結(jié)構(gòu)非常簡單,大家自行查閱。我的初始化數(shù)據(jù)為

圖片

依賴引入

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>

至于其它依賴,大家可以根據(jù)需要自行引入,不再贅述,回復(fù)“oauth2”獲取源碼。

資源服務(wù)

配置文件對服務(wù)端口、應(yīng)用名稱、數(shù)據(jù)庫、mybatis和日志進(jìn)行了配置。

寫了一個(gè)簡單的控制層代碼,用來模擬資源訪問

@RestController
@RequestMapping("/product")
public class ProductController {

@GetMapping("/findAll")
public String findAll(){
return "產(chǎn)品列表查詢成功";
}
}

接著創(chuàng)建配置類繼承ResourceServerConfigurerAdapter并增加@EnableResourceServer注解開啟資源服務(wù),重寫兩個(gè)configure方法

/**
* 指定token的持久化策略
* InMemoryTokenStore 表示將token存儲(chǔ)在內(nèi)存中
* RedisTokenStore 表示將token存儲(chǔ)在redis中
* JdbcTokenStore 表示將token存儲(chǔ)在數(shù)據(jù)庫中
* @return
*/
@Bean
public TokenStore jdbcTokenStore(){
return new JdbcTokenStore(dataSource);
}

/**
* 指定當(dāng)前資源的id和token的存儲(chǔ)策略
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//此處的id可以寫在配置文件中,這里我們先寫死
resources.resourceId("product_api").tokenStore(jdbcTokenStore());
}


/**
* 設(shè)置請求權(quán)限和header處理
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//固定寫法
http.authorizeRequests()
//指定不同請求方式訪問資源所需的權(quán)限,一般查詢是read,其余都是write
.antMatchers(HttpMethod.GET,"/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST,"/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PATCH,"/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PUT,"/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.DELETE,"/**").access("#oauth2.hasScope('write')")
.and()
.headers().addHeaderWriter((request,response) -> {
//域名不同或者子域名不一樣并且是ajax請求就會(huì)出現(xiàn)跨域問題
//允許跨域
response.addHeader("Access-Control-Allow-Origin","*");
//跨域中會(huì)出現(xiàn)預(yù)檢請求,如果不能通過,則真正請求也不會(huì)發(fā)出
//如果是跨域的預(yù)檢請求,則原封不動(dòng)向下傳遞請求頭信息,否則預(yù)檢請求會(huì)丟失請求頭信息(主要是token信息)
if(request.getMethod().equals("OPTIONS")){
response.setHeader("Access-Control-Allow-Methods",request.getHeader("Access-Control-Allow-Methods"));
response.setHeader("Access-Control-Allow-Headers",request.getHeader("Access-Control-Allow-Headers"));
}
});
}

當(dāng)然我們也可以配置忽略校驗(yàn)的??url??,在上邊的??public void configure(HttpSecurity http) throws Exception??中進(jìn)行配置

ExpressionUrlAuthorizationConfigurer<HttpSecurity>
.ExpressionInterceptUrlRegistry config = http.requestMatchers().anyRequest()
.and()
.authorizeRequests();
properties.getUrls().forEach(e -> {
config.antMatchers(e).permitAll();
});

因?yàn)槲覀兪切枰M(jìn)行校驗(yàn)的,所以我把對應(yīng)的代碼給注釋掉了,大家可以回復(fù)“oauth2”下載源碼自行查看。

然后將實(shí)現(xiàn)了UserDetails?的SysUser?和實(shí)現(xiàn)了GrantedAuthority?的SysRole?放到項(xiàng)目中,當(dāng)請求發(fā)過來時(shí),oauth2會(huì)幫我們自行校驗(yàn)。

認(rèn)證服務(wù)

配置文件對服務(wù)端口、應(yīng)用名稱、數(shù)據(jù)庫、mybatis和日志進(jìn)行了配置。

Security配置

還是和之前Security+JWT組合拳的配置大同小異,不了解的可以先看下該文。

①將繼承了UserDetailsService?的ISysUserService?的實(shí)現(xiàn)類SysUserServiceImpl?重寫loadUserByUsername方法

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.baseMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
}

②繼承WebSecurityConfigurerAdapter?類,增加@EnableWebSecurity注解并重寫方法

/**
* 指定認(rèn)證對象的來源和加密方式
* @param auth
* @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}

/**
* 安全攔截機(jī)制(最重要)
* @param httpSecurity
* @throws Exception
*/
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
//CSRF禁用,因?yàn)椴皇褂胹ession
.csrf().disable()
.authorizeRequests()
//登錄接口和靜態(tài)資源不需要認(rèn)證
.antMatchers("/login*","/css/*").permitAll()
//除上面的所有請求全部需要認(rèn)證通過才能訪問
.anyRequest().authenticated()
//返回HttpSecurity以進(jìn)行進(jìn)一步的自定義,證明是一次新的配置的開始
.and()
.formLogin()
//如果未指定此頁面,則會(huì)跳轉(zhuǎn)到默認(rèn)頁面
// .loginPage("/login.html")
.loginProcessingUrl("/login")
.permitAll()
//認(rèn)證失敗處理類
.failureHandler(customAuthenticationFailureHandler);
}

/**
* AuthenticationManager 對象在OAuth2.0認(rèn)證服務(wù)中要使用,提前放入IOC容器中
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

?Au?thorizationServer配置

①繼承AuthorizationServerConfigurerAdapter類,增加@EnableAuthorizationServer注解開啟認(rèn)證服務(wù)

②依賴注入,注入7個(gè)實(shí)例Bean對象

/**
* 數(shù)據(jù)庫連接池對象
*/
private final DataSource dataSource;

/**
* 認(rèn)證業(yè)務(wù)對象
*/
private final ISysUserService userService;

/**
* 授權(quán)碼模式專用對象
*/
private final AuthenticationManager authenticationManager;

/**
* 客戶端信息來源
* @return
*/
@Bean
public JdbcClientDetailsService jdbcClientDetailsService(){
return new JdbcClientDetailsService(dataSource);
}

/**
* token保存策略
* @return
*/
@Bean
public TokenStore tokenStore(){
return new JdbcTokenStore(dataSource);
}

/**
* 授權(quán)信息保存策略
* @return
*/
@Bean
public ApprovalStore approvalStore(){
return new JdbcApprovalStore(dataSource);
}

/**
* 授權(quán)碼模式數(shù)據(jù)來源
* @return
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices(){
return new JdbcAuthorizationCodeServices(dataSource);
}

③重寫方法進(jìn)行配置

/**
* 用來配置客戶端詳情服務(wù)(ClientDetailsService)
* 客戶端詳情信息在這里進(jìn)行初始化
* 指定客戶端信息的數(shù)據(jù)庫來源
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(jdbcClientDetailsService());
}

/**
* 檢測 token 的策略
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
//允許客戶端以form表單的方式將token傳達(dá)給我們
.allowFormAuthenticationForClients()
//檢驗(yàn)token必須需要認(rèn)證
.checkTokenAccess("isAuthenticated()");
}


/**
* OAuth2.0的主配置信息
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
//刷新token時(shí)會(huì)驗(yàn)證當(dāng)前用戶是否已經(jīng)通過認(rèn)證
.userDetailsService(userService)
.approvalStore(approvalStore())
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore());
}

其它關(guān)于用戶表和權(quán)限表的代碼可參考源碼,回復(fù)“oauth2”獲取源碼。

模式

授權(quán)碼模式

我們前邊所講的內(nèi)容都是基于授權(quán)碼模式,授權(quán)碼模式被稱為最安全的一種模式,它獲取令牌的操作是在兩個(gè)服務(wù)端進(jìn)行的,極大的減小了令牌泄漏的風(fēng)險(xiǎn)。

啟動(dòng)兩個(gè)服務(wù),當(dāng)我們再次請求127.0.0.1:9002/product/findAll接口時(shí)會(huì)提示以下錯(cuò)誤

{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}

①調(diào)用接口獲取授權(quán)碼

發(fā)送127.0.0.1:9001/oauth/authorize?response_type=code&client_id=cheetah_one?請求,前邊的路徑是固定形式的,response_type=code?表示獲取授權(quán)碼,client_id=cheetah_one表示客戶端的名稱是我們數(shù)據(jù)庫配置的數(shù)據(jù)。

圖片

該頁面是oauth2?的默認(rèn)頁面,輸入用戶的賬戶密碼點(diǎn)擊登錄會(huì)提示我們進(jìn)行授權(quán),這是數(shù)據(jù)庫oauth_client_details?表我們設(shè)置autoapprove?為false起到的效果。

圖片

選擇Approve?點(diǎn)擊Authorize?按鈕,會(huì)發(fā)現(xiàn)我們設(shè)置的回調(diào)地址(oauth_client_details?表中的web_server_redirect_uri?)后邊拼接了code值,該值就是授權(quán)碼。

圖片

查看數(shù)據(jù)庫發(fā)現(xiàn)oauth_approvals和oauth_code表已經(jīng)存入數(shù)據(jù)了。

拿著授權(quán)碼去獲取token

圖片

獲取到token?之后oauth_access_token和oauth_refresh_token?表中會(huì)存入數(shù)據(jù)以用于后邊的認(rèn)證。而oauth_code?表中的數(shù)據(jù)被清除了,這是因?yàn)閏ode?值是直接暴漏在網(wǎng)頁鏈接上的,oauth2?為了防止他人拿到code非法請求而特意設(shè)置為僅用一次。

拿著獲取到的token去請求資源服務(wù)的接口,此時(shí)有兩種請求方式

圖片

?接下來我們再來看一下oauth2的其它模式。

簡化模式

所謂簡化模式是針對授權(quán)碼模式進(jìn)行的簡化,它將授權(quán)碼模式中獲取授權(quán)碼的步驟省略了,直接去請求獲取token。?

圖片

流程:發(fā)送請求127.0.0.1:9001/oauth/authorize?response_type=token&client_id=cheetah_one?跳轉(zhuǎn)到登錄頁進(jìn)行登錄,response_type=token?表示獲取token。

輸入賬號密碼登錄之后會(huì)直接在瀏覽器返回token?,我們就可以像授權(quán)碼方式一樣攜帶token去請求資源了。

圖片

該模式的弊端就是token直接暴漏在瀏覽器中,非常不安全,不建議使用。

密碼模式

密碼模式下,用戶需要將賬戶和密碼提供給客戶端向認(rèn)證服務(wù)器申請令牌,所以該種模式需要用戶高度信任客戶端。

圖片

流程:請求如下

圖片

獲取成功之后可以去訪問資源了。

客戶端模式

客戶端模式已經(jīng)不太屬于??oauth2??的范疇了,用戶直接在客戶端進(jìn)行注冊,然后客戶端去認(rèn)證服務(wù)器獲取令牌時(shí)不需要攜帶用戶信息,完全脫離了用戶,也就不存在授權(quán)問題了。

圖片

發(fā)送請求如下

圖片

獲取成功之后可以去訪問資源了。

刷新token

圖片

權(quán)限校驗(yàn)

除了我們在數(shù)據(jù)庫中為客戶端配置資源服務(wù)外,我們還可以動(dòng)態(tài)的給用戶分配接口的權(quán)限。

①開啟Security內(nèi)置的動(dòng)態(tài)配置

在開啟資源服務(wù)時(shí)給ResourceServerConfig?類增加注解@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)

②給接口增加權(quán)限

@GetMapping("/findAll")
@Secured("ROLE_PRODUCT")
public String findAll(){
return "產(chǎn)品列表查詢成功";
}

③在用戶登錄時(shí)設(shè)置用戶權(quán)限

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = this.baseMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
sysUser.setRoleList(AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_PRODUCT"));
return sysUser;
}

然后測試會(huì)發(fā)現(xiàn)可以正常訪問。

采坑

包名問題

當(dāng)我在創(chuàng)建項(xiàng)目的時(shí)候,給product?和server兩個(gè)模塊設(shè)置了不同的包名,導(dǎo)致發(fā)送請求獲取資源時(shí)報(bào)錯(cuò)。

經(jīng)過分析得知,在登錄賬號時(shí)會(huì)將用戶的信息存儲(chǔ)到oauth_access_token?表的authentication?中,在進(jìn)行token?校驗(yàn)時(shí)會(huì)根據(jù)token_id?取出該字段進(jìn)行反序列化,如果此時(shí)發(fā)現(xiàn)包名不一致便會(huì)導(dǎo)致解析token失敗,因此請求資源失敗。

解決思路

兩個(gè)項(xiàng)目的包名改為一致;

可以將用戶和權(quán)限的實(shí)體抽成單獨(dú)的模塊,供其它模塊引用;

loadUserByUsername?方法中使用的用戶實(shí)體類不需要繼承UserDetailsService?類,每次返回時(shí)用user類包裝一下即可;

數(shù)據(jù)庫問題

當(dāng)我在進(jìn)行權(quán)限校驗(yàn)測試時(shí),在設(shè)置權(quán)限時(shí)發(fā)現(xiàn)少打了一個(gè)單詞,導(dǎo)致請求一直出錯(cuò)。修改完成之后繼續(xù)請求,仍提示權(quán)限不足。

于是我將數(shù)據(jù)庫中oauth_refresh_token?和oauth_access_token的數(shù)據(jù)清除,重新開始測試就可以了。

個(gè)人認(rèn)為是生成token?時(shí)發(fā)現(xiàn)數(shù)據(jù)庫中token?存在,故不刷新token?,但進(jìn)行校驗(yàn)時(shí)卻用帶有權(quán)限標(biāo)識(shí)的token前去校驗(yàn)導(dǎo)致失敗。

至于其它的小坑在這不再贅述,如果遇到問題,建議按照流程對比我的源碼仔細(xì)檢查,回復(fù)“oauth2”獲取源碼。

小結(jié)

本文從原理、應(yīng)用場景、認(rèn)證流程出發(fā),對oauth2進(jìn)行了基本的講解,并且手把手帶大家完成了項(xiàng)目的搭建。大家在對授權(quán)碼模式、簡化模式、密碼模式、客戶端模式進(jìn)行測試的同時(shí)要將重點(diǎn)放到授權(quán)碼模式上。


責(zé)任編輯:武曉燕 來源: 阿Q說代碼
相關(guān)推薦

2020-11-16 08:37:16

MariaDB性能優(yōu)化

2018-06-26 14:42:10

StringJava數(shù)據(jù)

2023-06-26 11:59:52

標(biāo)簽質(zhì)量梳理

2021-09-01 09:32:40

工具

2011-06-22 09:37:03

桌面虛擬化存儲(chǔ)

2020-12-02 09:36:09

處理器手機(jī)卡頓

2020-05-16 13:25:03

分析網(wǎng)購數(shù)據(jù)

2020-07-29 09:21:34

Docker集群部署隔離環(huán)境

2024-12-13 08:06:38

Java分類理

2020-12-31 07:57:25

JVM操作代碼

2021-09-21 16:18:07

手機(jī)電池快充

2020-03-02 15:17:37

云原生CNCF容器

2019-07-04 09:13:04

中臺(tái)百度團(tuán)隊(duì)

2021-02-25 08:21:38

高可用風(fēng)險(xiǎn)故障

2020-12-16 11:09:27

JavaScript語言開發(fā)

2017-08-15 08:27:48

云備份問題恢復(fù)

2025-01-13 08:04:24

2021-11-19 06:50:17

OAuth協(xié)議授權(quán)

2015-10-12 10:01:26

AndroidWindows應(yīng)用Windows 10

2020-10-29 10:35:53

Nginx架構(gòu)服務(wù)器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號