@
OAuth2.0系列博客:
- OAuth2.0系列之基本概念和運作流程(一)
- OAuth2.0系列之授權碼模式實踐教程(二)
- OAuth2.0系列之簡化模式實踐教程(三)
- OAuth2.0系列之密碼模式實踐教程(四)
- OAuth2.0系列之客戶端模式實踐教程(五)
- OAuth2.0系列之集成JWT實現單點登錄
1、密碼模式簡介
1.1 前言簡介
在上一篇文章中我們學習了OAuth2的一些基本概念,對OAuth2有了基本的認識,接着學習OAuth2.0授權模式中的密碼模式
ps:OAuth2.0的授權模式可以分為:
- 授權碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
密碼模式(resource owner password credentials):密碼模式中,用戶向客戶端提供自己的用戶名和密碼,這通常用在用戶對客戶端高度信任的情況
1.2 授權流程圖
官網圖片:
- (A)用戶訪問客戶端,提供URI連接包含用戶名和密碼信息給授權服務器
- (B)授權服務器對客戶端進行身份驗證
- (C)授權通過,返回acceptToken給客戶端
從調接口方面,簡單來說:
- 第一步:直接傳username,password獲取token
- 第二步:拿到acceptToken之后,就可以直接訪問資源
http://localhost:8084/api/userinfo?access_token=${accept_token}
2、例子實踐
2.1 實驗環境准備
- IntelliJ IDEA
- Maven3.+版本
新建SpringBoot Initializer項目,可以命名password
主要是想引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Oauth2-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- Spring Cloud Security-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
2.2 OAuth2.0角色
前面的學習,我們知道了OAuth2.0主要包括如下角色,下面通過代碼例子加深對理論的理解
- 資源所有者(Resource Owner)
- 用戶代理(User Agent)
- 客戶端(Client)
- 授權服務器(Authorization Server)
- 資源服務器(Resource Server)
生產環境、資源服務器和授權服務器一般是分開的,不過學習的可以放在一起
定義資源服務器,用注解@EnableResourceServer;
定義授權服務器,用注解@EnableAuthorizationServer;
2.3 OAuth2.0配置類
package com.example.oauth2.password.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
/**
* <pre>
* OAuth2.0配置類
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改后版本: 修改人: 修改日期: 2020/06/11 11:00 修改內容:
* </pre>
*/
@Configuration
//開啟授權服務
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
private static final String CLIENT_ID = "cms";
private static final String SECRET_CHAR_SEQUENCE = "{noop}secret";
private static final String SCOPE_READ = "read";
private static final String SCOPE_WRITE = "write";
private static final String TRUST = "trust";
private static final String USER ="user";
private static final String ALL = "all";
private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 2*60;
private static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 2*60;
// 密碼模式授權模式
private static final String GRANT_TYPE_PASSWORD = "password";
//授權碼模式
private static final String AUTHORIZATION_CODE = "authorization_code";
//refresh token模式
private static final String REFRESH_TOKEN = "refresh_token";
//簡化授權模式
private static final String IMPLICIT = "implicit";
//指定哪些資源是需要授權驗證的
private static final String RESOURCE_ID = "resource_id";
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
// 使用內存存儲
.inMemory()
//標記客戶端id
.withClient(CLIENT_ID)
//客戶端安全碼
.secret(SECRET_CHAR_SEQUENCE)
//為true 直接自動授權成功返回code
.autoApprove(true)
.redirectUris("http://127.0.0.1:8084/cms/login") //重定向uri
//允許授權范圍
.scopes(ALL)
//token 時間秒
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
//刷新token 時間 秒
.refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS)
//允許授權類型
.authorizedGrantTypes(GRANT_TYPE_PASSWORD );
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 使用內存保存生成的token
endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore());
}
/**
* 認證服務器的安全配置
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
//.realm(RESOURCE_ID)
// 開啟/oauth/token_key驗證端口認證權限訪問
.tokenKeyAccess("isAuthenticated()")
// 開啟/oauth/check_token驗證端口認證權限訪問
.checkTokenAccess("isAuthenticated()")
//允許表單認證
.allowFormAuthenticationForClients();
}
@Bean
public TokenStore memoryTokenStore() {
// 最基本的InMemoryTokenStore生成token
return new InMemoryTokenStore();
}
}
2.4 Security配置類
為了測試,可以進行簡單的SpringSecurity
package com.example.oauth2.password.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* <pre>
* SpringSecurity配置類
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改后版本: 修改人: 修改日期: 2020/06/11 11:23 修改內容:
* </pre>
*/
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { //auth.inMemoryAuthentication()
auth.inMemoryAuthentication()
.withUser("nicky")
.password("{noop}123")
.roles("admin");
}
@Override
public void configure(WebSecurity web) throws Exception {
//解決靜態資源被攔截的問題
web.ignoring().antMatchers("/asserts/**");
web.ignoring().antMatchers("/favicon.ico");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http // 配置登錄頁並允許訪問
//.formLogin().permitAll()
// 配置Basic登錄
//.and().httpBasic()
// 配置登出頁面
.logout().logoutUrl("/logout").logoutSuccessUrl("/")
// 配置允許訪問的鏈接
.and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**","/api/**").permitAll()
// 其余所有請求全部需要鑒權認證
.anyRequest().authenticated()
// 關閉跨域保護;
.and().csrf().disable();
}
}
2.5 功能簡單測試
接口測試,要用POST方式,在postman測試,response_type參數傳password:
http://localhost:8888/oauth/token?password=123&grant_type=password&username=nicky&scope=all
注意配置一下請求頭的授權參數,username即client_id,password即client_secret
代碼方式請求,可以進行如下封裝,即進行base64加密
HttpHeaders headers = new HttpHeaders();
byte[] key = (clientId+":"+clientSecret).getBytes();
String authKey = new String(Base64.encodeBase64(key));
LOG.info("Authorization:{}","Basic "+authKey);
headers.add("Authorization","Basic "+authKey);
拿到token直接去調業務接口:
http://localhost:8888/api/userinfo?access_token=61b113f3-f1e2-473e-a6d7-a0264bfdfa8d
例子代碼下載:code download