基于OAuth2+SpringSecurity+Jwt實現身份認證和權限管理后端服務

1、簡介

????????本文講述了如何實現簡易的后端鑒權服務。所謂“鑒權”,就是“身份鑒定”+“權限判斷”。涉及的技術有:OAuth2、SpringSecurity、Jwt、過濾器、攔截器。OAuth2用于授權,使用Jwt簽發Access Token和Refresh Token,并管理token的過期時間以及刷新校驗token。SpringSecurity用于認證,會拿著輸入的用戶名和密碼去數據庫中比對,如果比對成功則調用OAuth2取授權簽發token。Jwt則被用于生成token,jwt會根據用戶信息進行base64編碼,并對編碼后的字符串進行加密。過濾器則是用在網關,目的是把那些沒有認證過的請求,即沒有攜帶token或者攜帶的token不合法的請求過濾掉,使那些請求不會打到后端其他服務上去。攔截器的作用是在網關身份認證后,請求會被轉發到具體的各個后端服務上,如果請求的發起者沒有訪問接口的權限,那么請求就會被攔截掉。

2、相關技術介紹

2.1、OAuth2

????????OAuth2是一種授權框架,可以實現第三方授權。OAuth2一共有4種授權模式:

????????(1)客戶端模式:客戶端直接向驗證服務器請求一個token,獲得token后,客戶端攜帶著這個token就能訪問相應的其他服務了。不過這種模式下沒法進行身份驗證。通常適用于服務內部之間調用。類似于feign調用這種。

????????(2)密碼模式:客戶端提供用戶名密碼給驗證服務器,用戶名和密碼驗證通過后,驗證服務器返回給token,客戶端再攜帶著token去訪問其他服務。不過這種模式容易把用戶名密碼泄露給客戶端。比如,你在網站登錄頁面輸入用戶名和密碼,那么你的用戶名和密碼就有可能泄露給登錄頁面。有些釣魚網站就會以欺騙登錄頁面的方式獲取到用戶的用戶名和密碼。因此使用這種模式需確保客戶端是可信的。

????????(3)隱式授權模式:用戶訪問某個頁面時,如果該用戶尚未被身份認證,頁面就會重定向到認證服務器,認證服務器會給用戶一個認證頁面,用戶在上面輸入用戶名和密碼完成身份認證后,認證服務器就會返回token。用戶就可以拿著token去訪問其他服務了。隱式授權模式通常會用于實現sso單點登錄。不過該方式會暴露token給用戶。

????????(4)授權碼模式:這種模式是最安全的一種模式,也是推薦使用的一種,比如我們手機上的很多 App 都是使用的這種模式。相比隱式授權模式,它并不會直接返回 token,而是返回授權碼,真正的 token 是通過應用服務器訪問驗證服務器獲得的。在一開始的時候,應用服務器(客戶端通過訪問自己的應用服務器來進而訪問其他服務)和驗證服務器之間會共享一個 secret,這個東西沒有其他人知道,而驗證服務器在用戶驗證完成之后,會返回一個授權碼,應用服務器最后將授權碼和 secret 一起交給驗證服務器進行驗證,并且 Token 也是在服務端之間傳遞,是存放在應用服務器上的,不會直接給到客戶端。

2.2、SpringSecurity

????????SpringSecurity是一種安全框架,通常是會集成OAuth2一起使用。

????????SpringSecurity+OAuth2協作方式:

????????SpringSecurity可以作為OAuth2授權服務器,驗證用戶身份的合法性,如果身份合法則讓OAuth2簽發token。SpringSecurity框架本身也自帶了一個登錄頁面,并且提供了一個WebSecurityConfigurerAdapter類,可以通過繼承該類并重載configure方法,實現自定的權限攔截。

?????????簡而言之,OAuth2定義了 授權的標準協議,解決“如何安全地允許第三方訪問資源”的問題。Spring Security提供了 實現 OAuth2 和安全控制的工具鏈,包括認證、授權、令牌管理等具體功能。

2.3、JWT

????????JWT(JSON Web Token),是用于生成token的,其原理是將用戶身份信息和聲明,編碼為緊湊的、自包含的字符串,并通過數字簽名保證其完整性和真實性。JWT由Header、Payload、Signature三部分組成:

(1)Header是定義token的元數據,如簽名算法和類型(常用的加密算法有SHA256)。并通過base64對Header數據進行編碼。

(2)Payload是用于存儲用戶身份信息和自定義聲明。會存儲簽發者信息、過期時間、簽發時間等。也是采用base64編碼。

(3)Signature是用于驗證token的完整性和真實性,防止篡改。先對 Header 和 Payload 進行 Base64Url 編碼,然后再使用密鑰(Secret Key)和指定算法(如 HS256、RS256)對編碼后的字符串簽名。

????????而最后生成的token就是將三部分用"."拼接起來。即:token=Header.Payload.Signature

2.4、過濾器

????????過濾器(filter)是java web的核心組件,是用于攔截請求并執行預處理或者后處理邏輯。比較常用的過濾器有Filter和GlobalFilter,Filter是局部過濾器,是java servlet下的組件,僅對特定的路由生效,通常可以在yml里面通過filters關鍵字進行配置。而GlobalFilter是Spring Cloud Gateway下的組件,是全局過濾器。過濾攔截所有的請求。通常是加在網關服務中,可以對發向網關的請求進行全局身份認證、全局限流、日志記錄(記錄所有的請求信息)、統一修改請求的Header等。

2.5、攔截器

????????攔截器(Interceptor)是Spring MVC提供的組件,和過濾器一樣,也是用于攔截請求并執行預處理或者后處理邏輯。繼承HandlerInterceptorAdapter類,preHandle是預處理方法(在請求前執行),postHandle是后處理方法(在請求后執行)。攔截器通常會用在對接口的權限控制。使用preHandle進行請求預處理,沒有權限則攔截。也可以記錄請求情況日志,使用postHandle在請求后記錄日志。

3、代碼實現

【免費】基于OAuth2+SpringSecurity+Jwt實現身份認證和權限管理后端服務代碼合集資源-CSDN文庫

3.1、Eureka注冊中心

????????所有的服務都要向注冊中心注冊,以便于服務發現和服務之間的調用。

????????pom.xml

<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>org.example</groupId><artifactId>eureka-center</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><springframework.version>1.5.4.RELEASE</springframework.version><springframework.version1>1.3.5.RELEASE</springframework.version1></properties><dependencies><!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-eureka-server</artifactId><version>${springframework.version1}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>${springframework.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>${springframework.version}</version><scope>test</scope></dependency></dependencies>
</project>

?????????application.yml

server:port: 8001#Eureka配置
eureka:instance:hostname: localhost  #Eureka服務端的實例名稱client:register-with-eureka: false  #是否向eureka注冊中心注冊自己,因為這里本身就是eureka服務端,所以無需向eureka注冊自己fetch-registry: false #fetch-registry為false,則表示自己為注冊中心service-url:   #監控頁面defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

????????SpringcloudEurekaApplication.java

package eureka;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/*** @author: Wulc* @createTime: 2025-05-02* @description:* @version: 1.0*/
@SpringBootApplication
@EnableEurekaServer  //使eureka服務端可以工作
public class SpringcloudEurekaApplication {public static void main(String[] args) {SpringApplication.run(SpringcloudEurekaApplication.class, args);}
}

3.2、auth-service認證授權中心

????????認證授權中心是用于對用戶進行身份認證,授權可以訪問的范圍,生成token,管理token。

????????auth-service這部分的代碼我是直接用這篇文章里的:OAuth2.0 實現單點登錄_oauth2.0單點登錄-CSDN博客

????????因為密碼要加密存儲,我這里用的是證書加密。

-- 創建數據庫證書用于對密碼進行加密
--查看數據庫中的證書
select * from sys.certificates;
--創建數據庫主密鑰
CREATE MASTER KEY ENCRYPTION BY PASSWORD ='123@#456';--創建證書
CREATE CERTIFICATE MyCert
with SUBJECT = 'Certificate To Password'
GO-- 用戶表
CREATE TABLE UserInfo
(id int primary key identity(1,1),userName varchar(50),pwd varbinary(2000)
);--使用MyCert證書加密pwd字段
insert into UserInfo(userName,pwd) values('zhangsan',ENCRYPTBYCERT(CERT_ID('MyCert'),'123456')
);
insert into UserInfo(userName,pwd) values('lisi',ENCRYPTBYCERT(CERT_ID('MyCert'),'qwerty')
);
insert into UserInfo(userName,pwd) values('wangwu',ENCRYPTBYCERT(CERT_ID('MyCert'),'112233')
);--使用MyCert證書解密pwd字段
select id,userName,CONVERT(
varchar(100),
DecryptByCert(CERT_ID('MyCert'),pwd)
) as pwd from UserInfo;

????????pom.xml

<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>org.example</groupId><artifactId>auth-service</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.6</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId><version>2.2.5.RELEASE</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId><version>3.1.1</version></dependency><dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>mssql-jdbc</artifactId><version>9.4.0.jre8</version></dependency><!--Mybatis-Plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency><!--        Junit4--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.7.11</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.1</version><scope>test</scope></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.2</version> <!-- 對應 Spring Boot 2.7.x --><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
</project>

????????application.yml

server:port: 8002servlet:#為了防止一會在服務之間跳轉導致Cookie打架(因為所有服務地址都是localhost,都會存JSESSIONID)#這里修改一下context-path,這樣保存的Cookie會使用指定的路徑,就不會和其他服務打架了#但是注意之后的請求都得在最前面加上這個路徑context-path: /ssospring:application:name: auth-service-serverdatasource:name: MyTestDataBasedriverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriverurl: jdbc:sqlserver://127.0.0.1:1433;databaseName=MyTestDataBaseusername: wlcpassword: 123456redis:port: 6379database: 0host: 127.0.0.1password:mybatis:mapper-locations: classpath:mapper/*.xml #注意:一定要對應mapper映射xml文件的所在路徑eureka:client:service-url:defaultZone: http://localhost:8001/eureka/  # Eureka注冊中心地址register-with-eureka: truefetch-registry: trueinstance:prefer-ip-address: trueinstance-id: ${spring.application.name}:${server.port}

????????OAuth2Configuration.java

package com.auth.config;import com.auth.service.impl.MyUserDetailsService;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import javax.annotation.Resource;/*** @author: Wulc* @createTime: 2025-05-12* @description:* @version: 1.0*/@EnableAuthorizationServer   //開啟驗證服務器
@Configuration
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {@Resourceprivate MyUserDetailsService myUserDetailsService;@Resourceprivate AuthenticationManager manager;@Resourceprivate TokenStore store;@Resourceprivate JwtAccessTokenConverter converter;private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.tokenServices(serverTokenServices()).userDetailsService(myUserDetailsService).authenticationManager(manager);}/*** 這個方法是對客戶端進行配置,一個驗證服務器可以預設很多個客戶端,* 之后這些指定的客戶端就可以按照下面指定的方式進行驗證* @param clients 客戶端配置工具*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()   // 這里我們直接硬編碼創建,當然也可以像Security那樣自定義或是使用JDBC從數據庫讀取.withClient("web")   // 客戶端ID,隨便起就行.secret(encoder.encode("654321"))      // 只與客戶端分享的secret,隨便寫,但是注意要加密.autoApprove(false)    // 自動審批,這里關閉,要的就是一會體驗那種感覺.scopes("user").authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token");}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) {security.passwordEncoder(encoder)    // 編碼器設定為BCryptPasswordEncoder.allowFormAuthenticationForClients()  // 允許客戶端使用表單驗證,一會我們POST請求中會攜帶表單信息.checkTokenAccess("permitAll()");     // 允許所有的Token查詢請求}/**************************** JWT 配置 **********************************/private AuthorizationServerTokenServices serverTokenServices(){  // 這里對AuthorizationServerTokenServices進行一下配置DefaultTokenServices services = new DefaultTokenServices();services.setSupportRefreshToken(true);   // 允許Token刷新services.setTokenStore(store);   // 添加剛剛的TokenStoreservices.setTokenEnhancer(converter);   // 添加Token增強,其實就是JwtAccessTokenConverter,增強是添加一些自定義的數據到JWT中services.setAccessTokenValiditySeconds(60);    //訪問token有效期20秒services.setRefreshTokenValiditySeconds(120);    //刷新token有效期120秒services.setSupportRefreshToken(true);return services;}
}

?????????SecurityConfiguration.java

package com.auth.config;import com.auth.service.impl.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.redis.RedisTokenStore;/*** @author: Wulc* @createTime: 2025-05-12* @description:* @version: 1.0*/@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowiredprivate MyUserDetailsService myUserDetailsService;@Autowiredprivate RedisConnectionFactory redisConnectionFactory;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//從數據庫中獲取用戶信息auth.userDetailsService(myUserDetailsService).passwordEncoder(encoder);}@Bean   // 這里需要將AuthenticationManager注冊為Bean,在OAuth配置中使用@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Bean@Overridepublic UserDetailsService userDetailsServiceBean() throws Exception {return super.userDetailsServiceBean();}/***************************** JWT配置  ************************************/@Bean("tokenConverter")public JwtAccessTokenConverter tokenConverter(){  // Token轉換器,將其轉換為JWTJwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("wlcKey");   // 這個是對稱密鑰,一會資源服務器那邊也要指定為這個return converter;}//token存放在哪里,放在Redis里面@Beanpublic TokenStore tokenStore(){return new RedisTokenStore(redisConnectionFactory);}
}

?????????UserInfoDTO.java

package com.auth.dto;import lombok.Data;/*** @author: Wulc* @createTime: 2025-05-12* @description:* @version: 1.0*/@Data
public class UserInfoDTO {private Integer id;private String userName;private String pwd;
}

?????????UserMapper.java

package com.auth.mapper;import com.auth.dto.UserInfoDTO;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {UserInfoDTO getUserInfoByUserName(String userName);
}

?????????MyUserDetailsService.java

package com.auth.service.impl;import com.auth.dto.UserInfoDTO;
import com.auth.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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;/*** @author: Wulc* @createTime: 2025-05-12* @description:* @version: 1.0*/@Service
public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;/*** loadUserByUsername** description 從數據庫中根據用戶名獲取用戶信息,并轉為Spring Security的User* @param username* @return* @throws* @author Wulc* @date 2025/5/12 11:01*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {UserInfoDTO userInfoDTO=userMapper.getUserInfoByUserName(username);List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("user");return new User(userInfoDTO.getUserName(), new BCryptPasswordEncoder().encode(userInfoDTO.getPwd()), authorities);}
}

?????????ApplicationStarter.java

package com.auth;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;/*** @author: Wulc* @createTime: 2025-05-12* @description:* @version: 1.0*/@SpringBootApplication
@EnableDiscoveryClient
public class ApplicationStarter {public static void main(String[] args) {SpringApplication.run(ApplicationStarter.class, args);}
}

?????????UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.auth.mapper.UserMapper"><select id="getUserInfoByUserName" resultType="com.auth.dto.UserInfoDTO">SELECTid,userName,CONVERT(varchar(100),DecryptByCert(CERT_ID('MyCert'),pwd)) as pwdFROM UserInfo WHERE userName = #{userName}</select>
</mapper>

????????啟動該服務后:

????????訪問:http://localhost:8002/sso/oauth/token 獲取到token。

????????因為token是存放在redis里面的,可以在redis里面查看到token。

????????訪問:http://localhost:8002/sso/oauth/check_token 可以檢查token是否有效。

????????訪問:http://localhost:8002/sso/oauth/token 可以在access_token過期時,使用refresh_token重新獲取一遍token。這樣子就避免了用戶再次輸入用戶名密碼了。

3.3、action-controller-service權限控制中心

?action-controller-api

????????AccessActionControl.java

package com.action.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author Wulc* @date 2025/5/13 8:55* @description 定義注解用于加在接口方法上進行權限控制*/
@Target({ElementType.METHOD})// 可用在方法名上
@Retention(RetentionPolicy.RUNTIME)// 運行時有效
@Documented
public @interface AccessActionControl {String[] resource() default {};String[] action() default {};
}

????????AccessActionFeign.java

package com.action.feign;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(value = "action-controller-server")
public interface AccessActionFeign {@PostMapping("/api/checkAccessAction")boolean checkAccessAction(@RequestParam("username") String username,@RequestParam("resource") String[] resource,@RequestParam("action") String[] action);
}

?????????pom.xml(action-controller-api)

<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.example</groupId><artifactId>action-controller-service</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>action-controller-api</artifactId><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies><build><plugins><!-- 禁用 Spring Boot 的 Fat JAR 打包 --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><skip>true</skip>  <!-- 關鍵!禁止生成 BOOT-INF --></configuration></plugin><!-- 可選:確保生成的 JAR 包含源碼(方便調試) --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-source-plugin</artifactId><executions><execution><id>attach-sources</id><goals><goal>jar-no-fork</goal></goals></execution></executions></plugin></plugins></build>
</project>

action-controller-server

????????application.yml

server:port: 8004spring:application:name: action-controller-server#eureka配置,服務注冊到哪?
eureka:client:service-url:defaultZone: http://localhost:8001/eureka/instance:#修改eureka上默認描述信息instance-id: ${spring.application.name}:${server.port}

?????????AccessActionController.java

package com.action.controller.feign;import com.action.feign.AccessActionFeign;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author: Wulc* @createTime: 2025-05-13* @description:* @version: 1.0*/@RestController
@RequestMapping("/api")
public class AccessActionController implements AccessActionFeign {@PostMapping("/checkAccessAction")@Overridepublic boolean checkAccessAction(@RequestParam("username") String username,@RequestParam("resource") String[] resource,@RequestParam("action") String[] action) {//這里可以寫你的權限判斷邏輯,通常是根據數據庫中的角色表權限表計算出來的。//我這里作為例子,就直接寫死了if ("zhangsan".equals(username) && "1086".equals(resource[0]) && "read".equals(action[0])) {return true;}return false;}
}

?????????ApplicationStarter.java

package com.action;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;/*** @author: Wulc* @createTime: 2025-05-13* @description:* @version: 1.0*/
@EnableDiscoveryClient
@SpringBootApplication
public class ApplicationStarter {public static void main(String[] args) {SpringApplication.run(ApplicationStarter.class, args);}
}

?????????pom.xml(action-controller-server)

<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.example</groupId><artifactId>action-controller-service</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>action-controller-server</artifactId><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.example</groupId><artifactId>action-controller-api</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>
</project>

?????????pom.xml(action-controller-service)

<?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>org.example</groupId><artifactId>action-controller-service</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><modules><module>action-controller-api</module><module>action-controller-server</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.3</version><relativePath/> <!-- lookup parent from repository --></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies><!--    使用dependencyManagement統一管理SpringCloud組件,集中定義所有SpringCloud相關組件的兼容版本,避免手動指定每個依賴的版本號,--><!--    解決版本沖突問題。我這里使用了2021.0.3,對應的是Springboot2.6.x--><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</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><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><manifest><!-- 指定主類,格式為:包名.類名 --><mainClass>com.action.ApplicationStarter</mainClass></manifest></archive></configuration></plugin></plugins></build><!-- 上傳需要的配置到nexus倉庫 --><!--我這里是把action-controller-api打包成一個jar包上傳到nexus去了,這樣的話,如果要用到action-controller-api就可以直接在pom.xml添加依賴信息,從nexus中下載就行--><distributionManagement>
<!--        <repository>-->
<!--            <id>wulc-nexus</id>-->
<!--            &lt;!&ndash;            正式版&ndash;&gt;-->
<!--            <url>http://192.168.10.104:8081/repository/maven-releases/</url>-->
<!--        </repository>--><snapshotRepository><id>wulc-nexus</id><!--            快照版--><url>http://192.168.10.104:8081/repository/maven-snapshots/</url></snapshotRepository></distributionManagement>
</project>

?

????????關于如果上傳到nexus可以參考我的這篇:使用Nexus搭建遠程maven倉庫_nexus 倉庫教程-CSDN博客

????????當然如果嫌搭建一個Nexus太麻煩的話,可以直接本地對action-controller-api進行maven install,在本地maven倉庫中生成一個jar包。供其他服務需要時直接導入。

?

3.4、provider-server

????????provider-server是被訪問的服務,會引入action-controller-api依賴,在服務的接口上加上@AccessActionControl用于方法級別的權限控制。會寫一個攔截器,用于對所有加了@AccessActionControl注解的接口進行權限判斷預處理。

?????????pom.xml

<?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>org.example</groupId><artifactId>provider-server</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.3</version><relativePath/> <!-- lookup parent from repository --></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>
<!--        引入action-controller-api--><dependency><groupId>org.example</groupId><artifactId>action-controller-api</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies><!--    使用dependencyManagement統一管理SpringCloud組件,集中定義所有SpringCloud相關組件的兼容版本,避免手動指定每個依賴的版本號,--><!--    解決版本沖突問題。我這里使用了2021.0.3,對應的是Springboot2.6.x--><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</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和name可以隨便配置,因為在setting文件中配置過了--><id>wulc-nexus</id><name>wulc-nexus</name><url>http://192.168.10.104:8081/repository/maven-public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository></repositories>
</project>

?????????注:pom.xml中的<repositories><repository>的配置表示直接從192.168.10.104:8081上的nexus倉庫中獲取action-controller-api的jar包。當然你也可以直接引入action-controller-api的jar包。

?????????WebConfiguration.java

package com.provider.config;import com.provider.interceptor.AccessActionInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author: Wulc* @createTime: 2025-05-13* @description:* @version: 1.0*/@Configuration
public class WebConfiguration implements WebMvcConfigurer {@Autowired@Lazy // 延遲注入,避免循環依賴AccessActionInterceptor accessActionInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//攔截所有請求registry.addInterceptor(accessActionInterceptor).addPathPatterns("/**");}
}

?????????ProviderController.java

package com.provider.controller;import com.action.annotation.AccessActionControl;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*** @author: Wulc* @createTime: 2025-05-12* @description:* @version: 1.0*/@RestController
@RequestMapping("/provider")
public class ProviderController {@AccessActionControl(resource = {"1086"}, action = "read")@PostMapping("/getMsg")public String getMsg(){return "訪問到了provider";}
}

?????????AccessActionInterceptor.java

package com.provider.interceptor;import com.action.annotation.AccessActionControl;
import com.action.feign.AccessActionFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;/*** @author: Wulc* @createTime: 2025-05-13* @description:* @version: 1.0*/@Component
public class AccessActionInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate AccessActionFeign accessActionFeign;//在請求被處理之前,調用action-controller的權限判斷接口,如果有權限就放行,沒有權限就攔截@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果不是映射到方法直接通過if (!(handler instanceof HandlerMethod)) {return true;}// ①:START 方法注解級攔截器HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();AccessActionControl accessActionControl=method.getAnnotation(AccessActionControl.class);if(accessActionControl!=null){String username=request.getHeader("username");String[] resource=accessActionControl.resource();String[] action=accessActionControl.action();// accessActionFeign.checkAccessAction(username,resource,action);boolean flag=accessActionFeign.checkAccessAction(username,resource,action);if(!flag){// 設置響應狀態碼(如403 Forbidden)response.setStatus(HttpServletResponse.SC_FORBIDDEN);// 設置響應內容類型(如JSON)response.setContentType("application/json;charset=UTF-8");// 構建響應內容(示例:返回JSON格式錯誤信息)String errorMessage = "{\"code\":403,\"message\":\"權限不足,禁止訪問\"}";// 寫入響應體response.getWriter().write(errorMessage);// 關閉輸出流(重要!)response.getWriter().close();return flag;}}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {super.postHandle(request, response, handler, modelAndView);}
}

????????ApplicationStarter.java ?

package com.provider;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;/*** @author: Wulc* @createTime: 2025-05-01* @description:* @version: 1.0*/
@EnableDiscoveryClient
@EnableFeignClients({"com.action.feign"})
@SpringBootApplication
public class ApplicationStarter {public static void main(String[] args) {SpringApplication.run(ApplicationStarter.class, args);}
}

?????????application.yml

server:port: 8003spring:application:name: provider-server#eureka配置,服務注冊到哪?
eureka:client:service-url:defaultZone: http://localhost:8001/eureka/instance:#修改eureka上默認描述信息instance-id: ${spring.application.name}:${server.port}

3.5、SpringCloud網關

????????網關的作用是進行反向代理,把客戶端的請求轉發到對應的服務端。這里的網關是集成了身份認證服務。客戶端的請求發送到網關,會先經過網關的全局過濾器,在過濾器中先去判斷客戶端的請求中是否有攜帶token?如果攜帶了token,則去redis中驗證該token是否有效?如果token有效則過濾器放行,如果token失效了則使用refresh_token去調用http://localhost:8002/sso/oauth/token接口重新獲取token,獲取到新token后,過濾器再放行。

????????pom.xml ?

<?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>org.example</groupId><artifactId>cloud-gateway</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.3</version><relativePath/> <!-- lookup parent from repository --></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><exclusions><!-- 排除可能引入的Spring MVC依賴 --><!--                Spring Cloud Gateway基于WebFlux響應式框架(非阻塞式),而Spring MVC是傳統的Servlet-based框架(阻塞式)。--><!--                當兩者同時存在于classpath時,Spring Boot無法決定使用哪種Web服務器(Tomcat vs Netty),導致啟動失敗。--><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></exclusion></exclusions></dependency><!--        loadbalancer是負載均衡,對應yml中的lb--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency></dependencies><!--    使用dependencyManagement統一管理SpringCloud組件,集中定義所有SpringCloud相關組件的兼容版本,避免手動指定每個依賴的版本號,--><!--    解決版本沖突問題。我這里使用了2021.0.3,對應的是Springboot2.6.x--><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</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>
</project>

?????????application.yml

server:port: 8000spring:main:web-application-type: reactive  # 強制使用WebFluxapplication:name: cloud-gateway-serviceprofiles:include: route  #使用application-route.yml里面的配置eureka:client:service-url:defaultZone: http://localhost:8001/eureka/  # Eureka注冊中心地址register-with-eureka: truefetch-registry: trueinstance:prefer-ip-address: trueinstance-id: ${spring.application.name}:${server.port}

????????application-route.yml

spring:cloud:gateway:globalcors:cors-configurations:'[/**]':allowed-origin-patterns: '*'  #允許所有的跨域allowed-headers: '*'  #允許所有的頭allowed-methods: '*'  #允許所有的請求方式discovery:locator:enabled: true  # 開啟從注冊中心動態創建路由lower-case-service-id: true  # 服務名小寫routes:- id: route1uri: lb://provider-server  # lb表示負載均衡 loadbalancepredicates: #斷定,遵守哪些規則,就把請求轉發給wulc-test-consumer-server這個服務- Path=/api/provider/**filters:- StripPrefix=1

?????????pom.xml

<?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>org.example</groupId><artifactId>cloud-gateway</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.3</version><relativePath/> <!-- lookup parent from repository --></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><exclusions><!-- 排除可能引入的Spring MVC依賴 --><!--                Spring Cloud Gateway基于WebFlux響應式框架(非阻塞式),而Spring MVC是傳統的Servlet-based框架(阻塞式)。--><!--                當兩者同時存在于classpath時,Spring Boot無法決定使用哪種Web服務器(Tomcat vs Netty),導致啟動失敗。--><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></exclusion></exclusions></dependency><!--        loadbalancer是負載均衡,對應yml中的lb--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency></dependencies><!--    使用dependencyManagement統一管理SpringCloud組件,集中定義所有SpringCloud相關組件的兼容版本,避免手動指定每個依賴的版本號,--><!--    解決版本沖突問題。我這里使用了2021.0.3,對應的是Springboot2.6.x--><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</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>
</project>

?????????RedisConfig.java

package com.gateway.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @author Wulc* @date 2024/4/8 11:39* @description*/@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate1(RedisTemplate redisTemplate) {RedisSerializer stringSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringSerializer);redisTemplate.setStringSerializer(stringSerializer);redisTemplate.setValueSerializer(stringSerializer);redisTemplate.setHashKeySerializer(stringSerializer);redisTemplate.setHashValueSerializer(stringSerializer);return redisTemplate;}
}

?????????GatewayGlobalFilter.java

package com.gateway.filter;import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Map;/*** @author: Wulc* @createTime: 2025-05-12* @description: 過濾器,當請求發送到網關時,先走過濾器進行身份認證,再路由轉發* @version: 1.0*/@Component
public class GatewayGlobalFilter implements GlobalFilter {@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//獲取請求頭HttpHeaders headers = exchange.getRequest().getHeaders();String authorization = headers.getFirst("Authorization");String accessToken = authorization.substring(7);String refreshAccessToken = headers.get("Refresh").get(0);String accessKey = "access" + ":" + accessToken;String refreshAccessKey = "refresh" + ":" + refreshAccessToken;RestTemplate restTemplate = new RestTemplate();//判斷access_token在redis中是否存在if (redisTemplate.opsForValue().get(accessKey) == null) {//如果access_token在redis中不存在,但refresh_access_token存在,則用refresh_access_token自動重新認證一下if (redisTemplate.opsForValue().get(refreshAccessKey) != null) {//構建表頭數據HttpHeaders requestHeaders = new HttpHeaders();String auth = "web" + ":" + "654321";String encodedAuth = Base64Utils.encodeToString(auth.getBytes());requestHeaders.set("Authorization", "Basic " + encodedAuth);// 構建表單數據MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();formData.add("refresh_token", refreshAccessToken);formData.add("grant_type", "refresh_token");// 構建請求實體HttpEntity<MultiValueMap<String, Object>> requestEntity =new HttpEntity<>(formData, requestHeaders);try {ResponseEntity<Map> responseEntity = restTemplate.exchange("http://localhost:8002/sso/oauth/token", HttpMethod.POST, requestEntity, Map.class);return chain.filter(exchange);} catch (Exception ex) {return sendErrorResponse(exchange, HttpStatus.UNAUTHORIZED, "請登錄");}}return sendErrorResponse(exchange, HttpStatus.UNAUTHORIZED, "請登錄");}//繼續后續處理return chain.filter(exchange);}/*** sendErrorResponse* <p>* description //返回錯誤信息** @param* @return* @throws* @author Wulc* @date 2025/5/12 22:30*/private Mono<Void> sendErrorResponse(ServerWebExchange exchange,HttpStatus status,String message) {ServerHttpResponse response = exchange.getResponse();response.setStatusCode(status);response.getHeaders().setContentType(MediaType.TEXT_PLAIN);byte[] bytes = message.getBytes(StandardCharsets.UTF_8);DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);return response.writeWith(Flux.just(buffer));}
}

?????????ApplicationStarter.java

package com.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;/*** @author: Wulc* @createTime: 2025-05-12* @description:* @version: 1.0*/
//gateway服務一定要等其他服務啟動注冊eureka成功后,再最后啟動
@SpringBootApplication
@EnableDiscoveryClient
public class ApplicationStarter {public static void main(String[] args) {SpringApplication.run(ApplicationStarter.class, args);}
}

?????????啟動網關服務:

?????????先調用接口:http://localhost:8002/sso/oauth/token進行身份認證,并獲取token信息。

?????????使用從/oauth/token接口獲取的token訪問網關:http://localhost:8000/api/provider/getMsg 先經過網關的過濾器,根據token判斷用戶是否認證?如果是認證用戶,網關會根據yml里面配置的路由將/api/provider/getMsg請求轉發到相應的后端服務上。

?

????????如果username="wangwu",因為wangwu沒有在action-controller-server中checkAccessAction方法中配置權限,所以wangwu用戶是沒有訪問權限的。會被provider-server的攔截器給攔截掉。 ?

?????????只有當username="zhangsan"時,才有訪問權限。

????????以上就是基于OAuth2+SpringSecurity+Jwt+過濾器+攔截器+注解+feign+網關+Eureka實現的一個簡易身份認證和權限管理系統。

4、總結

? ? ? ? 實現一個鑒權系統其實只要有token+攔截器就行了,身份認證用token,權限控制用攔截器。使用OAuth2框架是為了應對不同的場景,比如隱式授權模式用來實現單點登錄,授權碼模式用于實現第三方登錄,即通過驗證服務器去代理客戶端進行身份驗證,而不是讓客戶端拿著token去身份認證。

? ? ? ? 而Spring Security本身提供了一個登錄頁面,但實際中不會用到。Spring Security提供了完整的鑒權、授權、會話管理、防護攻擊(如CRSF跨站請求偽造、XSS跨站腳本攻擊)。Spring Security默認開啟CRSF Token驗證防護,對所有的請求(Post、Put、Delete)統統要求攜帶有效token。防護XSS攻擊會設置一些內容安全策略,限制外部訪問,白名單黑名單等。

? ? ? ? 其實對于鑒權系統而言,最難反而是根據業務設計一個權限控制模型。常用的權限模型有RBAC和ABAC兩種。RBAC是基于角色的權限模型,角色是權限的集合,通過定義權限組(角色),把權限組授權給用戶。而ABAC是基于屬性的訪問控制,是在RBAC的基礎上更進一步細粒度的控制權限。比如某個用戶有訪問文檔庫的權限,這個可以用角色去授權文檔資源。每個用戶只能訪問自己所屬團隊的文檔,這個要基于團隊屬性進行授權。

? ? ? ? 在實際的授權中,通常會用到這些表:

  • 用戶表:存儲用戶基本信息,用戶名&密碼等,敏感信息要加密處理。
  • 角色表:存儲角色的定義,角色Id,角色名,角色說明等字段。
  • 權限表:存儲系統中各種可被訪問的資源,權限Id,資源Id,資源名稱,資源操作,說明等字段。
  • 角色權限表:角色Id,權限Id。
  • 用戶角色表:用戶Id,角色Id。

????????以上這些是基于角色的訪問控制RBAC,是外部權限。

????????基于屬性的訪問控制ABAC,通常會寫在權限判斷的方法里,定制化更強一些,是內部權限。

????????外部權限+內部權限,RBAC+ABAC共同構成了權限控制。

? ? ? ? 至于實際過程中如何使用?這里舉一個簡單的例子:

@AccessActionControl(resource = {"1086"}, action = "read")這個注解會加在接口方法上,用于對接口方法進行權限控制。會傳入“資源resource”和“動作action”。根據“資源”和“動作”去“權限表”中獲取對應的權限Id,然后根據權限Id在“角色權限表”獲取哪些角色有該權限(記為arryRoles1)。根據用戶Id在“用戶角色表”在查詢該用戶有哪些角色(記為arrRoles2)。最后只要判斷arrRoles2和arryRoles1有沒有交集就行了。如果有交集就說明該用戶有權限,如果沒有就說明該用戶沒有權限。

?

5、參考資料

2、用戶認證和授權嗶哩嗶哩bilibili

springsecurity+jwt+oauth2.0入門到精通視頻教程【免費學習】嗶哩嗶哩bilibili

Spring Cloud 微服務安全:OAuth2 + JWT 實現認證與授權_springcloud oauth2 jwt-CSDN博客

OAuth2.0 實現單點登錄_oauth2.0單點登錄-CSDN博客

Sql Server數據庫實現表中字段的列加密研究_sql實現對密碼字段加密-CSDN博客

Spring Security實現從數據庫中訪問用戶名和密碼實現登錄_spring security5 實現數據庫登錄-CSDN博客

OAuth2.0系列之信息Redis存儲實踐(七) - smileNicky - 博客園

IDEA使用系列之導入外部jar包_idea添加外部jar包-CSDN博客

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/83123.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83123.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/83123.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

<C++> MFC自動關閉對話框(MessageBoxTimeout)

MFC自動關閉對話框&#xff08;MessageBoxTimeout&#xff09; 記錄一下今天在界面開發中的解決方案。自動關閉對話框有兩種方案&#xff1a; 1.使用定時器實現延遲關閉&#xff08;DeepSeek方案&#xff09; 提示框顯示幾秒后自動關閉&#xff0c;可以使用 SetTimer KillT…

多語言支持的常見設計方案

在 Java 項目中實現**多語言&#xff08;國際化&#xff0c;i18n&#xff09;**功能&#xff0c;是很多企業級應用支持不同地區和語言用戶的基礎需求。以下是 Java 中實現多語言支持的常見設計方案&#xff1a; 一、常見多語言設計方案 1. 使用 ResourceBundle 讀取 propertie…

vuex基本介紹

Vuex是Vue.js應用程序中專門用于狀態管理的庫。以下是其基本介紹&#xff1a; 概念 Vuex采用集中式存儲管理應用的所有組件的狀態&#xff0c;并以相應的規則保證狀態以一種可預測的方式發生變化。 特點 - 集中化管理&#xff1a;將應用的狀態集中存儲在一個單一的狀態…

Android開發-在應用之間共享數據

在Android系統中&#xff0c;應用之間的隔離機制&#xff08;沙箱機制&#xff09;保障了系統的安全性與穩定性。然而&#xff0c;在實際開發中&#xff0c;我們經常需要實現跨應用的數據共享&#xff0c;例如&#xff1a; 從一個應用向另一個應用傳遞用戶信息&#xff1b;多個…

深度解析 JWT:從原理到實戰的全場景解決方案(附永久 Token 設計與集成系統實踐)

摘要 本文結合 JWT 官方標準&#xff08;RFC 7519&#xff09;與生產級實踐&#xff0c;全面解析 JSON Web Token 的核心機制、安全規范及 Java 生態最佳實現。涵蓋 JJWT 工具類優化、Auth0/Nimbus 替代方案對比、永久 Token 設計&#xff08;滿足集成系統長期調用需求&#x…

[特殊字符]Meilisearch:AI驅動的現代搜索引擎

前言 大家好&#xff0c;我是MAI麥造&#xff01; 上文介紹一了Manticore Search 這款輕量級的搜索引擎&#xff0c;這次又有了新的發現&#xff01;傳送門&#xff1a; Elasticsearch太重&#xff1f;它的超輕量的替代品找到了&#xff01; 這是一個讓我超級興奮的AI搜索引…

【Linux C/C++開發】輕量級關系型數據庫SQLite開發(包含性能測試代碼)

前言 之前的文件分享過基于內存的STL緩存、環形緩沖區&#xff0c;以及基于文件的隊列緩存mqueue、hash存儲、向量庫annoy存儲&#xff0c;這兩種屬于比較原始且高效的方式。 那么&#xff0c;有沒有高級且高效的方式呢。有的&#xff0c;從數據角度上看&#xff0c;&#xff0…

首個專業AI設計Agent發布-Lovart

Lovart是什么 Lovart 是為設計師打造的世界上首個專業設計 Agent。Lovart 能像專業設計師一樣思考和執行設計任務&#xff0c;提供高水平的設計方案。基于自然語言交互&#xff0c;用戶能快速調整布局、顏色和構圖。Lovart 支持從創意拆解到專業交付的全鏈路設計&#xff0c;單…

關于Python 實現接口安全防護:限流、熔斷降級與認證授權的深度實踐

作為一名IT從業者&#xff0c;就自己的職業經歷&#xff0c;我一直很注重系統安全的。從桌面時代就對此很感興趣&#xff0c;后來隨著技術的更新迭代&#xff0c;系統安全衍生出來了網絡安全。維度更大&#xff0c;范圍更廣。尤其在數字化浪潮席卷全球的今天&#xff0c;互聯網…

onGAU:簡化的生成式 AI UI界面,一個非常簡單的 AI 圖像生成器 UI 界面,使用 Dear PyGui 和 Diffusers 構建。

?一、軟件介紹 文末提供程序和源碼下載 onGAU&#xff1a;簡化的生成式 AI UI界面開源程序&#xff0c;一個非常簡單的 AI 圖像生成器 UI 界面&#xff0c;使用 Dear PyGui 和 Diffusers 構建。 二、Installation 安裝 文末下載后解壓縮 Run install.py with python to setup…

南方科技大學Science! 自由基不對稱催化新突破 | 樂研試劑

近日&#xff0c;南方科技大學劉心元教授團隊聯合浙江大學洪鑫教授團隊在自由基不對稱催化領域取得新進展。課題組開發了一系列大位阻陰離子 N,N,P-配體&#xff0c;用于銅催化未活化外消旋仲烷基碘與亞砜亞胺的不對稱胺化反應。該反應表現出廣泛的底物兼容性&#xff0c;涵蓋具…

Milvus 視角看主流嵌入式模型(Embeddings)

嵌入是一種機器學習概念&#xff0c;用于將數據映射到高維空間&#xff0c;其中語義相似的數據被緊密排列在一起。嵌入模型通常是 BERT 或其他 Transformer 系列的深度神經網絡&#xff0c;它能夠有效地用一系列數字&#xff08;稱為向量&#xff09;來表示文本、圖像和其他數據…

【MySQL】牛客網sql語句簡單例題,sql入門

目錄 一、基礎查詢 1、查詢所有列 2、 查詢多列 二、簡單處理查詢結果 1、查詢結果去重 2、查詢結果限制返回列數 3、將查詢后的列重新命名 三、條件查詢之基礎排序 1、查找后排序 2、 查找后多列排序 3、查找后降序排列 四、條件查詢之基礎操作符 1、查找學校是北…

Linux云計算訓練營筆記day06(Windows DOS下的常用命令 及 HTML)

windows dos命令行 切換盤符 d: 查看文件夾下的內容 dir 創建文件夾 md/mkdir gongli 進入文件夾 cd gongli 往回退一層 cd .. 清屏 cls 歷史命令(用鍵盤的上下鍵) 創建一個空的文件 echo.>a.txt 寫入內容到文件中 echo hello world > b.txt 刪除文件 del a.txt 查…

如何開啟或關閉WordPress的自動更新功能

WordPress是一個開源軟件&#xff0c;您可以從他們的官方網站免費下載。但是&#xff0c;要啟動WordPress站點&#xff0c;您需要安裝一個主題&#xff0c;以幫助為您的內容創建特定布局。此外&#xff0c;您可能還需要安裝一些插件來添加其他功能。 當您必須管理所有這些東西…

SpringSecurity當中的CSRF防范詳解

CSRF防范 什么是CSER 以下是基于 CSRF 攻擊過程的 順序圖 及詳細解釋&#xff0c;結合多個技術文檔中的攻擊流程&#xff1a; CSRF 攻擊順序圖 #mermaid-svg-FqfMBQr8DsGRoY2C {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#m…

給 DBGridEh 增加勾選用的檢查框 CheckBox

需求 Delphi 的 DBGrid 通過 DataSource 綁定到一個 DataSet 顯示數據表里面的 N 多條記錄。如果我想給每條記錄加一個 CheckBox 讓用戶去勾選&#xff0c;該怎么做&#xff1f; 以下描述&#xff0c;使用的 DBGrid 是 DBGrieEh。 Delphi 自帶的 DBGrid 要加 CheckBox 比較麻…

WordPress 和 GPL – 您需要了解的一切

如果您使用 WordPress&#xff0c;GPL 對您來說應該很重要&#xff0c;您也應該了解它。查看有關 WordPress 和 GPL 的最全面指南。 您可能聽說過 GPL&#xff08;通常被稱為 WordPress 的權利法案&#xff09;&#xff0c;但很可能并不完全了解它。這是有道理的–這是一個復雜…

力扣144題:二叉樹的前序遍歷(遞歸)

小學生一枚&#xff0c;自學信奧中&#xff0c;沒參加培訓機構&#xff0c;所以命名不規范、代碼不優美是在所難免的&#xff0c;歡迎指正。 標簽&#xff1a; 二叉樹、前序遍歷、遞歸 語言&#xff1a; C 題目&#xff1a; 給你二叉樹的根節點root&#xff0c;返回它節點值…

python:一個代理流量監控的媒體文件下載腳本

前言 一個mitmproxy代理服務應用&#xff0c;作用是監聽系統流量&#xff0c;并自動下載可能的video媒體文件到本地。 如果你沒有安裝mitmproxy或沒有做完準備工作&#xff0c;請參考我的這篇文章&#xff1a; python&#xff1a;mitmproxy代理服務搭建-CSDN博客 文件架構目錄…