首頁 > 軟體

Java中OAuth2.0第三方授權原理與實戰

2022-05-30 14:02:05

RFC6749

OAuth2的官方檔案在RFC6749:https://datatracker.ietf.org/doc/html/rfc6749

以王者榮耀請求微信登入的過程為例

  • A:Client申請存取使用者資源
  • B:使用者授權(過程較複雜)一次有效
  • C:Client向Server請求一個長時間有效的token
  • D:返回token
  • E:使用token存取資源

OAuth 2.0授權4大模式

授權碼模式:完整、嚴密,第三方認證和授權的最主流實現

  • A:APP請求微信登入
  • B:使用者資訊驗證
  • C:返回只能使用一次的授權碼
  • D:APP把授權碼發給後臺服務,服務請求微信登入請求一個長期有效的token
  • E:返回長期有效的token

token只有APP的後臺服務和微信的後臺才有,二者之間使用token通訊,保證資料不易洩露到後臺駭客

簡化模式:令牌使用者可見,行動端的常用實現手段

  • A:APP請求微信登入
  • B:使用者資訊驗證
  • C:直接返回一個長期有效的token

密碼模式:使用者名稱密碼都返回

  • A:使用者把使用者名稱 + 密碼傳送給APP
  • B:APP可以直接用賬號 + 密碼存取微信後臺

使用者端模式:後臺內容應用之間進行存取

微信登入會為APP分配一個內部的特定使用者名稱和密碼,APP用這個賬號 + 密碼和微信溝通,微信返回一個token,APP可以用這個token做很多部門自身的事情

合同到期後的續約機制

通常拿到Access token時還會拿到一個Refresh token,可以用來延長token的有效期

  • F:token已過期
  • G:使用Refresh token和微信登入溝通,嘗試延長token
  • H:重新返回一個新的token

OAuth2.0第三方授權實戰

oauth-client

表示王者榮耀端

pom檔案:

<?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.4.0</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  
  <groupId>org.example</groupId>
  <artifactId>oauth-client</artifactId>
  <version>1.0-SNAPSHOT</version>
  <description>Demo project for Spring Boot</description>
  
  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <spring-cloud.version>2020.0.0-M5</spring-cloud.version>
  </properties>
  
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  
  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
    </repository>
  </repositories>
  
</project>

SpringBoot的經典啟動類:

package com.wjw.oauthclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OauthClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(OauthClientApplication.class, args);
    }
}

建立OAuth2的設定類:

package com.wjw.oauthclient.config;

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

//啟用OAuth2 SSO使用者端
@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .authenticated()
            .and()
            .csrf()
            .disable();
    }
}

組態檔application.yaml:

security.oauth2.client.client-secret=my_client_secret
security.oauth2.client.client-id=my_client_id
security.oauth2.client.user-authorization-uri=http://localhost:9090/oauth/authorize
security.oauth2.client.access-token-uri=http://localhost:9090/oauth/token
security.oauth2.resource.user-info-uri=http://localhost:9090/user

server.port=8080

server.servlet.session.cookie.name=ut
  • 定義使用者id為my_client_id,密碼為my_client_secret
  • 定義“微信登入服務”為:http://localhost:9090/oauth/authorize
  • 請求長期有效token的地址為:http://localhost:9090/oauth/token
  • 拿到token後請求使用者資訊的地址為:http://localhost:9090/user

oauth-server

表示微信使用者端

pom檔案:

<?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.4.0</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>oauth-server</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>oauth-server</name>
  <description>Demo project for Spring Boot</description>
  
  <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>2020.0.0-M5</spring-cloud.version>
  </properties>
  
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    
  </dependencies>
  
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  
  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
    </repository>
  </repositories>
  
</project>

建立SpringBoot啟動類:

package com.wjw.oauthserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@SpringBootApplication
@EnableResourceServer
public class OauthServerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(OauthServerApplication.class, args);
    }
    
}

建立controller:

提供一個API介面,模擬資源伺服器

package com.wjw.oauthserver.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RestController
public class UserController {
    @GetMapping("/user")
    public Principal getCurrentUser(Principal principal) {
        return principal;
    }
}

建立oauth2的關鍵設定類:

package com.wjw.oauthserver.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() // 在記憶體裡
                .withClient("my_client_id")
                .secret(passwordEncoder.encode("my_client_secret")) // 加密密碼
                .autoApprove(true)  // 允許所有子類使用者登入
                .redirectUris("http://localhost:8080/login")    // 反跳會登入頁面
                .scopes("all")
                .accessTokenValiditySeconds(3600)
                .authorizedGrantTypes("authorization_code");    // 4大模式裡的授權碼模式
    }
}

現在需要提供一個登入頁面讓使用者輸入使用者名稱和密碼,並設定一個管理員賬戶:

package com.wjw.oauthserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .antMatchers("/login")
                .antMatchers("/oauth/authorize")
                .and()
                .authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("wjw")
                .password(passwordEncoder().encode("123456"))
                .roles("admin");
    }
}

組態檔application.yaml:

server.port=9090

分別啟動oauth-server和oauth-client進行測試:
存取localhost:8080/hello

存取hello時會重定向到login

存取登入時又會重定向到微信登入:

之後又會重定向到:

輸入賬戶名+密碼:

繼續看控制檯:
登入成功後還是會被重定向

此時再存取就會有授權碼了(只能用一次):

此時瀏覽器拿著授權碼才去真正請求王者榮耀的hello介面(瀏覽器自動使用授權碼交換token,對使用者透明):

王者榮耀使用授權碼請求微信得到一個token和使用者資訊並返回給瀏覽器,瀏覽器再使用這個token調王者榮耀的hello介面拿使用者資訊:

到此這篇關於Java中OAuth2.0第三方授權原理與實戰的文章就介紹到這了,更多相關Java OAuth2.0第三方授權內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com