SpringBoot跨系統(tǒng)單點登陸的實現(xiàn)方法
什么是單點登陸
單點登錄(英語:Single sign-on,縮寫為 SSO),又譯為單一簽入,一種對于許多相互關(guān)連,但是又是各自獨立的軟件系統(tǒng),提供訪問控制的屬性。當(dāng)擁有這項屬性時,當(dāng)用戶登錄時,就可以獲取所有系統(tǒng)的訪問權(quán)限,不用對每個單一系統(tǒng)都逐一登錄。這項功能通常是以輕型目錄訪問協(xié)議(LDAP)來實現(xiàn),在服務(wù)器上會將用戶信息存儲到LDAP數(shù)據(jù)庫中。相同的,單一退出(single sign-off)就是指,只需要單一的退出動作,就可以結(jié)束對于多個系統(tǒng)的訪問權(quán)限。
單點登陸帶來的好處
- 降低訪問第三方網(wǎng)站的風(fēng)險(不存儲用戶密碼,或在外部管理)
- 減少因不同的用戶名和密碼組合而帶來的密碼疲勞
- 減少為相同的身份重新輸入密碼所花費的時間
- 因減少與密碼相關(guān)的調(diào)用IT服務(wù)臺的次數(shù)而降低IT成本
單點登陸技術(shù)
現(xiàn)在很多語言都擁有自己的單點登陸實現(xiàn)方案,本次案例中我們用SpringBoot Oauh2來實現(xiàn)跨系統(tǒng)的單點登陸
單點登陸 流程
你的項目可能有很多個模塊,如訂單管理、商戶管理、會員管理、財務(wù)管理,這些都是需要登陸后才能訪問,當(dāng)我只要登陸一次,其它的系統(tǒng)都能訪問。


ps這張圖網(wǎng)上找的,也是最清晰描述單點登陸的流程,如上圖就是最基本的單點登陸流程。
oauth2 的四種模式:
- 密碼模式(resource owner password credentials)
- 授權(quán)碼模式(authorization code)
- 簡化模式(implicit)
- 客戶端模式(client credentials)
我們一般都用授權(quán)碼模式 這個模式用的人也最多。
這幾種模式如果想要了解的更清楚可以看阮一峰老師的oauth2
阮一峰老師的oauth2精講
單點登陸準備工作
首先我們創(chuàng)建一個叫spring_sso_parent 普通的maven工程 作為整個項目的父工程,創(chuàng)建好后,刪除src目錄,并且修改pom.xml的依賴
spring_sso_parent 父工程的依賴如下:
<?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 http://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.1.6.RELEASE</version>
<relativePath/>
</parent>
<groupId>cn.com.scitc</groupId>
<artifactId>spring_sso_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<!-- 通用配置 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!-- spring oauth2 版本 -->
<oauth.version>2.3.6.RELEASE</oauth.version>
<!-- Spring Security OAuth2 AutoConfigure 版本 -->
<oauth-auto.version>2.1.6.RELEASE</oauth-auto.version>
</properties>
</project>
開始編寫單點登陸
我們在spring_sso_parent 父工程中 添加一個子模塊叫oauth_server的SpringBoot工程,
依賴如下
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.com.scitc</groupId>
<artifactId>spring_sso_parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>cn.com.scitc</groupId>
<artifactId>oauth_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oauth_server</name>
<packaging>war</packaging>
<description>this is oauth2 server</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${oauth.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
需要注意的是這里的SpringBoot 版本使用的是父模塊的版本
<parent>
<groupId>cn.com.scitc</groupId>
<artifactId>spring_sso_parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
我們在oauth_server 中創(chuàng)建一個config的包,并且創(chuàng)建一個WebSecurityConfig的類
@Configuration
@Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login")
.antMatchers("/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and().csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用內(nèi)存模擬數(shù)據(jù)庫查詢的用戶
auth.inMemoryAuthentication() //內(nèi)存認證
.withUser("admin")//admin 內(nèi)存認證用戶名
.password(passwordEncoder().encode("123456"))//被加密的123456密碼
.roles("ADMIN");//ROLE_ADMIN的角色
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
這個類使用了兩個注解,@Configuration 讓這個類成為了一個配置類, @Order(1) 這個注解是優(yōu)先級,使用優(yōu)先級來加載。
http.requestMatchers()
.antMatchers("/login")
.antMatchers("/oauth/authorize")
http.requestMatchers() 這個方法下配置的就是security 接收以什么樣的請求,我們這里只接受/login和/oauth/authorize的請求 。
.authorizeRequests() .anyRequest().authenticated()
這兩句配置的意思是除了以上請求所有的請求都需要身份認證才能訪問。
.formLogin().loginPage("/login").permitAll()
.and().csrf().disable();
這幾個配置的意思是采用form表單登陸默認登陸頁面是/login,任何人都能訪問,關(guān)閉csrf的保護。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用內(nèi)存模擬數(shù)據(jù)庫查詢的用戶
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
.roles("ADMIN");
}
這里采用的是AuthenticationManagerBuilder 允許內(nèi)存驗證,這里我添加了一個用戶名為admin 密碼是 123456,角色是ADMIN的 一個用戶 來模擬數(shù)據(jù)庫查詢的用戶信息。
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
PasswordEncoder 是Spring 官方提供的一個md5 密碼加密器,一般用于密碼的加密。
這個就是WebSecurityConfig的配置
下面我們在config中繼續(xù)創(chuàng)建一個叫OauthServerConfig的類
@Configuration
@EnableAuthorizationServer
public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(final AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("handleCilentId")//客戶端id
.secret(passwordEncoder.encode("secret"))//客戶端密鑰
.authorizedGrantTypes("authorization_code")//授權(quán)碼模式
.scopes("user_info") //授權(quán)范圍
.autoApprove(true)//開啟自動授權(quán)
.redirectUris("http://localhost:8882/login") //認證成功重定向
.accessTokenValiditySeconds(10);//設(shè)置超時時間
}
}
這個類上也使用了兩個注解,@Configuration 這個注解成為Spring的一個配置類,@EnableAuthorizationServer 注解是開啟授權(quán)服務(wù)器認證
這個類繼承了AuthorizationServerConfigurerAdapter 這個類提供了授權(quán)服務(wù)器策略。
這里我們實現(xiàn)了兩個configure 認證策略方法,分別是AuthorizationServerSecurityConfigurer 和 ClientDetailsServiceConfigurer,
而AuthorizationServerSecurityConfigurer提供了十幾個配置的方法,這里我們不會多去深入
其中 tokenKeyAccess意思是:oauth2授權(quán)服務(wù)器會提供一個/oauth/token_key的url來供資源服務(wù)器獲取公鑰,這個方法就是配置獲取公鑰的權(quán)限范圍,它使用的是SpEL表達式且默認不開啟, 這里我們使用的是permitAll(),讓本身的oauth的訪問不需要授權(quán)
checkTokenAccess意思是:授權(quán)服務(wù)器提供一個/oauth/check_token的url來供資源服務(wù)器解碼令牌,該方法就是配置權(quán)限范圍,同樣使用的是SpEL表達式且默認不開啟,我們這里設(shè)置的是 isAuthenticated(),檢查access_token需要進行授權(quán)
當(dāng)客戶端向認證服務(wù)器認證的時候,我們需要判斷這個客戶端是否通過了認證那么就要使用ClientDetailsServiceConfigurer 它提供了三種認證方式
- clients.withClientDetails() :使用數(shù)據(jù)庫認證
- clients.jdbc(): 傳入一個dataSource 這里可以使用自定義的dataSource
- clients.inMemory():內(nèi)存認證 相當(dāng)于將認證信息 寫死
這樣我們就將授權(quán)服務(wù)器配置好了。
下面我們創(chuàng)建一個controller的包
創(chuàng)建一個LoginController 登陸的控制器
@Controller
public class LoginController {
@GetMapping("/login")
public String loginPage() {
return "login";
}
}
這里返回的是一個login的 html 頁面
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <h1>標準登陸</h1> <form action="/auth/login" method="post"> username: <input type="text" name="username"/> <br/> password: <input type="password" name="password"/> <br/> <button type="submit">登陸</button> </form> </body> </html>
在創(chuàng)建一個UserInfoController 用于獲取認證成功的用戶信息
@RestController
public class UserInfoController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@RequestMapping("/user")
public ResponseEntity<Object> getUser(Principal principal) {
logger.info("principal:" + principal);
return new ResponseEntity<Object>(principal, HttpStatus.OK);
}
}
applicatin.yml 配置
server: port: 8880 servlet: context-path: /auth
然后我們創(chuàng)建2個客戶端分別是oauth_client1 和 oauth_client2
oauth_client1 的依賴如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.com.scitc</groupId>
<artifactId>spring_sso_parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>cn.com.scitc</groupId>
<artifactId>oauth_clinet1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>oauth_clinet1</name>
<description>this is client1</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>${oauth-auto.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
同樣創(chuàng)建一個config 包 并且創(chuàng)建一個 Oauth2ClientSeurityConfig這個類
@Configuration
@EnableOAuth2Sso
public class Oauth2ClientSeurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //關(guān)閉csrf保護
.antMatcher("/**") //使用以任意開頭的url
.authorizeRequests() // 配置路徑攔截,表明路徑訪問所對應(yīng)的權(quán)限,角色,認證信息
.antMatchers("/", "/login**") //控制不同的url接受不同權(quán)限的用戶訪問
.permitAll()// 允許所有人訪問
.anyRequest()
.authenticated(); //除了以上請求都需要身份認證
}
}
這個類繼承了 WebSecurityConfigurerAdapter 這個SpringSecurity的適配器,實現(xiàn)了HttpSecurity 的 configure 方法。 這個類也是兩個注解 @Configuration 成為一個配置類,
@EnableOAuth2Sso 啟用Oauth2的單點登陸。
我們再創(chuàng)建一個controller 包 ,并且創(chuàng)建一個 InfoController
@Controller
public class InfoController {
@GetMapping("/getUser")
public ResponseEntity<Object> userPage(Principal principal) {
//客戶端認證成功后返回這個用戶信息
return new ResponseEntity<Object>(principal, HttpStatus.OK);
}
@GetMapping("/")
public String indexPage() {
return "index";
}
}
index.html 頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index</title> </head> <body> <h1>請登錄授權(quán)</h1> <a href="/getUser" rel="external nofollow" >login</a> </body> </html>
application.yml
auth-server: http://localhost:8880/auth
server:
port: 8881
servlet:
context-path: /
security:
basic:
enabled: false
oauth2:
client:
clientId: handleCilentId
clientSecret: secret
accessTokenUri: ${auth-server}/oauth/token
userAuthorizationUri: ${auth-server}/oauth/authorize
resource:
userInfoUri: ${auth-server}/user
spring:
thymeleaf:
cache: false
auth-server:是目標認證服務(wù)器
clientId: 目標認證服務(wù)器設(shè)置的客戶端id
clientSecret: 目標認證服務(wù)器設(shè)置的密碼
accessTokenUri:從目標認證服務(wù)器獲取令牌token
userAuthorizationUri:從目標認證服務(wù)器請求授權(quán)默認url是/oauth/authorize
userInfoUri: 從目標認證服務(wù)器上將認證信息Principal通過形參綁定的方法通過URL的方式獲取用戶信息
oauth_client2配置和 oauth_client1是一樣的
我們啟動 認證服務(wù)器oauth_server 和 兩個客戶端 oauth_client1 和 oauth_client2
chrome 瀏覽器訪問 localhost:8881

當(dāng)我們點擊login的時候會跳轉(zhuǎn)到認證服務(wù)器進行登陸授權(quán)

授權(quán)成功后 返回了 這個用戶的所有的信息

我們再去訪問localhost:8082

當(dāng)我點擊登陸的時候 ,并沒有出現(xiàn)登陸授權(quán),直接拿到了用戶信息

注意這里我們不管是訪問客戶端1還是客戶端2 ,還是n多個客戶端,只要有一個授權(quán)成功 那么訪問其它的客戶端就不需要登陸 就能訪問相關(guān)的rest服務(wù)了。
總結(jié)
oauth2 實現(xiàn)的單點登陸 并不是很復(fù)雜,歸根結(jié)底,Spring幫我們做了太多的底層實現(xiàn),讓我們實現(xiàn)起來非常的簡單 實現(xiàn)幾個接口就可以搞定授權(quán)服務(wù)器的配置,客戶端的配置也非常的簡單?,F(xiàn)在我們流行看到了如百度,我登陸了百度,那么就可以直接訪問百度貼吧,百度糯米,等。單點登陸運用已經(jīng)使用的非常的廣泛。所以我認為掌握單點登陸是十分有必要的。
源碼地址
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot+WebSocket搭建簡單的多人聊天系統(tǒng)
- 詳解在spring boot中消息推送系統(tǒng)設(shè)計與實現(xiàn)
- 基于springboot搭建的web系統(tǒng)架構(gòu)的方法步驟
- Spring Boot 會員管理系統(tǒng)之處理文件上傳功能
- Spring Boot與Spark、Cassandra系統(tǒng)集成開發(fā)示例
- spring boot+thymeleaf+bootstrap實現(xiàn)后臺管理系統(tǒng)界面
- Spring Boot中使用 Spring Security 構(gòu)建權(quán)限系統(tǒng)的示例代碼
- Spring Boot 開發(fā)私有即時通信系統(tǒng)(WebSocket)
- 如何通過SpringBoot實現(xiàn)商城秒殺系統(tǒng)
相關(guān)文章
Java數(shù)據(jù)結(jié)構(gòu)之快速冪的實現(xiàn)
快速冪是用來解決求冪運算的高效方式。本文將詳細為大家介紹如何利用Java實現(xiàn)快速冪,以及利用快速冪求解冪運算問題,需要的可以參考一下2022-03-03
MyBatis中執(zhí)行SQL語句的幾種方式總結(jié)
MyBatis是一個優(yōu)秀的持久層框架,它支持定制化SQL、存儲過程以及高級映射,下面這篇文章主要給大家介紹了關(guān)于MyBatis中執(zhí)行SQL語句的幾種方式,需要的朋友可以參考下2024-04-04
如何在SpringBoot+Freemarker中獲取項目根目錄
這篇文章主要介紹了如何在SpringBoot+Freemarker中獲取項目根目錄的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
MyBatis中基于別名typeAliases的設(shè)置
這篇文章主要介紹了MyBatis中基于別名typeAliases的設(shè)置,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
解決sharding JDBC 不支持批量導(dǎo)入問題
這篇文章主要介紹了解決sharding JDBC 不支持批量導(dǎo)入問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
SpringMVC 參數(shù)綁定意義及實現(xiàn)過程解析
這篇文章主要介紹了SpringMVC 參數(shù)綁定意義及實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11

