溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法

發(fā)布時(shí)間:2020-09-13 01:49:13 來(lái)源:腳本之家 閱讀:163 作者:代碼忘煩惱 欄目:編程語(yǔ)言

什么是單點(diǎn)登陸

單點(diǎn)登錄(英語(yǔ):Single sign-on,縮寫(xiě)為 SSO),又譯為單一簽入,一種對(duì)于許多相互關(guān)連,但是又是各自獨(dú)立的軟件系統(tǒng),提供訪(fǎng)問(wèn)控制的屬性。當(dāng)擁有這項(xiàng)屬性時(shí),當(dāng)用戶(hù)登錄時(shí),就可以獲取所有系統(tǒng)的訪(fǎng)問(wèn)權(quán)限,不用對(duì)每個(gè)單一系統(tǒng)都逐一登錄。這項(xiàng)功能通常是以輕型目錄訪(fǎng)問(wèn)協(xié)議(LDAP)來(lái)實(shí)現(xiàn),在服務(wù)器上會(huì)將用戶(hù)信息存儲(chǔ)到LDAP數(shù)據(jù)庫(kù)中。相同的,單一退出(single sign-off)就是指,只需要單一的退出動(dòng)作,就可以結(jié)束對(duì)于多個(gè)系統(tǒng)的訪(fǎng)問(wèn)權(quán)限。

單點(diǎn)登陸帶來(lái)的好處

  • 降低訪(fǎng)問(wèn)第三方網(wǎng)站的風(fēng)險(xiǎn)(不存儲(chǔ)用戶(hù)密碼,或在外部管理)
  • 減少因不同的用戶(hù)名和密碼組合而帶來(lái)的密碼疲勞
  • 減少為相同的身份重新輸入密碼所花費(fèi)的時(shí)間
  • 因減少與密碼相關(guān)的調(diào)用IT服務(wù)臺(tái)的次數(shù)而降低IT成本

單點(diǎn)登陸技術(shù)

現(xiàn)在很多語(yǔ)言都擁有自己的單點(diǎn)登陸實(shí)現(xiàn)方案,本次案例中我們用SpringBoot Oauh3來(lái)實(shí)現(xiàn)跨系統(tǒng)的單點(diǎn)登陸

單點(diǎn)登陸 流程

你的項(xiàng)目可能有很多個(gè)模塊,如訂單管理、商戶(hù)管理、會(huì)員管理、財(cái)務(wù)管理,這些都是需要登陸后才能訪(fǎng)問(wèn),當(dāng)我只要登陸一次,其它的系統(tǒng)都能訪(fǎng)問(wèn)。

SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法

SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法

ps這張圖網(wǎng)上找的,也是最清晰描述單點(diǎn)登陸的流程,如上圖就是最基本的單點(diǎn)登陸流程。

oauth3 的四種模式:

  • 密碼模式(resource owner password credentials)
  • 授權(quán)碼模式(authorization code)
  • 簡(jiǎn)化模式(implicit)
  • 客戶(hù)端模式(client credentials)

我們一般都用授權(quán)碼模式 這個(gè)模式用的人也最多。

這幾種模式如果想要了解的更清楚可以看阮一峰老師的oauth3
阮一峰老師的oauth3精講

單點(diǎn)登陸準(zhǔn)備工作

首先我們創(chuàng)建一個(gè)叫spring_sso_parent 普通的maven工程 作為整個(gè)項(xiàng)目的父工程,創(chuàng)建好后,刪除src目錄,并且修改pom.xml的依賴(lài)

spring_sso_parent 父工程的依賴(lài)如下:

<?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>

開(kāi)始編寫(xiě)單點(diǎn)登陸

我們?cè)趕pring_sso_parent 父工程中 添加一個(gè)子模塊叫oauth_server的SpringBoot工程,
依賴(lài)如下

<?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>

我們?cè)趏auth_server 中創(chuàng)建一個(gè)config的包,并且創(chuàng)建一個(gè)WebSecurityConfig的類(lèi)

@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ù)庫(kù)查詢(xún)的用戶(hù)
    auth.inMemoryAuthentication() //內(nèi)存認(rèn)證
      .withUser("admin")//admin 內(nèi)存認(rèn)證用戶(hù)名
      .password(passwordEncoder().encode("123456"))//被加密的123456密碼
      .roles("ADMIN");//ROLE_ADMIN的角色
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
}

這個(gè)類(lèi)使用了兩個(gè)注解,@Configuration 讓這個(gè)類(lèi)成為了一個(gè)配置類(lèi), @Order(1) 這個(gè)注解是優(yōu)先級(jí),使用優(yōu)先級(jí)來(lái)加載。

 http.requestMatchers()
      .antMatchers("/login")
      .antMatchers("/oauth/authorize")

http.requestMatchers() 這個(gè)方法下配置的就是security 接收以什么樣的請(qǐng)求,我們這里只接受/login和/oauth/authorize的請(qǐng)求 。

 .authorizeRequests()
 .anyRequest().authenticated()

這兩句配置的意思是除了以上請(qǐng)求所有的請(qǐng)求都需要身份認(rèn)證才能訪(fǎng)問(wèn)。

.formLogin().loginPage("/login").permitAll()
.and().csrf().disable();

這幾個(gè)配置的意思是采用form表單登陸默認(rèn)登陸頁(yè)面是/login,任何人都能訪(fǎng)問(wèn),關(guān)閉csrf的保護(hù)。

@Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //使用內(nèi)存模擬數(shù)據(jù)庫(kù)查詢(xún)的用戶(hù)
    auth.inMemoryAuthentication()
      .withUser("admin")
      .password(passwordEncoder().encode("123456"))
      .roles("ADMIN");
  }

這里采用的是AuthenticationManagerBuilder 允許內(nèi)存驗(yàn)證,這里我添加了一個(gè)用戶(hù)名為admin 密碼是 123456,角色是ADMIN的 一個(gè)用戶(hù) 來(lái)模擬數(shù)據(jù)庫(kù)查詢(xún)的用戶(hù)信息。

 @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

PasswordEncoder 是Spring 官方提供的一個(gè)md5 密碼加密器,一般用于密碼的加密。

這個(gè)就是WebSecurityConfig的配置

下面我們?cè)赾onfig中繼續(xù)創(chuàng)建一個(gè)叫OauthServerConfig的類(lèi)

@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")//客戶(hù)端id
        .secret(passwordEncoder.encode("secret"))//客戶(hù)端密鑰
        .authorizedGrantTypes("authorization_code")//授權(quán)碼模式
        .scopes("user_info") //授權(quán)范圍
        .autoApprove(true)//開(kāi)啟自動(dòng)授權(quán)
        .redirectUris("http://localhost:8882/login") //認(rèn)證成功重定向
        .accessTokenValiditySeconds(10);//設(shè)置超時(shí)時(shí)間
  }
}

這個(gè)類(lèi)上也使用了兩個(gè)注解,@Configuration 這個(gè)注解成為Spring的一個(gè)配置類(lèi),@EnableAuthorizationServer 注解是開(kāi)啟授權(quán)服務(wù)器認(rèn)證

這個(gè)類(lèi)繼承了AuthorizationServerConfigurerAdapter 這個(gè)類(lèi)提供了授權(quán)服務(wù)器策略。

這里我們實(shí)現(xiàn)了兩個(gè)configure 認(rèn)證策略方法,分別是AuthorizationServerSecurityConfigurer 和 ClientDetailsServiceConfigurer,
而AuthorizationServerSecurityConfigurer提供了十幾個(gè)配置的方法,這里我們不會(huì)多去深入
其中 tokenKeyAccess意思是:oauth3授權(quán)服務(wù)器會(huì)提供一個(gè)/oauth/token_key的url來(lái)供資源服務(wù)器獲取公鑰,這個(gè)方法就是配置獲取公鑰的權(quán)限范圍,它使用的是SpEL表達(dá)式且默認(rèn)不開(kāi)啟, 這里我們使用的是permitAll(),讓本身的oauth的訪(fǎng)問(wèn)不需要授權(quán)

checkTokenAccess意思是:授權(quán)服務(wù)器提供一個(gè)/oauth/check_token的url來(lái)供資源服務(wù)器解碼令牌,該方法就是配置權(quán)限范圍,同樣使用的是SpEL表達(dá)式且默認(rèn)不開(kāi)啟,我們這里設(shè)置的是 isAuthenticated(),檢查access_token需要進(jìn)行授權(quán)

當(dāng)客戶(hù)端向認(rèn)證服務(wù)器認(rèn)證的時(shí)候,我們需要判斷這個(gè)客戶(hù)端是否通過(guò)了認(rèn)證那么就要使用ClientDetailsServiceConfigurer 它提供了三種認(rèn)證方式

  • clients.withClientDetails() :使用數(shù)據(jù)庫(kù)認(rèn)證
  • clients.jdbc(): 傳入一個(gè)dataSource 這里可以使用自定義的dataSource
  • clients.inMemory():內(nèi)存認(rèn)證 相當(dāng)于將認(rèn)證信息 寫(xiě)死

這樣我們就將授權(quán)服務(wù)器配置好了。

下面我們創(chuàng)建一個(gè)controller的包
創(chuàng)建一個(gè)LoginController 登陸的控制器

@Controller
public class LoginController {
  @GetMapping("/login")
  public String loginPage() {
    return "login";
  }
}

這里返回的是一個(gè)login的 html 頁(yè)面

login.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>login</title>
</head>
<body>
<h2>標(biāo)準(zhǔn)登陸</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>

在創(chuàng)建一個(gè)UserInfoController 用于獲取認(rèn)證成功的用戶(hù)信息

@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個(gè)客戶(hù)端分別是oauth_client1 和 oauth_client2
oauth_client1 的依賴(lài)如下:

<?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>

同樣創(chuàng)建一個(gè)config 包 并且創(chuàng)建一個(gè) Oauth3ClientSeurityConfig這個(gè)類(lèi)

@Configuration
@EnableOAuth3Sso
public class Oauth3ClientSeurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable() //關(guān)閉csrf保護(hù)
      .antMatcher("/**") //使用以任意開(kāi)頭的url
      .authorizeRequests() // 配置路徑攔截,表明路徑訪(fǎng)問(wèn)所對(duì)應(yīng)的權(quán)限,角色,認(rèn)證信息
      .antMatchers("/", "/login**") //控制不同的url接受不同權(quán)限的用戶(hù)訪(fǎng)問(wèn)
      .permitAll()// 允許所有人訪(fǎng)問(wèn)
      .anyRequest() 
      .authenticated(); //除了以上請(qǐng)求都需要身份認(rèn)證
  }
}

這個(gè)類(lèi)繼承了 WebSecurityConfigurerAdapter 這個(gè)SpringSecurity的適配器,實(shí)現(xiàn)了HttpSecurity 的 configure 方法。 這個(gè)類(lèi)也是兩個(gè)注解 @Configuration 成為一個(gè)配置類(lèi),
@EnableOAuth3Sso 啟用Oauth3的單點(diǎn)登陸。

我們?cè)賱?chuàng)建一個(gè)controller 包 ,并且創(chuàng)建一個(gè) InfoController

@Controller
public class InfoController {
  @GetMapping("/getUser")
  public ResponseEntity<Object> userPage(Principal principal) {
    //客戶(hù)端認(rèn)證成功后返回這個(gè)用戶(hù)信息
    return new ResponseEntity<Object>(principal, HttpStatus.OK);
  }

  @GetMapping("/")
  public String indexPage() {
    return "index";
  }
}

index.html 頁(yè)面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>index</title>
</head>
<body>
<h2>請(qǐng)登錄授權(quán)</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:是目標(biāo)認(rèn)證服務(wù)器
clientId: 目標(biāo)認(rèn)證服務(wù)器設(shè)置的客戶(hù)端id
clientSecret: 目標(biāo)認(rèn)證服務(wù)器設(shè)置的密碼
accessTokenUri:從目標(biāo)認(rèn)證服務(wù)器獲取令牌token
userAuthorizationUri:從目標(biāo)認(rèn)證服務(wù)器請(qǐng)求授權(quán)默認(rèn)url是/oauth/authorize
userInfoUri: 從目標(biāo)認(rèn)證服務(wù)器上將認(rèn)證信息Principal通過(guò)形參綁定的方法通過(guò)URL的方式獲取用戶(hù)信息

oauth_client2配置和 oauth_client1是一樣的

我們啟動(dòng) 認(rèn)證服務(wù)器oauth_server 和 兩個(gè)客戶(hù)端 oauth_client1 和 oauth_client2
chrome 瀏覽器訪(fǎng)問(wèn) localhost:8881

SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法

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

SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法

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

SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法

我們?cè)偃ピL(fǎng)問(wèn)localhost:8082

SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法

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

SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法

注意這里我們不管是訪(fǎng)問(wèn)客戶(hù)端1還是客戶(hù)端2 ,還是n多個(gè)客戶(hù)端,只要有一個(gè)授權(quán)成功 那么訪(fǎng)問(wèn)其它的客戶(hù)端就不需要登陸 就能訪(fǎng)問(wèn)相關(guān)的rest服務(wù)了。

總結(jié)

oauth3 實(shí)現(xiàn)的單點(diǎn)登陸 并不是很復(fù)雜,歸根結(jié)底,Spring幫我們做了太多的底層實(shí)現(xiàn),讓我們實(shí)現(xiàn)起來(lái)非常的簡(jiǎn)單 實(shí)現(xiàn)幾個(gè)接口就可以搞定授權(quán)服務(wù)器的配置,客戶(hù)端的配置也非常的簡(jiǎn)單?,F(xiàn)在我們流行看到了如百度,我登陸了百度,那么就可以直接訪(fǎng)問(wèn)百度貼吧,百度糯米,等。單點(diǎn)登陸運(yùn)用已經(jīng)使用的非常的廣泛。所以我認(rèn)為掌握單點(diǎn)登陸是十分有必要的。

源碼地址

github

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI