文章目錄
- 單點登陸系統簡介
- 背景分析
- 單點登陸系統概述
- 單點登陸系統解決方案設計
- 單點登陸系統初步設計
- 服務設計
- 工程結構設計
- 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
