1. 簡介
在微服務架構中,系統被拆分成許多小型、獨立的服務,每個服務負責一個功能模塊。這種架構風格帶來了一系列的優勢,如服務的獨立性、彈性、可伸縮性等。然而,它也帶來了一些挑戰,特別是在安全性方面。這時候就體現出認證服務器的重要性,它可以在網關服務器的基礎上做登錄認證,權限認證等功能。本篇文章就以如下結構實現一個demo供大家參考選擇,整體邏輯如下圖所示
- 當客戶端第一次發起資源請求(一般前端會處理好邏輯,比如vue中實現未登錄的用戶無法訪問系統資源等)
- gateway攔截到請求并檢查請求頭中是否攜帶token,有則放行沒有則無權限無法訪問(返回401)
- 客戶端接攔截到響應并解析出當前響應狀態碼是401,則會redirect到登錄頁面(未登錄的用戶請先登錄)gateway攔截到請求后判斷當前是登錄url則放行,轉發到認證服務器進行登錄操作
- 根據email / username判斷是否存在數據庫,存在則取出數據對登錄密碼進行加密比對,比對通過則代表成功登錄生成token,并且獲取該用戶所對應角色的權限信息,并將其存在redis中
- 用戶端攔截到登錄響應數據,從其中獲取到token和一些用戶信息保存到本地(session,localStorage等)
- 登錄后的每次請求發送前都會在請求頭中添加token信息(本次實現鑒權邏輯不寫在認證服務器中,由每個資源服務器引入jar包依賴各自鑒權)
- 通過
@PreAuthorize
注解進行判斷當前用戶是否有執行該方法相應的權限,如果有則順利執行方法返回結果,否則無權限返回code401
2. 認證服務器實現
由于本次實現中,認證服務器負責的功能就是登錄(查詢用戶信息,登出) 、 查找權限、對token的簽發、刷新管理,所以該服務器就不考慮集成springsecurity,只需引入mybatis相關依賴和nacos服務注冊發現的
2.1 微服務配置
2.1.1 改pom
<dependencies><!-- nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- 支持負載均衡 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--mybatis和springboot整合--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--Mysql數據庫驅動8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--通用Mapper4--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--SpringBoot集成druid連接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency><!--cloud_commons_utils--><dependency><groupId>com.simple.cloud</groupId><artifactId>simpleCloud_api_commons</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>
2.1.2 application.yml 配置
server:port: 10001spring:application:name: auth-servercloud:nacos:discovery:server-addr: localhost:8848data:redis:host: localhostport: 6379database: 0timeout: 1800000password:jedis:pool:max-active: 20 #最大連接數max-wait: -1 #最大阻塞等待時間(負數表示沒限制)max-idle: 5 #最大空閑min-idle: 0 #最小空閑datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver # 用戶登錄表所在的數據庫url: jdbc:mysql://localhost:3306/seata_system?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: abc123# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.simple.cloud.entitiesconfiguration:map-underscore-to-camel-case: true
2.1.3 主啟動
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.simple.cloud.mapper")
public class AuthMain10001 {public static void main(String[] args) {SpringApplication.run(AuthMain10001.class , args);}
}
2.2 需求功能實現
跟springsecurity的邏輯一樣,我們需要提供一個加密器和一個UserDetailService并定義findByUsername
方法,話不多說下面就跟我一起一一實現吧
2.2.1 utils工具類
2.2.1.1 SHA-256 加密器
在選擇加密或哈希算法時,更推薦使用SHA-256或SHA-3這兩個方法都屬于SHA(安全散列算法)系列,它們提供了比MD5更強的安全性。SHA-256生成的是256位的哈希值,而SHA-3是最新的成員,提供了與SHA-2類似的安全性,但采用了不同的算法設計。這些算法在生成數字簽名和驗證數據完整性方面被廣泛使用。本篇教程基于SHA-256實現密碼加密
當然如果作者想基于對稱加密是西安,AES(高級加密標準)是目前推薦的算法。感興趣的讀者可以去了解了解😁
public class SHA_256Helper {public static String encrypt(String password) {try {// 獲取SHA-256 MessageDigest實例MessageDigest digest = MessageDigest.getInstance("SHA-256");// 將輸入字符串轉換為字節數組byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));// 將字節數組轉換為十六進制字符串StringBuilder hexString = new StringBuilder();for (byte b : hash) {String hex = Integer.toHexString(0xff & b);if (hex.length() == 1) {hexString.append('0');}hexString.append(hex);}return hexString.toString();} catch (NoSuchAlgorithmException e) {throw new RuntimeException("SHA-256 encoded fail!!+" + e);}}
}
2.2.1.2 JWTUtiles
JWT(JSON Web Token)是一種開放標準,它允許在兩方之間安全地傳輸信息。由于JWT是經過數字簽名的,因此它的內容不僅可以被校驗,而且可以被信任。這使得JWT成為在用戶登錄場景中存儲用戶登錄狀態、過期時間等信息的理想選擇。
將權限信息存儲在JWT中的做法通常包括以下步驟:
- 編碼權限信息:在生成JWT時,可以將用戶的權限信息作為有效載荷的一部分進行編碼。這些信息可以是角色、權限級別或其他與用戶相關的訪問控制數據。
- 傳輸token:當用戶登錄成功并獲得了JWT后,前端會在后續的請求中攜帶這個JWT。這樣,后端就可以通過解析JWT來驗證用戶的權限信息。
- 解析和驗證:后端接收到含有JWT的請求時,會首先對JWT進行解碼和驗證。驗證成功后,就可以從JWT的有效載荷中讀取出用戶的權限信息,并根據這些信息來判斷用戶是否有權訪問請求的資源或執行操作。
需要注意的是,盡管可以將這些信息放入JWT,但也要考慮安全性問題。例如,不應將敏感信息放入JWT的有效載荷中,因為有效載荷是可以被解碼的。此外,應該設置合理的過期時間,并在必要時提供刷新機制,以便在不重新進行完整身份驗證的情況下更新令牌。
public class JWTHelper {private static long tokenExpiration = 20 * 60 * 1000; // 20min過期private static long tokenRefreshExpiration = 12 * 60 * 60 * 1000; // 12小時過期private static String tokenSignKey = "31c78b41f"; //密鑰private static String buildToken(Long userId, String email, List<String> permission , long timeToLive){return Jwts.builder().setSubject("AUTH-USER").setExpiration(new Date(System.currentTimeMillis() + timeToLive)).claim("userId", userId).claim("email", email).claim("permission", permission).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();}public static String[] createToken(Long userId, String email, List<String> permission) {String token = buildToken(userId,email,permission,tokenExpiration);//token過期時可以刷新長期tokenString refreshToken = buildToken(userId,email,permission,tokenRefreshExpiration);return new String[]{token , refreshToken};}// 原始token過期時刷新token 而refreshToken保持不變(如果refresh都過期則需重新登錄)public static String refresh(String refreshToken){return buildToken(SecurityAccessConstant.TOKEN_TYPE, getUserId(refreshToken) ,getEmail(refreshToken), getPermission(refreshToken) , tokenExpiration);}// 去掉前綴public static String getToken(String token){if(token == null)return null;if(token.startsWith(SecurityAccessConstant.TOKEN_PREFIX))return token.replace(SecurityAccessConstant.TOKEN_PREFIX,"");//沒帶前綴的認為是無效tokenreturn null;}// 獲取當前token過期時間public static Date getExpirationDate(String token) {if(StringUtil.isBlank(token))return null;Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();return claims.getExpiration();}//判斷當前token是否過期public static boolean isOutDate(String token){try {Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Date expirationDate = claimsJws.getBody().getExpiration();return expirationDate.before(new Date());} catch (JwtException e) {// JWT token無效或已損壞return true;}}public static Long getUserId(String token) {try {if (token == null || token == "") return null;Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();Integer userId = (Integer) claims.get("userId");return userId.longValue();} catch (Exception e) {e.printStackTrace();return null;}}public static String getEmail(String token) {try {if (token == null || token == "") return "";Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return (String) claims.get("email");} catch (Exception e) {e.printStackTrace();return null;}}public static List<String> getPermission(String token) {try {if (token == null || token == "") return null;Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return (List<String>) claims.get("permission");} catch (Exception e) {e.printStackTrace();return null;}}
}
對于對token無感刷新感興趣的讀者可以查閱 對token無感刷新的理解,里面提到了具體的實現邏輯和編碼過程中的一些思考😁希望能對你有所幫助
2.2.1.3 SecurityAccessConstant 定義一些全局常量
往往是一些字符串類型的關鍵字,在這里統一定義外部就可以直接調用,方便微服務之間的管理
public class SecurityAccessConstant {public static String TOKEN_PREFIX = "Bearer ";public static String HEADER_NAME_TOKEN = "Authorization";public static String TOKEN_TYPE = "Short-lived";public static String REFRESH_TOKEN_TYPE = "refresh";public static String WEB_REQUEST_TO_AUTH_URL = "http://127.0.0.1:10001";public static String REQUEST_LOGGING_URI = "/simple/cloud/access/login";public static String REQUEST_REFRESH = "/simple/cloud/access/refresh";public static String USERINFO_REDIS_STORAGE_KEY = "_INFO_dbh9";public static String REFRESH_TOKEN_REDIS_STORAGE_KEY = "_REFRESH_s9k1";
}
2.2.1.4 ResponseUtil
在后面就可以看到,springboot3響應式編程里的filter使用的是ServerWebExchange,所以這里就會對該ServerWebExchange實例修改其響應返回而不繼續執行后面的邏輯 其中響應修改的內容有響應狀態碼StatusCode
和可能攜帶的響應信息RespondBody
具體封裝響應體的寫法可以看return處(使用writeWith封裝)
public class ResponseUtils {public static Mono<Void> out(ServerWebExchange exchange, ResultData r){// 將ResultData對象轉換為JSON字符串,并設置為響應體ObjectMapper objectMapper = new ObjectMapper();byte[] responseBody = new byte[0];try {responseBody = objectMapper.writeValueAsBytes(r);} catch (JsonProcessingException e) {e.printStackTrace();}exchange.getResponse().setStatusCode(HttpStatus.NETWORK_AUTHENTICATION_REQUIRED);exchange.getResponse().getHeaders().add("Content-Type", "application/json");return exchange.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(responseBody)));}/*** 使用WebClient異步訪問 localhost:10001/auth/login 為例子* @param url http://localhost:10001 前綴* @param uri /auth/login 后邊的路徑名稱* @param key,value 請求頭中的鍵值對* @return*/public static Mono<ResultData> webClientRequest(String url , String uri , String key , String value){WebClient webClient = WebClient.create(url);Mono<ResultData> response = webClient.get().uri(uri).header(key , value).retrieve().bodyToMono(ResultData.class);return response;}
}
2.3 主體功能實現
該controller即是本次認證服務器實現的所以方法:
- login: 登錄方法,接收一個LoginVo 類型的登錄數據(其中包含了email和password),首先根據郵箱去數據庫找是否有該用戶,如果有則繼續對密碼加密然后比對,當比對成功時則會查詢該用戶所有的權限信息本次實現的權限是通過與meau表集成,即根據type判斷是權限還是菜單如下圖(這部分根據自己的需求來自定義,拆開也可以)
之后將所有需要返回的數據放入map中統一返回就好了,這里包括了
token
、tokenExpire
(便于前端判斷token是否過期動態刷新)和refreshToken
(用于短token刷新的憑證) - refresh : 即是上面使用refreshToken 來刷新token的實現方法,注意這里返回的結果為新下發的token和其過期時間
- info : 該方法用戶獲取用戶信息(這里就偷懶了從redis取出直接返回,想做更細化功能的讀者可以在此基礎上擴展)
- logout : 退出登錄接口,用于提醒服務器刪掉保存的一些信息(比如redis或者消息隊列中的)防止信息泄露
@RestController
@RequestMapping("/simple/cloud/access")
public class AuthController {@Resourceprivate SysUserService sysUserService;@Resourceprivate SysMenuService sysMenuService;@Resourceprivate RedisTemplate redisTemplate;/*** 登錄* @return*/@PostMapping("/login")public ResultData login(@RequestBody LoginVo loginVo) throws Exception{// 先根據email找指定用戶SysUser sysUser = sysUserService.findUserByEmail(loginVo.getEmail());if(sysUser == null)throw new Exception("找不到該用戶");//加密密碼來比較String encryptValue = SHA_256Helper.encrypt(loginVo.getPassword());if(!StringUtils.pathEquals(encryptValue,sysUser.getPassword()))throw new Exception("密碼錯誤");System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+encryptValue);//獲取用戶的角色List<SysRole> sysRoles = sysUserService.selectAllByUserId(sysUser.getId());sysUser.setRoleList(sysRoles);//根據id獲取所有菜單列表List<RouterVo> routerList = sysMenuService.getAllRouterListByUserId(sysUser.getId());//根據id獲取所有按鈕列表List<String> permsList = sysMenuService.getAllMenuListByUserId(sysUser.getId());//map中插入相應的值Map<String, Object> map = new HashMap<>();map.put("routers",routerList);map.put("buttons",permsList);map.put("roles",sysUser.getRoleList());map.put("name",sysUser.getName());//存放token到請求頭中String[] tokenArray = JWTHelper.createToken(sysUser.getId(), sysUser.getEmail(), permsList);map.put("token",tokenArray[0]);map.put("tokenExpire",JWTHelper.getExpirationDate(tokenArray[0]).getTime());map.put("refreshToken",tokenArray[1]);// 存放用戶信息權限數據redisTemplate.opsForValue().set(sysUser.getId() + SecurityAccessConstant.USERINFO_REDIS_STORAGE_KEY, new ObjectMapper().writeValueAsString(map), 30*60, TimeUnit.SECONDS);// 存放refreshTokenredisTemplate.opsForValue().set(tokenArray[0], tokenArray[1], JWTHelper.getExpirationDate(tokenArray[1]).getTime() , TimeUnit.MILLISECONDS);return ResultData.success(map);}@GetMapping("/refresh")public ResultData refresh(HttpServletRequest request){String refreshToken = JWTHelper.getToken(request.getHeader(SecurityAccessConstant.HEADER_NAME_TOKEN));//刷新tokenString refresh = JWTHelper.refresh(refreshToken);Map<String, Object> map = new HashMap<>();map.put("token",refresh);map.put("expire",JWTHelper.getExpirationDate(refresh).getTime());return ResultData.success(map);}/*** 獲取用戶信息* @return*/@PostMapping("/info")public ResultData info(HttpServletRequest request) throws JsonProcessingException {String token = JWTHelper.getToken(request.getHeader(SecurityAccessConstant.HEADER_NAME_TOKEN));Long userId = JWTHelper.getUserId(token);if(userId == null)return ResultData.fail(ResultCodeEnum.RC401.getCode(), "token失效請重新登錄");// 存放權限信息到redis中 , springsecurity通過 userId 做為key獲取權限列表String storageJSON = (String) redisTemplate.opsForValue().get(userId + SecurityAccessConstant.USERINFO_REDIS_STORAGE_KEY);if(null == storageJSON)return ResultData.fail(ResultCodeEnum.RC401.getCode(), "登錄失敗請重新登錄");return ResultData.success(new ObjectMapper().readValue(storageJSON , Map.class));}/*** 退出* @return*/@PostMapping("/logout")public ResultData logout(HttpServletRequest request){String token = JWTHelper.getToken(request.getHeader(SecurityAccessConstant.HEADER_NAME_TOKEN));if(token == null)return ResultData.success("token失效 以退出登錄");Long userId = JWTHelper.getUserId(token);if(userId == null)return ResultData.success("token失效 以退出登錄");redisTemplate.delete(userId + SecurityAccessConstant.USERINFO_REDIS_STORAGE_KEY);redisTemplate.delete(userId + SecurityAccessConstant.REFRESH_TOKEN_REDIS_STORAGE_KEY);return ResultData.success("退出成功");}
}
對于其中service、mapper方法這里就不在細講,因為不同需求實現的邏輯都不相同,所以提供一個controller給各位讀者參考(其實根據方法名可以直到該方法做什么的👍)
到此認證服務器的基本功能就實現完了,總結一下主要做的就是登錄認證,token的下發、刷新和維護(對于這塊感興趣的讀者可以去查閱另一篇文章 對token無感刷新的理解)
3. 鑒權功能依賴集成
在本次的實戰中,我使用在每個微服務引入自定義的鑒權jar包的方法實現鑒權(該鑒權功能通過springsecurity實現)具體架構如下,只需要在需要鑒權的微服務pom.xml中引入該adapter即可
3.1 pom依賴引入
主要就是springsecurity的依賴,另外common是實現的一些工具類jar包,就如2.2.1 所講述的那些
<dependencies><!-- Spring Security依賴 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--cloud_commons_utils--><dependency><groupId>com.simple.cloud</groupId><artifactId>simpleCloud_api_commons</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
3.2 過濾器實現
核心就是獲取到封裝權限信息的UsernamePasswordAuthenticationToken ,方法定義如下圖所示,其中第二個credentials放的是密碼等,但是為了防止泄露在登錄成功后會將其設置為null
@Order(1)
public class TokenAuthenticationFilter extends OncePerRequestFilter {public TokenAuthenticationFilter() {}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {logger.info("uri:"+request.getRequestURI());//獲取包含權限的authentication UsernamePasswordAuthenticationToken authentication = getAuthentication(request);if(null != authentication) {SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(request, response);} else {ResponseUtil.out(response, ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));}}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {//請求頭是否有tokenString token = JWTHelper.getToken(request.getHeader(SecurityAccessConstant.HEADER_NAME_TOKEN));if(null != token) {String email = JWTHelper.getEmail(token);Long userId = JWTHelper.getUserId(token);List<String> permission = JWTHelper.getPermission(token);if(null != permission) {//當前用戶信息放到ThreadLocal里面LoginUserInfoHelper.setUserId(userId);LoginUserInfoHelper.setEmail(email);//把權限數據轉換要求集合類型 List<SimpleGrantedAuthority>List<SimpleGrantedAuthority> collect = permission.stream().map(val -> new SimpleGrantedAuthority(val)).collect(Collectors.toList());return new UsernamePasswordAuthenticationToken(email, null, collect);}}return null;}
}
3.3 springsecurity 配置類
注意在springboot3中集成的springsecurity已經淘汰掉繼承 WebSecurityConfigurerAdapter 的方法,鼓勵開發者自己寫配置類將bean注入容器中
由于不需要做登錄認證,只需要做權限校驗,所以 不需要引入登錄相關的filter(userdetailService等那些都不用引入)需要引入的只有前面定義的TokenAuthenticationFilter 和 PasswordEncoder (其實這個也不需要因為并沒有在邏輯中用到,但是加上也不妨礙)然后就是根據自己的業務需求配置SecurityFilterChain 就好啦
@Configuration
@EnableWebSecurity //@EnableWebSecurity是開啟SpringSecurity的默認行為
@EnableMethodSecurity //啟用方法級別鑒權
public class WebSecurityConfig{@Beanpublic PasswordEncoder passwordEncoder() {return new CustomSHA_256PasswordEncoder();}@Beanpublic TokenAuthenticationFilter authenticationJwtTokenFilter() {return new TokenAuthenticationFilter();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 禁用basic明文驗證.httpBasic().disable()// 前后端分離架構不需要csrf保護.csrf().disable()// 禁用默認登錄頁.formLogin().disable()// 禁用默認登出頁.logout().disable()// 前后端分離是無狀態的,不需要session了,直接禁用。.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests// 允許所有OPTIONS請求.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 允許 SpringMVC 的默認錯誤地址匿名訪問.requestMatchers("/error").permitAll()// 允許任意請求被已登錄用戶訪問,不檢查Authority.anyRequest().authenticated())// 加我們自定義的過濾器,替代UsernamePasswordAuthenticationFilter.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);return http.build();}/*** 需要調用AuthenticationManager.authenticate執行一次校驗** @param config* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}
}
以上就是全部編碼,在完成后可以打成jar包供其他微服務引入依賴
3.4 使用鑒權
在需要鑒權的微服務中引入依賴
<!-- security -->
<dependency><groupId>com.simple.cloud</groupId><artifactId>simpleCloud_security_adapter</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
然后在指定的方法上用springsecurity提供的方法進行權限控制就好啦,如下例子
注意注解的使用,在內是 hasAuthority(權限名/類別) ,為了該注解能在方法級別起效必需在springsecurity的配置類上標注注解
@EnableMethodSecurity
啟用方法級別鑒權
/*** 獲取所有用戶列表* */
@GetMapping("/listAll")
@PreAuthorize("hasAuthority('bnt.sysUser.list')")
public ResultData getAllUser(){List<SysUser> sysUsers = sysUserService.findAllUsers();if(sysUsers != null)return ResultData.success(sysUsers);return ResultData.fail(ResultCodeEnum.RC996.getCode(), "查詢失敗,請聯系管理員");
}
4. 總結
基于這種登錄認證和權限認證分離的方式設計有好有壞,對于好處而言:
-
集中式認證管理:通過統一的認證服務器進行登錄認證和token的簽發刷新,可以簡化認證流程,提高安全性和效率。
-
靈活性和可擴展性:各個微服務自行處理權限認證,可以根據各自的業務需求靈活設計權限控制邏輯,便于擴展和維護。
-
適應多種鑒權場景:這種方式可以適應外部應用接入、用戶-服務鑒權、服務-服務鑒權等多種鑒權場景。
同時也會帶來一些壞處:
- 潛在的安全風險:如果各個微服務的權限認證實現不一致或存在缺陷,可能會引入安全風險。
- 性能考慮:每個請求都可能需要經過權限認證,如果沒有合理的優化,可能會對系統性能產生影響。
現在基于OAuth2 的權限認證模式也是一種普遍的實現方案,對于上面自研認證服務畢竟還是沒有實現高并發場景下的功能,所以有感興趣的讀者可以往這方面繼續專研?