主頁 > 後端開發 > 08-微服務版單點登陸系統(SSO)實踐(2107)

08-微服務版單點登陸系統(SSO)實踐(2107)

2021-11-02 09:55:25 後端開發

文章目錄

  • 單點登陸系統簡介
    • 背景分析
    • 單點登陸系統概述
    • 單點登陸系統解決方案設計
  • 單點登陸系統初步設計
    • 服務設計
    • 工程結構設計
  • SSO父工程創建及初始化
    • 創建父工程
    • 父工程pom檔案初始配置
  • 系統基礎服務工程設計及實作
    • 業務描述
    • 表結構設計
    • 工程資料初始化
    • 創建系統服務工程并初始化
    • Pojo物件邏輯實作
    • Dao物件邏輯實作
    • Service物件邏輯實作
    • Controller物件邏輯實作
    • 啟動服務進行訪問測驗
  • 統一認證工程設計及實作
    • 業務描述
    • 創建工程及初始化
    • 啟動并訪問專案
    • 定義用戶資訊處理物件
    • 定義Security配置類
    • 基于Postman進行訪問測驗
    • Security 認證流程分析(了解)
    • 構建令牌生成及配置物件
    • 定義Oauth2認證授權配置
    • 啟動postman進行訪問測驗
  • 資源服務工程設計及實作
    • 業務描述
    • 業務設計架構
    • 專案創建及初始化
    • 創建資源Controller物件
    • 配置令牌決議器物件
    • 配置資源認證授權規則
    • 啟動Postman進行訪問測驗
  • 網關工程設計及實作
    • 業務描述
    • 專案創建及初始化
    • 啟動postman進行訪問測驗
  • 客戶端UI工程設計及實作
    • 業務描述
    • 專案創建及初始化
    • 創建UI工程登陸頁面
    • 創建資源展現頁面
  • 技術摘要應用實踐說明
    • 背景分析
    • Spring Security 技術
    • Jwt 資料規范
    • Oauth2規范
  • 總結(Summary)
    • 重難點分析
    • FAQ 分析
    • Bug 分析

單點登陸系統簡介

背景分析

傳統的登錄系統中,每個站點都實作了自己的專用登錄模塊,各站點的登錄狀態相互不認可,各站點需要逐一手工登錄,例如:
在這里插入圖片描述
這樣的系統,我們又稱之為多點登陸系統,應用起來相對繁瑣(每次訪問資源服務都需要重新登陸認證和授權),與此同時,系統代碼的重復也比較高,由此單點登陸系統誕生,

單點登陸系統概述

單點登錄,英文是 Single Sign On(縮寫為 SSO),即多個站點共用一臺認證授權服務器,用戶在其中任何一個站點登錄后,可以免登錄訪問其他所有站點,而且,各站點間可以通過該登錄狀態直接互動,例如:

在這里插入圖片描述

單點登陸系統解決方案設計

  • 解決方案1:用戶登陸成功以后,將用戶登陸狀態存盤到redis資料庫,例如:

在這里插入圖片描述

說明,在這套方案中,用戶登錄成功后,會基于UUID生成一個token,然后與用戶資訊系結在一起存盤到資料庫.后續用戶在訪問資源時,基于token從資料庫查詢用戶狀態,這種方式因為要基于資料庫存盤和查詢用戶狀態,所以性能表現一般.

  • 解決方案2:用戶登陸成功以后,將用戶資訊存盤到token(令牌),然后寫到客戶端進行存盤,(本次設計方案)

在這里插入圖片描述

說明,在這套方案中,用戶登錄成功后,會基于JWT技術生成一個token,用戶資訊可以存盤到這個token中.后續用戶在訪問資源時,對token內容決議,檢查登錄狀態以及權限資訊,無須再訪問資料庫.

單點登陸系統初步設計

服務設計

基于單點登陸系統中的業務描述,進行初步服務架構設計,如圖所示:
在這里插入圖片描述
其中,服務基于業務進行劃分,系統(system)服務只提供基礎資料(例如用戶資訊,日志資訊等),認證服務(auth)負責完成用戶身份的校驗,密碼的比對,資源服務(resource)代表一些業務服務(例如我的訂單,我的收藏等等).

工程結構設計

基于服務的劃分,設計工程結構如下:
在這里插入圖片描述

SSO父工程創建及初始化

創建父工程

第一步:創建父工程,例如:
在這里插入圖片描述

第二步:洗掉父工程src目錄(可選),

父工程pom檔案初始配置

初始化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>

    <groupId>com.jt</groupId>
    <artifactId>02-sso</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--maven父工程的pom檔案中一般要定義子模塊,
    子工程中所需依賴版本的管理,公共依賴并且父工程的打包方式一般為pom方式-->

    <!--第一步: 定義子工程中核心依賴的版本管理(注意,只是版本管理)-->
    <dependencyManagement>
        <dependencies>
            <!--spring boot 核心依賴版本定義(spring官方定義)-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--Spring Cloud 微服務規范(由spring官方定義)-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type><!--假如scope是import,type必須為pom-->
                <scope>import</scope><!--引入三方依賴的版本設計-->
            </dependency>

            <!--Spring Cloud alibaba 依賴版本管理 (參考官方說明)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--第二步: 添加子工程的所需要的公共依賴-->
    <dependencies>
        <!--lombok 依賴,子工程中假如需要lombok,不需要再引入-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope><!--provided 表示此依賴僅在編譯階段有效-->
        </dependency>
        <!--單元測驗依賴,子工程中需要單元測驗時,不需要再次引入此依賴了-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope><!--test表示只能在test目錄下使用此依賴-->
            <exclusions>
                <exclusion><!--排除一些不需要的依賴-->
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--其它依賴...-->
    </dependencies>
    <!--第三步: 定義當前工程模塊及子工程的的統一編譯和運行版本-->
    <build><!--專案構建配置,我們基于maven完成專案的編譯,測驗,打包等操作,
    都是基于pom.xml完成這一列的操作,但是編譯和打包的配置都是要寫到build元素
    內的,而具體的編譯和打包配置,又需要plugin去實作,plugin元素不是必須的,maven
    有默認的plugin配置,常用插件可去本地庫進行查看-->
        <plugins>
            <!--通過maven-compiler-plugin插件設定專案
            的統一的jdk編譯和運行版本-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <!--假如本地庫沒有這個版本,這里會出現紅色字體錯誤-->
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

系統基礎服務工程設計及實作

業務描述

本次設計系統服務(System),主要用于提供基礎資料服務,例如日志資訊,用戶資訊等,

表結構設計

系統服務模塊,基本表結構設計,例如:
在這里插入圖片描述

工程資料初始化

將jt-sso.sql檔案在mysql中執行一下,其程序如下:
第一:登錄mysql

mysql -uroot -proot

第二:通過source指令執行jt-sso.sql檔案

source d:/jt-sso.sql

創建系統服務工程并初始化

第一步:創建sso-system工程,例如:
在這里插入圖片描述
第二步:添加專案依賴,例如

        <!--1.資料庫訪問相關-->
        <!--1.1 mysql 資料庫驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--1.2 mybatis plus 插件-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <!--服務治理相關-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--Web 服務相關-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

第三步:在專案中添加bootstrap.yml檔案,其內容如下:

server:
  port: 8061
spring:
  application:
    name: sso-system
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml
  datasource:
    url: jdbc:mysql:///jt-sso?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: root

說明,可將連接資料庫的配置,添加到配置中心,

第四步:在專案中添加啟動類,例如:

package com.jt;

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

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

第五步:在專案中添加單元測驗類,測驗資料庫連接,例如:

package com.jt;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
public class DataSourceTests {
    @Autowired
    private DataSource dataSource;//HikariDataSource
    @Test
    void testGetConnection() throws SQLException {
        Connection conn=
        dataSource.getConnection();
        System.out.println(conn);
    }
}

Pojo物件邏輯實作

添加專案User物件,用于封裝用戶資訊,

package com.jt.system.pojo;
import lombok.Data;
import java.io.Serializable;

/**
 * 通過此物件封裝用戶資訊
 */
@Data
public class User implements Serializable {
    private static final long serialVersionUID = 4831304712151465443L;
    private Long id;
    private String username;
    private String password;
    private String status;
}

Dao物件邏輯實作

第一步:創建UserMapper介面,并定義基于用戶名查詢用戶資訊,基于用戶id查詢用戶權限資訊的方法,代碼如下:

package com.jt.system.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.system.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper extends BaseMapper<User> {
    /**
     * 基于用戶名獲取用戶資訊
     * @param username
     * @return
     */
    @Select("select id,username,password,status " +
            "from tb_users " +
            "where username=#{username}")
    User selectUserByUsername(String username);

    /**
     * 基于用戶id查詢用戶權限
     * @param userId 用戶id
     * @return 用戶的權限
     * 涉及到的表:tb_user_roles,tb_role_menus,tb_menus
     */
    @Select("select distinct m.permission " +
            "from tb_user_roles ur join tb_role_menus rm on ur.role_id=rm.role_id" +
            "     join tb_menus m on rm.menu_id=m.id " +
            "where ur.user_id=#{userId}")
    List<String> selectUserPermissions(Long userId);

}

第二步:創建UserMapperTests類,對業務方法做單元測驗,例如:

package com.jt;

import com.jt.system.pojo.User;
import com.jt.system.dao.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class UserMapperTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void testSelectUserByUsername(){
        User user =
        userMapper.selectUserByUsername("admin");
        System.out.println(user);
    }
    @Test
    void testSelectUserPermissions(){
        List<String> permission=
        userMapper.selectUserPermissions(1L);
        System.out.println(permission);
    }
}

Service物件邏輯實作

創建UserService介面及實作淚,定義用戶及用戶權限查詢邏輯,代碼如下:

第一步:定義service介面,代碼如下:

package com.jt.system.service;

import com.jt.system.pojo.User;

import java.util.List;

public interface UserService {
    User selectUserByUsername(String username);
    List<String> selectUserPermissions(Long userId);
}

第二步:定義service介面實作類,代碼如下:

package com.jt.system.service.impl;

import com.jt.system.dao.UserMapper;
import com.jt.system.pojo.User;
import com.jt.system.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public User selectUserByUsername(String username) {
        return userMapper.selectUserByUsername(username);
    }
    @Override
    public List<String> selectUserPermissions(Long userId) {
        return userMapper.selectUserPermissions(userId);
    }
}

Controller物件邏輯實作

package com.jt.system.controller;

import com.jt.system.pojo.User;
import com.jt.system.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/user/")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/login/{username}")
    public User doSelectUserByUsername(
            @PathVariable("username") String username){
        return userService.selectUserByUsername(username);
    }
    @GetMapping("/permission/{userId}")
    public List<String> doSelectUserPermissions(
            @PathVariable("userId") Long userId){
        return userService.selectUserPermissions(userId);
    }
}

啟動服務進行訪問測驗

啟動sso-system工程服務,打開瀏覽器分別對用戶及用戶權限資訊的獲取進行訪問測驗

  • 基于用戶名查詢用戶資訊,例如:
    在這里插入圖片描述
  • 基于用戶id(這里假設用戶id為1)查詢用戶權限,例如:
    在這里插入圖片描述

統一認證工程設計及實作

業務描述

用戶登陸時呼叫此工程對用戶身份進行統一身份認證和授權,

創建工程及初始化

第一步:創建sso-auth工程,如圖所示
在這里插入圖片描述

第二步:打開sso-auth工程中的pom檔案,然后添加如下依賴:

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--SSO技術方案:SpringSecurity+JWT+oauth2-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!--open feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

    </dependencies>

第三步:在sso-auth工程中創建bootstrap.yml檔案,例如:

server:
  port: 8071
spring:
  application:
    name: sso-auth
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848

第四步 添加專案啟動類,例如

package com.jt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableFeignClients
@SpringBootApplication
public class AuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}

啟動并訪問專案

專案啟動時,系統會默認生成一個登陸密碼,例如:
在這里插入圖片描述
打開瀏覽器輸入http://localhost:8071呈現登陸頁面,例如:

在這里插入圖片描述
其中,默認用戶名為user,密碼為系統啟動時,在控制臺呈現的密碼,執行登陸測驗,登陸成功進入如下界面(因為沒有定義登陸頁面,所以會出現404):

在這里插入圖片描述

定義用戶資訊處理物件

第一步:定義User物件,用于封裝從資料庫查詢到的用戶資訊,例如:

package com.jt.auth.pojo;

import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
    private static final long serialVersionUID = 4831304712151465443L;
    private Long id;
    private String username;
    private String password;
    private String status;
}

第二步:定義遠程Service物件,用于實作遠程用戶資訊呼叫,例如:

package com.jt.auth.service;

import com.jt.auth.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

@FeignClient(value = "sso-system", contextId ="remoteUserService" )
public interface RemoteUserService {

    @GetMapping("/user/login/{username}")
    User selectUserByUsername( @PathVariable("username") String username);

    @GetMapping("/user/permission/{userId}")
    List<String> selectUserPermissions(@PathVariable("userId") Long userId);
}

第三步:定義用戶登陸業務邏輯處理物件,例如:

package com.jt.auth.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private RemoteUserService remoteUserService;
    /**
     * 基于用戶名獲取資料庫中的用戶資訊
     * @param username 這個username來自客戶端
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        //基于feign方式獲取遠程資料并封裝
        //1.基于用戶名獲取用戶資訊
        com.jt.auth.pojo.User user=
        remoteUserService.selectUserByUsername(username);
        if(user==null)
            throw new UsernameNotFoundException("用戶不存在");
        //2.基于用于id查詢用戶權限
        List<String> permissions=
        remoteUserService.selectUserPermissions(user.getId());
        log.info("permissions {}",permissions);
        //3.對查詢結果進行封裝并回傳
        User userInfo= new User(username,
                user.getPassword(),
                AuthorityUtils.createAuthorityList(permissions.toArray(new String[]{})));
        //......
        return userInfo;
        //回傳給認證中心,認證中心會基于用戶輸入的密碼以及資料庫的密碼做一個比對
    }
}

定義Security配置類

定義Spring Security配置類,在此類中配置認證規則,例如:

package com.jt.auth.config;

import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 當我們在執行登錄操作時,底層邏輯(了解):
 * 1)Filter(過濾器)
 * 2)AuthenticationManager (認證管理器)
 * 3)AuthenticationProvider(認證服務處理器)
 * 4)UserDetailsService(負責用戶資訊的獲取及封裝)
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

     /**
     * 初始化加密物件
     * 此物件提供了一種不可逆的加密方式,相對于md5方式會更加安全
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
    /**
     * 定義認證管理器物件,這個物件負責完成用戶資訊的認證,
     * 即判定用戶身份資訊的合法性,在基于oauth2協議完成認
     * 證時,需要此物件,所以這里講此物件拿出來交給spring管理
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
      return super.authenticationManager();
    }

    /**配置認證規則*/
    @Override
    protected void configure(HttpSecurity http)
            throws Exception {
        //super.configure(http);//默認所有請求都要認證
        //1.禁用跨域攻擊(先這么寫,不寫會報403例外)
        http.csrf().disable();
        //2.放行所有資源的訪問(后續可以基于選擇對資源進行認證和放行)
        http.authorizeRequests()
                .anyRequest().permitAll();
        //3.自定義定義登錄成功和失敗以后的處理邏輯(可選)
        //假如沒有如下設定登錄成功會顯示404
        http.formLogin()//這句話會對外暴露一個登錄路徑/login
                 .successHandler(successHandler())
                 .failureHandler(failureHandler());
    }
    //定義認證成功處理器
    //登錄成功以后回傳json資料
    @Bean
    public AuthenticationSuccessHandler successHandler(){
         //lambda
         return (request,response,authentication)->{
             //構建map物件封裝到要回應到客戶端的資料
             Map<String,Object> map=new HashMap<>();
             map.put("state",200);
             map.put("message", "login ok");
             //將map物件轉換為json格式字串并寫到客戶端
             writeJsonToClient(response,map);
         };
    }
    //定義登錄失敗處理器
    @Bean
    public AuthenticationFailureHandler failureHandler(){
        return (request,response,exception)->{
            //構建map物件封裝到要回應到客戶端的資料
            Map<String,Object> map=new HashMap<>();
            map.put("state",500);
            map.put("message", "login error");
            //將map物件轉換為json格式字串并寫到客戶端
            writeJsonToClient(response,map);
        };
    }
    private void writeJsonToClient(
            HttpServletResponse response,
            Map<String,Object> map) throws IOException {
         //將map物件,轉換為json
          String json=new ObjectMapper().writeValueAsString(map);
          //設定回應資料的編碼方式
          response.setCharacterEncoding("utf-8");
          //設定回應資料的型別
          response.setContentType("application/json;charset=utf-8");
          //將資料回應到客戶端
          PrintWriter out=response.getWriter();
          out.println(json);
          out.flush();
    }
}

基于Postman進行訪問測驗

啟動sso-system,sso-auth服務,然后基于postman訪問網關,執行登錄測驗,例如:
在這里插入圖片描述

Security 認證流程分析(了解)

目前的登陸操作,也就是用戶的認證操作,其實作主要基于Spring Security框架,其認證簡易流程如下:
在這里插入圖片描述

構建令牌生成及配置物件

本次我們借助JWT(Json Web Token-是一種json格式)方式將用戶相關資訊進行組織和加密,并作為回應令牌(Token),從服務端回應到客戶端,客戶端接收到這個JWT令牌之后,將其保存在客戶端(例如localStorage),然后攜帶令牌訪問資源服務器,資源服務器獲取并決議令牌的合法性,基于決議結果判定是否允許用戶訪問資源.

package com.jt.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;


/**
 * 在此配置類中配置令牌的生成,存盤策略,驗簽方式(令牌合法性),
 */
@Configuration
public class TokenConfig {

    /**
     * 配置令牌的存盤策略,對于oauth2規范中提供了這樣的幾種策略
     * 1)JdbcTokenStore(這里是要將token存盤到關系型資料庫)
     * 2)RedisTokenStore(這是要將token存盤到redis資料庫-key/value)
     * 3)JwtTokenStore(這里是將產生的token資訊存盤客戶端,并且token
     * 中可以以自包含的形式存盤一些用戶資訊)
     * 4)....
     */
    @Bean
    public TokenStore tokenStore(){
        //這里采用JWT方式生成和存盤令牌資訊
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    /**
     * 配置令牌的創建及驗簽方式
     * 基于此物件創建的令牌資訊會封裝到OAuth2AccessToken型別的物件中
     * 然后再存盤到TokenStore物件,外界需要時,會從tokenStore進行獲取,
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter=
                new JwtAccessTokenConverter();
        //JWT令牌構成:header(簽名演算法,令牌型別),payload(資料部分),Signing(簽名)
        //這里的簽名可以簡單理解為加密,加密時會使用header中演算法以及我們自己提供的密鑰,
        //這里加密的目的是為了防止令牌被篡改,(這里密鑰要保管好,要存盤在服務端)
        jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//設定密鑰
        return jwtAccessTokenConverter;
    }

    /**
     * JWT 令牌簽名時使用的密鑰(可以理解為鹽值加密中的鹽)
     * 1)生成的令牌需要這個密鑰進行簽名
     * 2)獲取的令牌需要使用這個密鑰進行驗簽(校驗令牌合法性,是否被篡改過)
     */
    private static final String SIGNING_KEY="auth";
}

定義Oauth2認證授權配置

第一步:所有零件準備好了開始拼裝最后的主體部分,這個主體部分就是授權服務器的核心配置

package com.jt.auth.config;
import lombok.AllArgsConstructor;
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.core.userdetails.UserDetailsService;
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.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.Arrays;

/**
 * 在這個物件中負責將所有的認證和授權相關配置進行整合,例如
 * 業務方面:
 * 1)如何認證(認證邏輯的設計)
 * 2)認證通過以后如何頒發令牌(令牌的規范)
 * 3)為誰頒發令牌(客戶端標識,client_id,...)
 * 技術方面:
 * 1)SpringSecurity (提供認證和授權的實作)
 * 2)TokenConfig(提供了令牌的生成,存盤,校驗)
 * 3)Oauth2(定義了一套認證規范,例如為誰發令牌,都發什么,...)
 */
@AllArgsConstructor //生成一個全參建構式
@Configuration
@EnableAuthorizationServer//啟動認證和授權
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
  
    private AuthenticationManager authenticationManager;
    private UserDetailsService userDetailsService;
    private TokenStore tokenStore;
    private PasswordEncoder passwordEncoder;
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    /**
     * oauth2中的認證細節配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        //super.configure(endpoints);
        endpoints
        //由誰完成認證?(認證管理器)
        .authenticationManager(authenticationManager)
        //誰負責訪問資料庫?(認證時需要兩部分資訊:一部分來自客戶端,一部分來自資料庫)
        .userDetailsService(userDetailsService)
        //支持對什么請求進行認證(默認支持post方式)
        .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
        //認證成功以后令牌如何生成和存盤?(默認令牌生成UUID.randomUUID(),存盤方式為記憶體)
        .tokenServices(tokenService());

    }
    //系統底層在完成認證以后會呼叫TokenService物件的相關方法
    //獲取TokenStore,基于tokenStore獲取token物件
    @Bean
    public AuthorizationServerTokenServices tokenService(){
      //1.構建TokenService物件(此物件提供了創建,獲取,重繪token的方法)
        DefaultTokenServices tokenServices=new DefaultTokenServices();
      //2.設定令牌生成和存盤策略
        tokenServices.setTokenStore(tokenStore);
      //3.設定是否支持令牌重繪(訪問令牌過期了,是否支持通過令牌重繪機制,延長令牌有效期)
        tokenServices.setSupportRefreshToken(true);
      //4.設定令牌增強(默認令牌會比較簡單,沒有業務資料,
        //就是簡單隨機字串,但現在希望使用jwt方式)
        TokenEnhancerChain tokenEnhancer=new TokenEnhancerChain();
        tokenEnhancer.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
        tokenServices.setTokenEnhancer(tokenEnhancer);
      //5.設定訪問令牌有效期
        tokenServices.setAccessTokenValiditySeconds(3600);//1小時
      //6.設定重繪令牌有效期
        tokenServices.setRefreshTokenValiditySeconds(3600*72);//3天
        return tokenServices;
    }

    /**
     * 假如我們要做認證,我們輸入了用戶名和密碼,然后點提交
     * ,提交到哪里(url-去哪認證),這個路徑是否需要認證?還有令牌過期了,
     * 我們要重新生成一個令牌,哪個路徑可以幫我們重新生成?
     * 如下這個方法就可以提供這個配置
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security)
            throws Exception {
        security
        //1.定義(公開)要認證的url(permitAll()是官方定義好的)
         //公開oauth/token_key端點
        .tokenKeyAccess("permitAll()") //return this
        //2.定義(公開)令牌檢查的url
         //公開oauth/check_token端點
        .checkTokenAccess("permitAll()")
        //3.允許客戶端直接通過表單方式提交認證
        .allowFormAuthenticationForClients();
    }

    /**
     * 認證中心是否要給所有的客戶端發令牌呢?假如不是,那要給哪些客戶端
     * 發令牌,是否在服務端有一些規則的定義呢?
     * 例如:老賴不能做飛機,不能做高鐵
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
         //super.configure(clients);
          clients.inMemory()
                //定義客戶端的id(客戶端提交用戶資訊進行認證時需要這個id)
               .withClient("gateway-client")
                //定義客戶端密鑰(客戶端提交用戶資訊時需要攜帶這個密鑰)
               .secret(passwordEncoder.encode("123456"))
                //定義作用范圍(所有符合規則的客戶端)
               .scopes("all")
                //允許客戶端基于密碼方式,重繪令牌方式實作認證
               .authorizedGrantTypes("password","refresh_token");
    }
}

啟動postman進行訪問測驗

  • 登陸訪問測驗,例如:
    在這里插入圖片描述

登陸成功以后,會在postman控制臺顯示如下格式資訊,例如:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzU2ODAwMjMsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsic3lzOnJlczpjcmVhdGUiLCJzeXM6cmVzOmxpc3QiLCJzeXM6cmVzOmRlbGV0ZSJdLCJqdGkiOiJjZTRhYWVlOC0wMzFmLTRmZjgtYTBmZS1lMGNkOTNlOGYzNzQiLCJjbGllbnRfaWQiOiJnYXRld2F5LWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.gr3FxM0RdiEbmmHIdLi234kwPHRAFm02xNH9EnqEpbY",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJjZTRhYWVlOC0wMzFmLTRmZjgtYTBmZS1lMGNkOTNlOGYzNzQiLCJleHAiOjE2MzU5MzU2MjMsImF1dGhvcml0aWVzIjpbInN5czpyZXM6Y3JlYXRlIiwic3lzOnJlczpsaXN0Iiwic3lzOnJlczpkZWxldGUiXSwianRpIjoiZjllYjZhOTAtNGQ3MC00OGZhLTgzMzktMmFiZGUwYmJmOTQ5IiwiY2xpZW50X2lkIjoiZ2F0ZXdheS1jbGllbnQifQ.c-MrRMNYtI9C9RnX0LchwJ-gLxeFZscpU2VM97vv-7A",
    "expires_in": 3599,
    "scope": "all",
    "jti": "ce4aaee8-031f-4ff8-a0fe-e0cd93e8f374"
}
  • 檢查token資訊,例如:
    在這里插入圖片描述
    假如,請求訪問ok,在postman控制臺會顯示如下格式資訊,例如:
{
    "user_name": "admin",
    "scope": [
        "all"
    ],
    "active": true,
    "exp": 1635680023,
    "authorities": [
        "sys:res:create",
        "sys:res:list",
        "sys:res:delete"
    ],
    "jti": "ce4aaee8-031f-4ff8-a0fe-e0cd93e8f374",
    "client_id": "gateway-client"
}
  • 重繪令牌應用測驗,例如:
    在這里插入圖片描述
    假如,請求訪問ok,在postman控制臺會顯示如下格式資訊,例如:
{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzU2ODA3NzAsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsic3lzOnJlczpjcmVhdGUiLCJzeXM6cmVzOmxpc3QiLCJzeXM6cmVzOmRlbGV0ZSJdLCJqdGkiOiI5MzIzNzI1MC05NzQxLTQ0MjAtOWI3OS04NGZkODg0MDM4ZTUiLCJjbGllbnRfaWQiOiJnYXRld2F5LWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.6zcw0tuAM0wlBvjBHxzk1JqFLweBU9p6uB720pdwWxs",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI5MzIzNzI1MC05NzQxLTQ0MjAtOWI3OS04NGZkODg0MDM4ZTUiLCJleHAiOjE2MzU5MzU2MjMsImF1dGhvcml0aWVzIjpbInN5czpyZXM6Y3JlYXRlIiwic3lzOnJlczpsaXN0Iiwic3lzOnJlczpkZWxldGUiXSwianRpIjoiZjllYjZhOTAtNGQ3MC00OGZhLTgzMzktMmFiZGUwYmJmOTQ5IiwiY2xpZW50X2lkIjoiZ2F0ZXdheS1jbGllbnQifQ.6KJOryS6j78Edk-8N4MWAIKifyRYbH5MvEO-mHRWW6w",
    "expires_in": 3599,
    "scope": "all",
    "jti": "93237250-9741-4420-9b79-84fd884038e5"
}

資源服務工程設計及實作

業務描述

資源服務工程為一個業務資料工程,此工程中資料在訪問通常情況下是受限訪問,例如有些資源有用戶,都可以方法,有些資源必須認證才可訪問,有些資源認證后,有權限才可以訪問,

業務設計架構

用戶訪問資源時的認證,授權流程設計如下:
在這里插入圖片描述

專案創建及初始化

第一步:創建工程,例如:
在這里插入圖片描述
第二步:初始化pom檔案依賴,例如:

   <dependencies>
        <!--spring boot web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--nacos discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--nacos config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <!--在資源服務器添加此依賴,只做授權,不做認證,添加完此依賴以后,
        在專案中我們要做哪些事情?對受限訪問的資源可以先判斷是否登錄了,
        已經認證用戶還要判斷是否有權限?
        -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

第三步:創建bootstrap.yml組態檔,例如:

server:
  port: 8881
spring:
  application:
    name: sso-resource
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml

第四步:創建啟動類,代碼如下:

package com.jt;

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

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

創建資源Controller物件

package com.jt.controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ResourceController {
    
    @PreAuthorize("hasAuthority('sys:res:create')")
    @GetMapping("/resource/select")
    public String doSelect(){
        return "Select Resource Ok...";
    }
}

配置令牌決議器物件

package com.jt;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;


/**
 * 在此配置類中配置令牌的生成,存盤策略,驗簽方式(令牌合法性),
 */
@Configuration
public class TokenConfig {

    /**
     * 配置令牌的存盤策略,對于oauth2規范中提供了這樣的幾種策略
     * 1)JdbcTokenStore(這里是要將token存盤到關系型資料庫)
     * 2)RedisTokenStore(這是要將token存盤到redis資料庫-key/value)
     * 3)JwtTokenStore(這里是將產生的token資訊存盤客戶端,并且token
     * 中可以以自包含的形式存盤一些用戶資訊)
     * 4)....
     */
    @Bean
    public TokenStore tokenStore(){
        //這里采用JWT方式生成和存盤令牌資訊
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    /**
     * 配置令牌的創建及驗簽方式
     * 基于此物件創建的令牌資訊會封裝到OAuth2AccessToken型別的物件中
     * 然后再存盤到TokenStore物件,外界需要時,會從tokenStore進行獲取,
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter=
                new JwtAccessTokenConverter();
        //JWT令牌構成:header(簽名演算法,令牌型別),payload(資料部分),Signing(簽名)
        //這里的簽名可以簡單理解為加密,加密時會使用header中演算法以及我們自己提供的密鑰,
        //這里加密的目的是為了防止令牌被篡改,(這里密鑰要保管好,要存盤在服務端)
        jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//設定密鑰
        return jwtAccessTokenConverter;
    }

    /**
     * JWT 令牌簽名時使用的密鑰(可以理解為鹽值加密中的鹽)
     * 1)生成的令牌需要這個密鑰進行簽名
     * 2)獲取的令牌需要使用這個密鑰進行驗簽(校驗令牌合法性,是否被篡改過)
     */
    private static final String SIGNING_KEY="auth";
}

配置資源認證授權規則

package com.jt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

/**
 * 思考?對于一個系統而言,它資源的訪問權限你是如何進行分類設計的
 * 1)不需要登錄就可以訪問(例如12306查票)
 * 2)登錄以后才能訪問(例如12306的購票)
 * 3)登錄以后沒有權限也不能訪問(例如會員等級不夠不讓執行一些相關操作)
 */
@Configuration
@EnableResourceServer
//啟動方法上的權限控制,需要授權才可訪問的方法上添加@PreAuthorize等相關注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        //1.關閉跨域攻擊
        http.csrf().disable();
        //2.放行相關請求
        http.authorizeRequests()
                .antMatchers("/resource/**")
                .authenticated()
                .anyRequest().permitAll();
    }
}

啟動Postman進行訪問測驗

  • 不攜帶令牌訪問,例如:
    在這里插入圖片描述
  • 攜帶令牌訪問,例如:
    在這里插入圖片描述

網關工程設計及實作

業務描述

本次設計中,API網關是服務訪問入口,身份認證,資源訪問都通過網關進行資源統一轉發,

專案創建及初始化

第一步:創建專案,例如:
在這里插入圖片描述
第二步:初始化pom檔案內容,例如:

 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--假如網關層面進行限流,添加如下依賴-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
        </dependency>
    </dependencies>

第三步:創建bootstrap.yml組態檔并進行路由定義,例如:

server:
  port: 9000
spring:
  application:
    name: sso-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml
    sentinel:
      transport:
        dashboard: localhost:8180
      eager: true
    gateway:
      routes:
        - id: router01
          uri: lb://sso-resource
          predicates:
            - Path=/sso/resource/**
          filters:
            - StripPrefix=1
        - id: router02
          uri: lb://sso-auth
          predicates:
            - Path=/sso/oauth/**
          filters:
            - StripPrefix=1
      globalcors: #跨域配置(寫到組態檔的好處是可以將其配置寫到配置中心)
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"
            allowCredentials: true

第四步:定義啟動類,例如:

package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}

啟動postman進行訪問測驗

  • 基于網關進行登陸訪問測驗,例如:
    在這里插入圖片描述

  • 基于網關進行資源訪問測驗,例如:
    在這里插入圖片描述
    在這里插入圖片描述

客戶端UI工程設計及實作

業務描述

本次專案設計采用前后端分離架構設計,前端工程服務基于springboot web服務進行實作,

專案創建及初始化

第一步:創建專案,例如:
在這里插入圖片描述

第二步:啟動類,例如:

package com.jt;

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

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

創建UI工程登陸頁面

第一步:在resource目錄下創建static目錄
第二步:在static目錄下創建登陸頁面login.html,例如:

<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>login</title>
</head>
<body>
<div class="container"id="app">
    <h3>Please Login</h3>
    <form>
        <div class="mb-3">
            <label for="usernameId" class="form-label">Username</label>
            <input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
        </div>
        <div class="mb-3">
            <label for="passwordId" class="form-label">Password</label>
            <input type="password" v-model="password" class="form-control" id="passwordId">
        </div>
        <button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
    </form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    var vm=new Vue({
        el:"#app",//定義監控點,vue底層會基于此監控點在記憶體中構建dom樹
        data:{ //此物件中定義頁面上要操作的資料
            username:"",
            password:""
        },
        methods: {//此位置定義所有業務事件處理函式
            doLogin() {
                //1.定義url
                let url = "http://localhost:9000/sso/oauth/token"
                //2.定義引數
                let params = new URLSearchParams()
                params.append('username',this.username);
                params.append('password',this.password);
                params.append('client_id',"gateway-client");
                params.append('client_secret',"123456");
                params.append('grant_type',"password");
                //3.發送異步請求
                axios.post(url, params)
                    .then((response) => {//ok
                         alert("login ok")
                         let result=response.data;
                         console.log("result",result);
                         //將回傳的訪問令牌存盤到瀏覽器本地物件中
                         localStorage.setItem("accessToken",result.access_token);
                         location.href="/resource.html";
                         //啟動一個定時器,一個小時以后,向認證中心發送重繪令牌
                     })
                    .catch((e)=>{
                        console.log(e);
                    })

            }
        }
    });
</script>
</body>
</html>

第三步:打開瀏覽器進行訪問測驗,例如:

在這里插入圖片描述

創建資源展現頁面

第一步:在UI工程的static目錄下創建resource.html,例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   <div>
       <h1>The Resource Page</h1>
       <button onclick="doSelect()">我的資源(例如我的訂單)</button>
   </div>
   <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
   <script>
       function doSelect(){
           let url="http://localhost:9000/sso/resource/select";
           //獲取登錄后,存盤到瀏覽器客戶端的訪問令牌
           let token=localStorage.getItem("accessToken");
           //發送請求時,攜帶訪問令牌
           axios.get(url,{headers:{"Authorization":"Bearer "+token}})
               .then(function (response){
                   alert("select ok")
                   console.log(response.data);
               })
               .catch(function (e){//失敗時執行catch代碼塊
                   if(e.response.status==401){
                       alert("請先登錄");
                       location.href="/login.html";
                   }else if(e.response.status==403){
                       alert("您沒有權限")
                   }
                   console.log("error",e);
               })
       }
   </script>
</body>
</html>

第二步:打開瀏覽器進行訪問測驗(登陸前和登陸后檢查點擊我的資源按鈕看看是什么效果),例如:
在這里插入圖片描述
說明,可以用admin和user賬戶分別登陸后,訪問我的資源,看看有什么不同,

技術摘要應用實踐說明

背景分析

企業中資料是最重要的資源,對于這些資料而言,有些可以直接匿名訪問,有些只能登錄以后才能訪問,還有一些你登錄成功以后,權限不夠也不能訪問.總之這些規則都是保護系統資源不被破壞的一種手段.幾乎每個系統中都需要這樣的措施對資料(資源)進行保護.我們通常會通過軟體技術對這樣業務進行具體的設計和實作.早期沒有統一的標準,每個系統都有自己獨立的設計實作,但是對于這個業務又是一個共性,后續市場上就基于共性做了具體的落地實作,例如Spring Security,Apache shiro,JWT,Oauth2等技術誕生了.

Spring Security 技術

Spring Security 是一個企業級安全框架,由spring官方推出,它對軟體系統中的認證,授權,加密等功能進行封裝,并在springboot技術推出以后,配置方面做了很大的簡化.現在市場上分布式架構中的安全控制,正在逐步的轉向Spring Security,Spring Security 在企業中實作認證和授權業務時,底層構建了大量的過濾器,如圖所示:
在這里插入圖片描述
其中:
圖中綠色部分為認證過濾器,黃色部分為授權過濾器,Spring Security就是通過這些過濾器然后呼叫相關物件一起完成認證和授權操作.

Jwt 資料規范

JWT(JSON WEB Token)是一個標準,采用資料自包含方式進行json格式資料設計,實作各方安全的資訊傳輸,其官方網址為:https://jwt.io/,官方JWT規范定義,它構成有三部分,分別為Header(頭部),Payload(負載),Signature(簽名),其格式如下:

xxxxx.yyyyy.zzzzz

Header部分

Header 部分是一個 JSON 物件,描述 JWT 的元資料,通常是下面的樣子,

{
  "alg": "HS256",
  "typ": "JWT"
}

上面代碼中,alg屬性表示簽名的演算法(algorithm),默認是 HMAC SHA256(簡寫HS256);typ屬性表示這個令牌(token)的型別(type),JWT 令牌統一寫為JWT,最后,將這個 JSON 物件使用 Base64URL 演算法(詳見后文)轉成字串,

Payload部分

Payload 部分也是一個 JSON 物件,用來存放實際需要傳遞的資料,JWT規范中規定了7個官方欄位,供選用(了解),

  • iss (issuer):簽發人
  • exp (expiration time):過期時間
  • sub (subject):主題
  • aud (audience):受眾
  • nbf (Not Before):生效時間
  • iat (Issued At):簽發時間
  • jti (JWT ID):編號
    除了官方欄位,你還可以在這個部分定義私有欄位,下面就是一個例子,
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

注意,JWT 默認是不加密的,任何人都可以讀到,所以不要把秘密資訊放在這個部分,

這個 JSON 物件也要使用 Base64URL 演算法轉成字串,

Signature部分

Signature 部分是對前兩部分的簽名,其目的是防止資料被篡改,

首先,需要指定一個密鑰(secret),這個密鑰只有服務器才知道,不能泄露給用戶,然后,使用 Header 里面指定的簽名演算法(默認是 HMAC SHA256),按照下面的公式產生簽名,

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出簽名以后,把 Header、Payload、Signature 三個部分拼成一個字串,每個部分之間用"點"(.)分隔,就可以回傳給用戶,

Oauth2規范

oauth2定義了一種認證授權協議,一種規范,此規范中定義了四種型別的角色:
1)資源有者(User)
2)認證授權服務器(jt-auth)
3)資源服務器(jt-resource)
4)客戶端應用(jt-ui)
同時,在這種協議中規定了認證授權時的幾種模式:
1)密碼模式 (基于用戶名和密碼進行認證)
2)授權碼模式(就是我們說的三方認證:QQ,微信,微博,,,,,)
3)…

總結(Summary)

重難點分析

  • 單點登陸系統的設計架構(微服務架構)
  • 服務的設計及劃分(資源服務器,認證服務器,網關服務器,客戶端服務)
  • 認證及資源訪問的流程(資源訪問時要先認證再訪問)
  • 認證和授權時的一些關鍵技術(Spring Security,Jwt,Oauth2)

FAQ 分析

  • 為什么要單點登陸(分布式系統,再訪問不同服務資源時,不要總是要登陸,進而改善用戶體驗)
  • 單點登陸解決方案?(市場常用兩種: spring security+jwt+oauth2,spring securit+redis+oauth2)
  • Spring Security 是什么?(spring框架中的一個安全默認,實作了認證和授權操作)
  • JWT是什么?(一種令牌格式,一種令牌規范,通過對JSON資料采用一定的編碼,加密進行令牌設計)
  • OAuth2是什么?(一種認證和授權規范,定義了單點登陸中服務的劃分方式,認證的相關型別)

Bug 分析

  • 401 : 訪問資源時沒有認證,
  • 403 : 訪問資源時沒有權限,
  • 404:訪問的資源找不到(一定要檢查你訪問資源的url)
  • 405: 請求方式不匹配(客戶端請求方式是GET,服務端處理請求是Post就是這個問題)
  • 500: 不看后臺無法解決?(error,warn)

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/344217.html

標籤:java

上一篇:Java與Python的區別告訴你,學什么看自己

下一篇:linux內核fs/buffer.c中的函式名稱“brelse”代表什么?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more