您好,登錄后才能下訂單哦!
什么是單點登陸
單點登錄(英語:Single sign-on,縮寫為 SSO),又譯為單一簽入,一種對于許多相互關連,但是又是各自獨立的軟件系統,提供訪問控制的屬性。當擁有這項屬性時,當用戶登錄時,就可以獲取所有系統的訪問權限,不用對每個單一系統都逐一登錄。這項功能通常是以輕型目錄訪問協議(LDAP)來實現,在服務器上會將用戶信息存儲到LDAP數據庫中。相同的,單一退出(single sign-off)就是指,只需要單一的退出動作,就可以結束對于多個系統的訪問權限。
單點登陸帶來的好處
單點登陸技術
現在很多語言都擁有自己的單點登陸實現方案,本次案例中我們用SpringBoot Oauh3來實現跨系統的單點登陸
單點登陸 流程
你的項目可能有很多個模塊,如訂單管理、商戶管理、會員管理、財務管理,這些都是需要登陸后才能訪問,當我只要登陸一次,其它的系統都能訪問。
ps這張圖網上找的,也是最清晰描述單點登陸的流程,如上圖就是最基本的單點登陸流程。
oauth3 的四種模式:
我們一般都用授權碼模式 這個模式用的人也最多。
這幾種模式如果想要了解的更清楚可以看阮一峰老師的oauth3
阮一峰老師的oauth3精講
單點登陸準備工作
首先我們創建一個叫spring_sso_parent 普通的maven工程 作為整個項目的父工程,創建好后,刪除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 oauth3 版本 --> <oauth.version>2.3.6.RELEASE</oauth.version> <!-- Spring Security OAuth3 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 oauth3 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-oauth3</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 中創建一個config的包,并且創建一個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 { //使用內存模擬數據庫查詢的用戶 auth.inMemoryAuthentication() //內存認證 .withUser("admin")//admin 內存認證用戶名 .password(passwordEncoder().encode("123456"))//被加密的123456密碼 .roles("ADMIN");//ROLE_ADMIN的角色 } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
這個類使用了兩個注解,@Configuration 讓這個類成為了一個配置類, @Order(1) 這個注解是優先級,使用優先級來加載。
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,任何人都能訪問,關閉csrf的保護。
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //使用內存模擬數據庫查詢的用戶 auth.inMemoryAuthentication() .withUser("admin") .password(passwordEncoder().encode("123456")) .roles("ADMIN"); }
這里采用的是AuthenticationManagerBuilder 允許內存驗證,這里我添加了一個用戶名為admin 密碼是 123456,角色是ADMIN的 一個用戶 來模擬數據庫查詢的用戶信息。
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
PasswordEncoder 是Spring 官方提供的一個md5 密碼加密器,一般用于密碼的加密。
這個就是WebSecurityConfig的配置
下面我們在config中繼續創建一個叫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")//授權碼模式 .scopes("user_info") //授權范圍 .autoApprove(true)//開啟自動授權 .redirectUris("http://localhost:8882/login") //認證成功重定向 .accessTokenValiditySeconds(10);//設置超時時間 } }
這個類上也使用了兩個注解,@Configuration 這個注解成為Spring的一個配置類,@EnableAuthorizationServer 注解是開啟授權服務器認證
這個類繼承了AuthorizationServerConfigurerAdapter 這個類提供了授權服務器策略。
這里我們實現了兩個configure 認證策略方法,分別是AuthorizationServerSecurityConfigurer 和 ClientDetailsServiceConfigurer,
而AuthorizationServerSecurityConfigurer提供了十幾個配置的方法,這里我們不會多去深入
其中 tokenKeyAccess意思是:oauth3授權服務器會提供一個/oauth/token_key的url來供資源服務器獲取公鑰,這個方法就是配置獲取公鑰的權限范圍,它使用的是SpEL表達式且默認不開啟, 這里我們使用的是permitAll(),讓本身的oauth的訪問不需要授權
checkTokenAccess意思是:授權服務器提供一個/oauth/check_token的url來供資源服務器解碼令牌,該方法就是配置權限范圍,同樣使用的是SpEL表達式且默認不開啟,我們這里設置的是 isAuthenticated(),檢查access_token需要進行授權
當客戶端向認證服務器認證的時候,我們需要判斷這個客戶端是否通過了認證那么就要使用ClientDetailsServiceConfigurer 它提供了三種認證方式
這樣我們就將授權服務器配置好了。
下面我們創建一個controller的包
創建一個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> <h2>標準登陸</h2> <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>
在創建一個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
然后我們創建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-oauth3-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>
同樣創建一個config 包 并且創建一個 Oauth3ClientSeurityConfig這個類
@Configuration @EnableOAuth3Sso public class Oauth3ClientSeurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() //關閉csrf保護 .antMatcher("/**") //使用以任意開頭的url .authorizeRequests() // 配置路徑攔截,表明路徑訪問所對應的權限,角色,認證信息 .antMatchers("/", "/login**") //控制不同的url接受不同權限的用戶訪問 .permitAll()// 允許所有人訪問 .anyRequest() .authenticated(); //除了以上請求都需要身份認證 } }
這個類繼承了 WebSecurityConfigurerAdapter 這個SpringSecurity的適配器,實現了HttpSecurity 的 configure 方法。 這個類也是兩個注解 @Configuration 成為一個配置類,
@EnableOAuth3Sso 啟用Oauth3的單點登陸。
我們再創建一個controller 包 ,并且創建一個 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> <h2>請登錄授權</h2> <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 oauth3: 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:是目標認證服務器
clientId: 目標認證服務器設置的客戶端id
clientSecret: 目標認證服務器設置的密碼
accessTokenUri:從目標認證服務器獲取令牌token
userAuthorizationUri:從目標認證服務器請求授權默認url是/oauth/authorize
userInfoUri: 從目標認證服務器上將認證信息Principal通過形參綁定的方法通過URL的方式獲取用戶信息
oauth_client2配置和 oauth_client1是一樣的
我們啟動 認證服務器oauth_server 和 兩個客戶端 oauth_client1 和 oauth_client2
chrome 瀏覽器訪問 localhost:8881
當我們點擊login的時候會跳轉到認證服務器進行登陸授權
授權成功后 返回了 這個用戶的所有的信息
我們再去訪問localhost:8082
當我點擊登陸的時候 ,并沒有出現登陸授權,直接拿到了用戶信息
注意這里我們不管是訪問客戶端1還是客戶端2 ,還是n多個客戶端,只要有一個授權成功 那么訪問其它的客戶端就不需要登陸 就能訪問相關的rest服務了。
總結
oauth3 實現的單點登陸 并不是很復雜,歸根結底,Spring幫我們做了太多的底層實現,讓我們實現起來非常的簡單 實現幾個接口就可以搞定授權服務器的配置,客戶端的配置也非常的簡單。現在我們流行看到了如百度,我登陸了百度,那么就可以直接訪問百度貼吧,百度糯米,等。單點登陸運用已經使用的非常的廣泛。所以我認為掌握單點登陸是十分有必要的。
源碼地址
github
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。