一.前言
1.principal和credential的區(qū)別
-
principal:能唯一標(biāo)識用戶身份的屬性,一個用戶可以有多個principal
- 如登錄的唯一標(biāo)識,
用戶可以使用用戶名或手機(jī)或郵箱進(jìn)行登錄
,這些principal是讓別人知道的
- 如登錄的唯一標(biāo)識,
-
credential:憑證,用戶才知道的,簡單的說就是
密碼
-
如:手機(jī)開鎖,可以使用
屏幕密碼
也可以使用人臉識別
,屏幕密碼和人臉是你個人(用戶
)才擁有的; -
principals 和 credentials 組合就是用戶名 / 密碼了。
-
2.生成私鑰公鑰
- 使用命令生成密鑰證書,采用
RSA
算法,每個證書包含公鑰和私鑰- Keytool 是一個java提供的證書管理工具
keytool -genkeypair -alias oyjp -keyalg RSA -keypass ouyangjianpeng -keystore ouyangjianpeng.jks -storepass ouyangjianpeng -alias:密鑰的別名 -keyalg:使用的hash算法 -keypass:密鑰的訪問密碼 -keystore:密鑰庫文件名,changgou.jks保存了生成的證書 -storepass:密鑰庫的訪問密碼
- 成功后會在當(dāng)前目錄生成一個
ouyangjianpeng.jks
文件
- Keytool 是一個java提供的證書管理工具
- 查詢證書信息:
keytool -list -keystore ouyangjianpeng.jks
3.導(dǎo)出公鑰
-
openssl是一個加解密工具包,這里使用openssl來導(dǎo)出公鑰信息。
-
安裝 openssl:http://slproweb.com/products/Win32OpenSSL.html
- 安裝資料目錄下的
Win64OpenSSL-1_1_1b.exe
- 安裝資料目錄下的
-
cmd進(jìn)入
ouyangjianpeng.jks
文件所在目錄執(zhí)行如下命令:keytool -list -rfc --keystore ouyangjianpeng.jks | openssl x509 -inform pem -pubkey
-
3.用戶認(rèn)證分析
4.認(rèn)證解決方案
4.1.單點(diǎn)登錄
單點(diǎn)登錄(Single Sign On),簡稱為 SSO,是目前比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一。 SSO的定義是在多個應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng)
-
分布式系統(tǒng)要實(shí)現(xiàn)單點(diǎn)登錄,通常將認(rèn)證系統(tǒng)
獨(dú)立抽取
出來,并且將用戶信息存儲在單獨(dú)數(shù)據(jù)庫中,比如MySQL、Redis
,考慮性能要求,通常存儲在Redis中,如下圖:
Java中有很多用戶認(rèn)證的框架都可以實(shí)現(xiàn)單點(diǎn)登錄:Apache Shiro. CAS Spring security
4.2.第三方賬號登錄
所謂的第三方登錄,是說基于用戶在第三方平臺上已有的賬號和密碼來實(shí)現(xiàn)應(yīng)用的登錄或者注冊的功能。而第三方平臺一般是已擁有大量用戶的平臺,國外的比如Facebook,Twitter
等,國內(nèi)的比如微博、微信、QQ
等。
? 第三方認(rèn)證技術(shù)方案最主要是 解決認(rèn)證協(xié)議
的通用標(biāo)準(zhǔn)問題,因?yàn)橐獙?shí)現(xiàn)跨系統(tǒng)認(rèn)證
,各系統(tǒng)之間要遵循一定的
接口協(xié)議。
-
?
Oauth協(xié)議
為用戶資源的授權(quán)提供了一個安全的、開放而又簡易的標(biāo)準(zhǔn)。同時(shí),任何第三方都可以使用Oauth認(rèn)
證服務(wù),任何服務(wù)提供商都可以實(shí)現(xiàn)自身的OAUTH認(rèn)證服務(wù),因而Oauth是開放的。 -
Oauth協(xié)議目前發(fā)展到
2.0版本
,1.0版本過于復(fù)雜,2.0版本已得到廣泛應(yīng)用。
使用QQ認(rèn)證的過程:
- 用戶訪問網(wǎng)站A,網(wǎng)站A請求QQ授權(quán)服務(wù)器發(fā)送授權(quán)請求給用戶
- 用戶確認(rèn)授權(quán)后,返回授權(quán)碼給第三方
- 第三方拿到授權(quán)碼后,攜帶授權(quán)碼向QQ授權(quán)服務(wù)器申請令牌
- 第三方拿到令牌后,攜帶令牌向QQ請求用戶的基本信息
- QQ資源服務(wù)器根據(jù)訪問令牌,返回給第三方用戶的基本信息
4.3.單token系統(tǒng)和雙token系統(tǒng)業(yè)務(wù)邏輯
二.Spring Security
Spring Security是一個用于快速實(shí)現(xiàn)認(rèn)證/授權(quán)的安全框架。
-
認(rèn)證(Authentication) :即校驗(yàn)用戶的身份信息是否合法的過程,合法方可繼續(xù)訪問,不合法則拒絕訪問。常見的認(rèn)證方式有:
用戶名密碼登錄,二維碼登錄,手機(jī)短信登錄,指紋認(rèn)證
等方式。 -
授權(quán)(Authorization): 即是
Authentication認(rèn)證通過后
根據(jù)用戶的權(quán)限來控制用戶是否能訪問指定資源,擁有該資源的訪問權(quán)限則正常訪問,沒有權(quán)限則拒絕訪問。
1.快速入門
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
編寫controller
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/hello")
public String add(){
return "hello security";
}
}
訪問頁面時(shí)出現(xiàn)了默認(rèn)的安全認(rèn)證窗口,密碼在控制臺處進(jìn)行顯示。默認(rèn)的用戶名為user
2.基本原理
SpringSecurity本質(zhì)上是一個過濾器鏈。常用過濾器如下
FilterSecurityInterceptor
- 位于
最底端
的,是一個方法級
的權(quán)限過濾器
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
}
ExceptionTranslationFilter
- 異常處理過濾器,用來處理在
認(rèn)證授權(quán)
的過程中拋出的異常信息
。
public class ExceptionTranslationFilter extends GenericFilterBean implements MessageSourceAware {
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (IOException var7) {
throw var7;
} catch (Exception var8) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var8);
RuntimeException securityException = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
securityException = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (securityException == null) {
this.rethrow(var8);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var8);
}
this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException);
}
}
UsernamePasswordAuthenticationFilter
- 對
/login
的POST請求做攔截,校驗(yàn)表單中用戶名密碼
核心的業(yè)務(wù)邏輯如下
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username.trim() : "";
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
3.基于數(shù)據(jù)庫中實(shí)現(xiàn)用戶認(rèn)證/授權(quán)
WebSecurityConfigurerAdapter類
- 引入springSecurity的依賴并做相關(guān)的配置,對資源進(jìn)行保護(hù)。通常需要重寫以下方法:
//可以配置忽略某些請求。
public void configure(WebSecurity web) throws Exception {}
//配置安全攔截機(jī)制控制資源的訪問,可配置匹配哪些請求、哪些可以直接訪問、哪些需要授權(quán)后訪問。
protected void configure(HttpSecurity http) throws Exception {}
//配置驗(yàn)證的用戶信息源和密碼加密策略,并向容器注入AuthenticationManager對象,這需要在OAuth2中配置(授權(quán)服務(wù)器),配置了AuthenticationManager密碼驗(yàn)證才會生效。
public void configure(AuthenticationManagerBuilder auth) throws Exception {}
//配置驗(yàn)證管理的Bean
public AuthenticationManager authenticationManagerBean() throws Exception {}
新增依賴
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
自定義UserDetail類(偽代碼)
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.blb.java11springsecurity.entity.User;
import com.blb.java11springsecurity.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 實(shí)現(xiàn)自定義用戶登錄邏輯
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private IUserService userService;//用戶表實(shí)現(xiàn)類
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//按用戶名查詢用戶信息
User user = userService.getOne(new QueryWrapper<User>().lambda().eq(User::getUsername, s));
if(user == null){
throw new UsernameNotFoundException("用戶名不存在");
}
//查詢所有用戶權(quán)限 List<String> --> xx,xxx,xxx,xx --> List<Authory>
List<String> authList = userService.getAuthoritiesByUsername(s);
String auths = String.join(",", authList);
//把用戶信息包裝到UserDetails的實(shí)現(xiàn)類User中
return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),
AuthorityUtils.commaSeparatedStringToAuthorityList(auths));
}
}
SpringSecurity的核心配置(偽代碼)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//啟動Security的驗(yàn)證
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private UserDetailsService userDetailsService;//引入自定義用戶登錄邏輯
//配置驗(yàn)證用戶的賬號和密碼
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用內(nèi)存中用戶信息進(jìn)行認(rèn)證
/*auth.inMemoryAuthentication()
.withUser("zhangsan")
.password(bCryptPasswordEncoder.encode("123"))
.roles("ADMIN","USER") //角色
.and()
.withUser("lisi")
.password(bCryptPasswordEncoder.encode("123"))
.roles("USER"); //權(quán)限
*/
//使用自定義UserDetail類定義邏輯進(jìn)行認(rèn)證
auth.userDetailsService(userDetailsService);
}
//配置訪問控制
@Override
protected void configure(HttpSecurity http) throws Exception {
//給請求授權(quán)
http.authorizeRequests()
//給登錄相關(guān)的請求放行(permitAll即未登陸,定義的路徑依舊不會攔截)
.antMatchers("/login", "/login.html", "/**/*.css").permitAll()
//訪問控制(即使登陸了,訪問路徑需要相應(yīng)的權(quán)限)
.antMatchers("/admin/**").hasAuthority("銷售管理")
.antMatchers("/user/**").hasAuthority("采購管理")
//其他路徑進(jìn)行權(quán)限驗(yàn)證,說白了就是攔截
.anyRequest().authenticated()
.and()
//配置自定義登錄
.formLogin()
.loginPage("/login.html") //登錄頁面
.loginProcessingUrl("/login") //處理登錄的url
.successForwardUrl("/hello.html") //登錄成功后跳轉(zhuǎn)的url
;
}
//提供密碼編碼器,采用加密,加鹽策略。每次加密后結(jié)果都不一樣。賊安全
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
三.Spring Security集成 OAuth2
- 將
OAuth2和Spring Security集成
,就可以得到一套完整的安全解決方案。然后通過Spring Security OAuth2構(gòu)建一個授權(quán)服務(wù)器來驗(yàn)證用戶身份以提供access_token,并使用這個access_token來從資源服務(wù)器請求數(shù)據(jù)。
1.搭建授權(quán)服務(wù)器
1.0.授權(quán)服務(wù)器簡介
搭建授權(quán)服務(wù)器最重要的是:繼承AuthorizationServerConfigurerAdapter接口
并在實(shí)現(xiàn)類上加注解@EnableAuthorizationServer
標(biāo)識這是一個授權(quán)服務(wù)器。
- AuthorizationServerConfigurerAdapter接口有三個方法
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer { //配置客戶端信息,即怎么讀取用戶信息 是基于內(nèi)存還是jdbc @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { } //用來配置令牌(token)的訪問端點(diǎn)和令牌服務(wù)(token services)。 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { } //用來配置令牌端點(diǎn)(Token Endpoint)的安全約束。 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception{ } }
ClientDetailsServiceConfigurer:配置客戶端信息
- 可以使用
內(nèi)存或JDBC
來實(shí)現(xiàn)客戶端詳情服務(wù), ClientDetails有如下幾個重要屬性:
clients.inMemory()
//客戶端id
.withClient("client_1")
//客戶端secret
.secret(passwordEncoder().encode("123456"))
//回調(diào)地址
.redirectUris("https://www.baidu.com/")
/* OAuth2為我們提供了四種授權(quán)方式:
* 1、授權(quán)碼模式(authorization code)用在客戶端與服務(wù)端應(yīng)用之間授權(quán)
* 2、簡化模式(implicit)用在移動app或者web app(這些app是在用戶的設(shè)備上的,如在手機(jī)上調(diào)起微信來進(jìn)行認(rèn)證授權(quán))
* 3、密碼模式(resource owner password credentials)應(yīng)用直接都是受信任的(都是由一家公司開發(fā)的)
* 4、客戶端模式(client credentials)用在應(yīng)用API訪問
*/
.authorizedGrantTypes("password", "client_credentials", "refresh_token", "authorization_code")
//授權(quán)范圍,默認(rèn)為空,表示客戶端擁有全部的訪問范圍。
.scopes("all")
//accessToken有效期
.accessTokenValiditySeconds(accessTokenValiditySeconds)
//refreshToken有效期
.refreshTokenValiditySeconds(refreshTokenValiditySeconds);
AuthorizationServerEndpointsConfigurer:配置令牌訪問端點(diǎn)
-
使用
@EnableAuthorizationServer
注解后,應(yīng)用啟動后將自動生成幾個Endpoint:/oauth/authorize:驗(yàn)證 /oauth/token:獲取token /oauth/confirm_access:用戶授權(quán) /oauth/error:認(rèn)證失敗 /oauth/check_token:資源服務(wù)器用來校驗(yàn)token /oauth/token_key:如果使用JWT令牌,則公開用于令牌驗(yàn)證的公鑰
AuthorizationServerSecurityConfigurer:用來配置令牌端點(diǎn)(Token Endpoint)的安全約束
上面的/oauth/check_token和/oauth/token_key端點(diǎn),他們都是用于檢查令牌的,默認(rèn)受保護(hù)denyAll()。
使用tokenKeyAccess()和checkTokenAccess()方法會打開這些端點(diǎn)以供使用,如:
// 允許check_token訪問
oauthServer.checkTokenAccess("permitAll()");
// 允許表單認(rèn)證
oauthServer.allowFormAuthenticationForClients();
Token存儲方式
OAuth2存儲token值的方式由多種,所有的實(shí)現(xiàn)方式都是實(shí)現(xiàn)了TokenStore接口
InMemoryTokenStore: 存儲在本機(jī)內(nèi)存
JdbcTokenStore: 存儲在數(shù)據(jù)庫
JwtTokenStore: 不會存儲到任何介質(zhì)中
RedisTokenStore: 存儲在Redis
用戶(客戶端)信息存儲方式
內(nèi)存中
數(shù)據(jù)庫中
客戶端信息存儲到 oauth_client_details 表中,建表語句可以從如下網(wǎng)站找到:
spring-security-oauth/schema.sql at main · spring-attic/spring-security-oauth · GitHub
oauth_client_details :客戶端賬號密碼、授權(quán)、回調(diào)地址等重要信息;核心表。
oauth_access_token :存儲access_token。
oauth_refresh_token :存儲refresh_token。
oauth_client_token :存儲從服務(wù)端獲取的token數(shù)據(jù)。
oauth_code :存儲授權(quán)碼。
oauth_approvals :存儲授權(quán)成功的客戶端信息。
1.1.引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
或者 引入spring cloud oauth2依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- spring cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
完整如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.oyjp</groupId>
<artifactId>spring-boot-oauth2-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<!-- spring cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-->spring-boot 整合security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- springboot整合freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2.application.yaml
server:
port: 8080
spring:
application:
name: oauth2-center-server
logging:
level:
root: info
org.springframework.security: DEBUG
1.3.Oauth2授權(quán)服務(wù)配置
具體實(shí)現(xiàn)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.provisioning.InMemoryUserDetailsManager;
//配置授權(quán)中心信息
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* accessToken有效期 兩小時(shí)
*/
private int accessTokenValiditySeconds = 7200;
/**
* refreshToken有效期 兩小時(shí)
*/
private int refreshTokenValiditySeconds = 7200;
/**
* 添加商戶信息
* @param clients 客戶端即可
* @throws Exception 異常
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client_id
.withClient("client_1")
//配置client-secret
.secret(passwordEncoder().encode("123456"))
//配置redirect_uri,用于授權(quán)成功后跳轉(zhuǎn)
.redirectUris("https://www.baidu.com/")
//配置grant_type,表示授權(quán)類型
/* OAuth2為我們提供了四種授權(quán)方式:
* 1、授權(quán)碼模式(authorization code)用在客戶端與服務(wù)端應(yīng)用之間授權(quán)
* 2、簡化模式(implicit)用在移動app或者web app(這些app是在用戶的設(shè)備上的,如在手機(jī)上調(diào)起微信來進(jìn)行認(rèn)證授權(quán))
* 3、密碼模式(resource owner password credentials)應(yīng)用直接都是受信任的(都是由一家公司開發(fā)的)
* 4、客戶端模式(client credentials)用在應(yīng)用API訪問
*/
.authorizedGrantTypes("password", "client_credentials", "refresh_token", "authorization_code", "implicit")
//授權(quán)范圍
.scopes("all")
//配置訪問token的有效期
.accessTokenValiditySeconds(accessTokenValiditySeconds)
//配置刷新token的有效期
.refreshTokenValiditySeconds(refreshTokenValiditySeconds);
}
/**
* 設(shè)置token類型
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager())
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST請求
endpoints.userDetailsService(userDetailsService());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
// 允許表單認(rèn)證
oauthServer.allowFormAuthenticationForClients();
// 允許check_token訪問
oauthServer.checkTokenAccess("permitAll()");
}
@Bean
AuthenticationManager authenticationManager() {
AuthenticationManager authenticationManager = new AuthenticationManager() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return daoAuhthenticationProvider().authenticate(authentication);
}
};
return authenticationManager;
}
@Bean
public AuthenticationProvider daoAuhthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
/**
* 設(shè)置添加用戶信息,正常應(yīng)該從數(shù)據(jù)庫中讀取
* @return UserDetailsService
*/
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456"))
.authorities("ROLE_USER").build());
userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567"))
.authorities("ROLE_USER").build());
return userDetailsService;
}
/**
* 采用BCryptPasswordEncoder對密碼進(jìn)行編碼
* @return PasswordEncoder
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
1.4.SpringSecurity配置
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 添加Security權(quán)限配置
*/
@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/***
* 忽略安全攔截的URL
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/oauth/**","/login.html","/css/**","/data/**","/fonts/**","/img/**","/js/**");
}
/**
* 授權(quán)中心管理器
* @return AuthenticationManager
* @throws Exception 異常
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//@Bean
//public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
//}
/**
* 攔截所有請求,使用httpBasic方式登陸
* @param http 請求
* @throws Exception 異常
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();
}
}
1.5.啟動類
@SpringBootApplication
public class Oauth2AuthorizationCenterApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2AuthorizationCenterApplication.class, args);
}
}
1.測試授權(quán)碼模式
-
請求 http://localhost:8080/oauth/authorize?response_type=code&client_id=client_1&redirect_uri=https://www.baidu.com/
- 授權(quán)服務(wù)器將攜帶授權(quán)碼code然后轉(zhuǎn)發(fā)至redirect_uri=https://www.baidu.com/然
- 授權(quán)服務(wù)器將攜帶授權(quán)碼code然后轉(zhuǎn)發(fā)至redirect_uri=https://www.baidu.com/然
-
使用授權(quán)碼獲取accessToken
POST請求 http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=client_1&client_secret=123456&code=EwaTib&redirect_uri=https://www.baidu.com/&scope=all
2.測試密碼模式
POST請求 http://localhost:8080/oauth/token?grant_type=password&client_id=client_1&client_secret=123456&code=YerSeW&scope=all&username=user_1&password=123456
2.搭建資源服務(wù)器
1.0.前言
搭建資源服務(wù)器最重要的是:繼承ResourceServerConfigurerAdapter接口
并在實(shí)現(xiàn)類上加注解@EnableResourceServer
標(biāo)識這是一個資源服務(wù)器。
-
該接口只有兩個方法
public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer { //配置哪些可以訪問,哪些不可訪問,默認(rèn)情況下所有不在/oauth/**下的資源都是受保護(hù)的資源 @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated(); } //它可以為資源服務(wù)器添加一個特定的屬性,如resourceId,也就是說我們一個授權(quán)服務(wù)器可能對應(yīng)多個資源服務(wù)器, //可以為每個資源服務(wù)器添加一個resourceId屬性進(jìn)行區(qū)分,可選,但建議使用,如果存在,auth服務(wù)器將進(jìn)行驗(yàn)證。 @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { } }
1.1.引入依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.oyjp</groupId>
<artifactId>spring-boot-oauth2-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<!-- spring cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-->spring-boot 整合security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- springboot整合freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2.application.yaml
server:
port: 8081
spring:
application:
name: oauth2-resource-server
security:
oauth2:
resource:
####從認(rèn)證授權(quán)中心上驗(yàn)證token
tokenInfoUri: http://localhost:8080/oauth/check_token
preferTokenInfo: true
client:
accessTokenUri: http://localhost:8080/oauth/token
userAuthorizationUri: http://localhost:8080/oauth/authorize
###appid
clientId: client_1
###appSecret
clientSecret: 123456
logging:
level:
root: info
org.springframework.security: DEBUG
1.3.oauth2資源服務(wù)配置
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
/**
* @Description: 資源攔截配置
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// 對 api/order 請求進(jìn)行攔截
http.authorizeRequests().antMatchers("/api/test/**").authenticated();
}
}
1.4.請求測試類
@RestController
@RequestMapping("/api/test")
public class TestController {
@RequestMapping("/add")
public String addOrder() {
return "add success!";
}
}
1.5.啟動類開啟Oauth2
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
@SpringBootApplication
@EnableOAuth2Sso
public class Oauth2ResourceServerApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ResourceServerApplication.class, args);
}
}
1.5.1.授權(quán)服務(wù)器和資源服務(wù)聯(lián)合測試
- 沒授權(quán)時(shí)請求http://127.0.0.1:8081/api/test/add
- 授權(quán)時(shí)請求http://127.0.0.1:8081/api/test/add
-
先獲取token(密碼模式)
-
http://localhost:8080/oauth/token?grant_type=password&client_id=client_1&client_secret=123456&code=YerSeW&scope=all&username=user_1&password=123456
-
http://localhost:8080/oauth/token?grant_type=password&client_id=client_1&client_secret=123456&code=YerSeW&scope=all&username=user_1&password=123456
-
使用token訪問資源服務(wù)器
3.授權(quán)服務(wù)器改成動態(tài)數(shù)據(jù)庫查詢的方式
1.1.下載oauth2官方建表語句
spring-security-oauth/schema.sql at main · spring-attic/spring-security-oauth · GitHub
我用的是Mysql,對于上面鏈接里的建表語句,有某些字段為`LONGVARBINARY`類型,它對應(yīng)mysql的`blob`類型,這需要改下,
另外主鍵是varchar(255),如果又用的utf8mb4編碼,主鍵長度會超限制,所以這個主鍵長度最好也要改一下。
oauth_client_details :客戶端賬號密碼、授權(quán)、回調(diào)地址等重要信息;核心表。
oauth_access_token :存儲access_token。
oauth_refresh_token :存儲refresh_token。
oauth_client_token :存儲從服務(wù)端獲取的token數(shù)據(jù)。
oauth_code :存儲授權(quán)碼。
oauth_approvals :存儲授權(quán)成功的客戶端信息
1.2.新增依賴
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
1.3./修改授權(quán)服務(wù)器
1.修改配置文件
server:
port: 8080
spring:
application:
name: oauth2-center-server
datasource:
hikari:
connection-test-query: SELECT 1
minimum-idle: 1
maximum-pool-size: 5
pool-name: dbcp1
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/oauth2_test?autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
logging:
level:
root: info
org.springframework.security: DEBUG
2.修改AuthorizationServerConfig
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.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import javax.sql.DataSource;
/**
* @Description: 配置授權(quán)中心信息
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//數(shù)據(jù)源,用于從數(shù)據(jù)庫獲取數(shù)據(jù)進(jìn)行認(rèn)證操作,測試可以從內(nèi)存中獲取
@Autowired
@Qualifier("dataSource")
private DataSource dataSource;
/**
* accessToken有效期 兩小時(shí)
*/
private int accessTokenValiditySeconds = 7200;
/**
* refreshToken有效期 兩小時(shí)
*/
private int refreshTokenValiditySeconds = 7200;
@Bean
public TokenStore tokenStore() {
// return new InMemoryTokenStore(); //使用內(nèi)存中的 token store
return new JdbcTokenStore(dataSource); /// 使用Jdbctoken store
}
/**
* 添加商戶信息
*
* @param clients 商戶
* @throws Exception 異常
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource)
//測試首次運(yùn)行可以指定測試數(shù)據(jù),如果數(shù)據(jù)庫中沒有則不報(bào)錯,如果有或者第二次運(yùn)行會報(bào)錯,因?yàn)閿?shù)據(jù)庫已經(jīng)存在了,需要注釋掉
.withClient("client_1")
//商戶secret
.secret(passwordEncoder().encode("123456"))
//回調(diào)地址
.redirectUris("https://www.baidu.com/")
/* OAuth2為我們提供了四種授權(quán)方式:
* 1、授權(quán)碼模式(authorization code)用在客戶端與服務(wù)端應(yīng)用之間授權(quán)
* 2、簡化模式(implicit)用在移動app或者web app(這些app是在用戶的設(shè)備上的,如在手機(jī)上調(diào)起微信來進(jìn)行認(rèn)證授權(quán))
* 3、密碼模式(resource owner password credentials)應(yīng)用直接都是受信任的(都是由一家公司開發(fā)的)
* 4、客戶端模式(client credentials)用在應(yīng)用API訪問
*/
.authorizedGrantTypes("password", "client_credentials", "refresh_token", "authorization_code", "implicit")
//授權(quán)范圍
.scopes("all")
//accessToken有效期
.accessTokenValiditySeconds(accessTokenValiditySeconds)
//refreshToken有效期
.refreshTokenValiditySeconds(refreshTokenValiditySeconds);
}
/**
* 設(shè)置token類型
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager())
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
endpoints.userDetailsService(userDetailsService());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
// 允許表單認(rèn)證
oauthServer.allowFormAuthenticationForClients();
// 允許check_token訪問
oauthServer.checkTokenAccess("permitAll()");
}
@Bean
AuthenticationManager authenticationManager() {
AuthenticationManager authenticationManager = new AuthenticationManager() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return daoAuhthenticationProvider().authenticate(authentication);
}
};
return authenticationManager;
}
@Bean
public AuthenticationProvider daoAuhthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
/**
* 設(shè)置添加用戶信息,正常應(yīng)該從數(shù)據(jù)庫中讀取
*
* @return UserDetailsService
*/
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456"))
.authorities("ROLE_USER").build());
userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567"))
.authorities("ROLE_USER").build());
return userDetailsService;
}
/**
* 設(shè)置加密方式
*
* @return PasswordEncoder
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4.測試
檢查數(shù)據(jù)庫發(fā)現(xiàn)測試商戶已經(jīng)導(dǎo)入到數(shù)據(jù)庫了
四.4種授權(quán)模式
OAuth2為我們提供了四種授權(quán)方式:
- 授權(quán)碼模式(
authorization_code
)用在客戶端與服務(wù)端應(yīng)用之間授權(quán) - 簡化模式(
implicit
)用在移動app或者web app
(這些app是在用戶的設(shè)備上的,如:在手機(jī)上調(diào)起微信來進(jìn)行認(rèn)證授權(quán)) - 密碼模式(
password
)應(yīng)用直接都是受信任的(都是由一家公司
開發(fā)的) - 客戶端模式(client_credentials)用在
應(yīng)用API
訪問
1.授權(quán)碼模式
-
客戶端請求第三方授權(quán),將重定向到授權(quán)服務(wù)器,重定向時(shí)會附加客戶端信息,然后客戶端要求用戶給予授權(quán),如:
-
http://localhost:8080/oauth/authorize?response_type=code&client_id=client_1&redirect_uri=https://www.baidu.com/
-
參數(shù)列表如下:
client_id:客戶端標(biāo)識。 response_code:授權(quán)碼模式固定為code。 scope:客戶端權(quán)限范圍。 redirect_uri:跳轉(zhuǎn)uri,當(dāng)授權(quán)碼申請成功后會跳轉(zhuǎn)到此地址,并在后邊帶上code參數(shù)。
-
-
-
瀏覽器出現(xiàn)向授權(quán)服務(wù)器授權(quán)頁面,用戶同意給客戶端授權(quán)。
-
授權(quán)服務(wù)器將授權(quán)碼發(fā)送給客戶端
通過redirect_uri攜帶code
-
客戶端拿著授權(quán)碼想授權(quán)服務(wù)器申請token
-
http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=client_1&client_secret=123456&code=iZ81fR&redirect_uri=https://www.baidu.com/&scope=all
-
參數(shù)列表如下:
client_id:客戶端標(biāo)識。 client_secret:客戶端密鑰。 grant_type:授權(quán)類型,填寫authorization_code。 code:授權(quán)碼,授權(quán)碼只使用一次就失效。 redirect_uri:申請授權(quán)碼時(shí)的跳轉(zhuǎn)url。
-
-
授權(quán)服務(wù)器返回令牌(
access_token
) -
客戶端請求資源服務(wù)器的資源,資源服務(wù)校驗(yàn)令牌合法性,完成授權(quán)
-
資源服務(wù)器返回受保護(hù)資源
總結(jié):
- 這種模式是四種模式中最安全的,一般用于client是Web服務(wù)器端應(yīng)用或第三方App調(diào)用資源服務(wù)的時(shí)候,因?yàn)樵谶@種模式中access_token不經(jīng)過瀏覽器或者移動端的App,而是直接從服務(wù)端去交換,這樣就最大限度的減少了令牌泄露的風(fēng)險(xiǎn)。
測試
-
請求認(rèn)證服務(wù)獲取授權(quán)碼
- http://localhost:8080/oauth/authorize?response_type=code&client_id=client_1&redirect_uri=https://www.baidu.com/
-
跳轉(zhuǎn)到登錄頁面: 輸入賬號:user_1 密碼:123456,登錄進(jìn)入授權(quán)頁面:
-
進(jìn)入授權(quán)頁面
-
點(diǎn)擊Authorize,確認(rèn)授權(quán)后, 認(rèn)證服務(wù)攜帶授權(quán)碼跳轉(zhuǎn)redirect_uri,code=pfiYeD就是返回的授權(quán)碼, 每一個授權(quán)碼只能使用一次
https://www.baidu.com/?code=pfiYeD -
使用該code申請令牌
http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=client_1&client_secret=123456&code=pfiYeD&redirect_uri=https://www.baidu.com/&scope=all
可能你會遇到返回沒有refresh_token的情況,檢查看下客戶端配置authorizedGrantTypes
是否支持refresh_token,我上面配置了才會返回,如下:
.authorizedGrantTypes("authorization_code", "client_credentials", "password", "implicit", "refresh_token")
2.密碼模式
步驟一
- 資源擁有者將用戶名、密碼發(fā)送給客戶端。
步驟二
-
客戶端拿著資源擁有者的用戶名、密碼向授權(quán)服務(wù)器請求令牌,如下:
-
http://localhost:8080/oauth/token?grant_type=password&client_id=client_1&client_secret=123456&code=YerSeW&scope=all&username=user_1&password=123456
- 參數(shù)列表如下:
client_id:客戶端標(biāo)識。 client_secret:客戶端密鑰。 grant_type:授權(quán)類型,填寫password。 username:資源擁有者用戶名。 password:資源擁有者密碼。
- 參數(shù)列表如下:
步驟三
- 授權(quán)服務(wù)器將令牌發(fā)送給client。
總結(jié)
- 這種模式十分簡單,但直接將用戶敏感信息泄露給了client,因此這就說明這種模式只能用于client是我們自己開發(fā)的情況下。
測試請求令牌,如下:
2.1.密碼模式與授權(quán)碼模式區(qū)別
-
授權(quán)碼模式申請授權(quán)碼的過程是
用戶直接與認(rèn)證服務(wù)器進(jìn)行交互
,然后授權(quán)結(jié)果由認(rèn)證服務(wù)器告知第三方客戶端
,也就是不會向第三方客戶端暴露服務(wù)提供商的用戶密碼信息
。 -
密碼模式,是用戶將
用戶密碼信息
交給第三方,然后由第三方向服務(wù)提供商進(jìn)行認(rèn)證和資源請求。- 絕大多數(shù)的服務(wù)提供商都會選擇使用授權(quán)碼模式,
避免自己的用戶密碼暴漏給第三方
。所以密碼模式只適用于服務(wù)提供商對第三方廠商高度信任
的情況下才能使用。
- 絕大多數(shù)的服務(wù)提供商都會選擇使用授權(quán)碼模式,
-
客戶端模式、簡化模式的應(yīng)用很少
3.簡化模式
步驟一
-
資源擁有者打開客戶端,客戶端要求資源擁有者給預(yù)授權(quán),它將瀏覽器重定向到授權(quán)服務(wù)器,重定向時(shí)會附加客戶端的身份信息,如:
-
http://localhost:8080/oauth/authorize?client_id=client_1&response_type=token&scope=all&redirect_uri=https://www.baidu.com/
- 參數(shù)同授權(quán)碼模式,將response_type改為token即可。
-
http://localhost:8080/oauth/authorize?client_id=client_1&response_type=token&scope=all&redirect_uri=https://www.baidu.com/
步驟二
- 瀏覽器出現(xiàn)向授權(quán)服務(wù)器授權(quán)頁面,之后用戶同意授權(quán)。
步驟三
- 授權(quán)服務(wù)器將授權(quán)碼和令牌到重定向uri之后。
測試
-
瀏覽器訪問認(rèn)證頁面:
- http://localhost:8080/oauth/authorize?client_id=client_1&response_type=token&scope=all&redirect_uri=https://www.baidu.com/
- 輸入賬號:user_1 密碼:123456,登錄進(jìn)入授權(quán)頁面:
- 確認(rèn)授權(quán)后,瀏覽器會重定向到指定的
redirect_uri
路徑,并將token
存放在uri路徑之后。
- https://www.baidu.com/#access_token=c2535eca-d9b5-4176-830b-aea1fa2ccf4c&token_type=bearer&expires_in=7199
4.客戶端模式
步驟一
- 客戶端向授權(quán)服務(wù)器發(fā)送自己的身份信息,并請求令牌。
步驟二
-
確認(rèn)客戶端身份無誤后,將令牌發(fā)送給client,請求如下:
-
http://localhost:8080/oauth/token?client_id=client_1&client_secret=123456&grant_type=client_credentials&scopes=all
-
參數(shù)列表如下:
client_id:客戶端標(biāo)識。 client_secret:客戶端密鑰。 grant_type:授權(quán)類型,填寫client_credentials。
-
總結(jié):
- 這種模式是最方便但最不安全的模式。因此這就要求我們對client完全信任,而client本身也是安全的,因此這種模式一般用來提供給我們完全信任的服務(wù)端使用。
測試請求令牌,如下:
五.Spring Security OAuth2集成JWT
1.生成JTW
- 修改tokenStor為JwtTokenStore
// //令牌持久化存儲接口
@Bean
public TokenStore tokenStore() {
// return new InMemoryTokenStore(); //使用內(nèi)存中的 token store
// return new JdbcTokenStore(dataSource); /// 使用Jdbctoken store
return new JwtTokenStore(accessTokenConverter());
}
//jwt令牌轉(zhuǎn)換器
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("abc123");
return converter;
}
- 設(shè)置AuthorizationServerEndpointsConfigurer的
tokenStore、tokenEnhancer
//端點(diǎn)設(shè)置自定義token存儲,token增強(qiáng)
endpoints.tokenStore(tokenStore()).tokenEnhancer(accessTokenConverter());
- 使用密碼模式訪問
-
http://localhost:8080/oauth/token?grant_type=password&client_id=client_1&client_secret=123456&code=YerSeW&scope=all&username=user_1&password=123456
- 解析access_token
2、自定義Payload
將自定義的TokenEnhancer加入到TokenEnhancerChain中,最后設(shè)置到端點(diǎn)中endpoints.tokenEnhancer(tokenEnhancerChain)
。
-
授權(quán)服務(wù)器新增TokenEnhancer實(shí)現(xiàn)類
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class CustomTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> additionalInfo = new HashMap<>(16); additionalInfo.put("userid", "123"); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }
-
將tokenEnhancer注入,追加到
TokenEnhancerChain
中,并設(shè)置到endponits
文章來源:http://www.zghlxwxcb.cn/news/detail-424017.html
/**
* 設(shè)置token類型
*
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager()).allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
//用戶信息
endpoints.userDetailsService(userDetailsService());
//自定義token存儲,token增強(qiáng)
//endpoints.tokenStore(tokenStore()).tokenEnhancer(accessTokenConverter());
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer, accessTokenConverter()));
endpoints.tokenEnhancer(tokenEnhancerChain);
endpoints.tokenEnhancer(tokenEnhancer);
}
- 使用密碼模式訪問
- http://localhost:8080/oauth/token?grant_type=password&client_id=client_1&client_secret=123456&code=YerSeW&scope=all&username=user_1&password=123456
文章來源地址http://www.zghlxwxcb.cn/news/detail-424017.html
到了這里,關(guān)于【SpringBoot】自從集成spring-security-oauth2后,實(shí)現(xiàn)統(tǒng)一認(rèn)證授權(quán)so easy!的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!