springboot3微服務下結合springsecurity的認證授權實現

1. 簡介

在微服務架構中,系統被拆分成許多小型、獨立的服務,每個服務負責一個功能模塊。這種架構風格帶來了一系列的優勢,如服務的獨立性、彈性、可伸縮性等。然而,它也帶來了一些挑戰,特別是在安全性方面。這時候就體現出認證服務器的重要性,它可以在網關服務器的基礎上做登錄認證,權限認證等功能。本篇文章就以如下結構實現一個demo供大家參考選擇,整體邏輯如下圖所示

  1. 當客戶端第一次發起資源請求(一般前端會處理好邏輯,比如vue中實現未登錄的用戶無法訪問系統資源等)
  2. gateway攔截到請求并檢查請求頭中是否攜帶token,有則放行沒有則無權限無法訪問(返回401)
  3. 客戶端接攔截到響應并解析出當前響應狀態碼是401,則會redirect到登錄頁面(未登錄的用戶請先登錄)gateway攔截到請求后判斷當前是登錄url則放行,轉發到認證服務器進行登錄操作
  4. 根據email / username判斷是否存在數據庫,存在則取出數據對登錄密碼進行加密比對,比對通過則代表成功登錄生成token,并且獲取該用戶所對應角色的權限信息,并將其存在redis中
  5. 用戶端攔截到登錄響應數據,從其中獲取到token和一些用戶信息保存到本地(session,localStorage等)
  6. 登錄后的每次請求發送前都會在請求頭中添加token信息(本次實現鑒權邏輯不寫在認證服務器中,由每個資源服務器引入jar包依賴各自鑒權
  7. 通過@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中統一返回就好了,這里包括了tokentokenExpire(便于前端判斷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 的權限認證模式也是一種普遍的實現方案,對于上面自研認證服務畢竟還是沒有實現高并發場景下的功能,所以有感興趣的讀者可以往這方面繼續專研?

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

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

相關文章

【前端筆記】Vue項目報錯Error: Cannot find module ‘webpack/lib/RuleSet‘

網上搜了下發現原因不止一種&#xff0c;這里僅記錄本人遇到的原因和解決辦法&#xff0c;僅供參考 原因&#xff1a;因為某種原因導致本地package.json中vue/cli與全局vue/cli版本不同導致沖突。再次提示&#xff0c;這是本人遇到的&#xff0c;可能和大家有所不同&#xff0c…

一張圖片中有多個一樣的目標物體,分別進行識別定位分割(Python實現)

需求&#xff1a; 一張圖片中有多個目標物體&#xff0c;將多個目標物體進行識別分割定位 import cv2 import numpy as npdef show_photo(name,picture):cv2.imshow(name,picture)cv2.waitKey(0)cv2.destroyAllWindows()img_path r"test3.png" img cv2.imread(img…

關于微信小程序低功耗藍牙ECharts實時刷新

最近搞了這方面的東西&#xff0c;是剛剛開始接觸微信小程序&#xff0c;因為是剛剛開始接觸藍牙設備&#xff0c;所以這篇文章適合既不熟悉小程序&#xff0c;又不熟悉藍牙的新手看。 項目要求是獲取到藍牙傳輸過來的數據&#xff0c;并顯示成圖表實時顯示&#xff1b; 我看了…

轉運機器人負載最高可達 1000kg,重復精度高達±5mm

轉運機器人&#xff0c;內部搭載ICD系列核心控制器&#xff0c;擁有不同的移載平臺&#xff0c;負載最高可達 1000kg;重復精度高達5mm;支持 Wi-Fi漫游&#xff0c;實現更穩健的網絡數據交互;無軌化激光 SLAM 導航&#xff0c;配合 3D 避障相機等多傳感器進行安全防護。轉運器人…

java中使用jedis連接redis

4.java中使用jedis連接redis

P1-機器學習的核心算法-九五小龐

核心算法 線性回歸算法 線性回歸是一種預測數值型數據的監督學習算法。它的基本思想是通過學習一個線性模型&#xff0c;使得模型能夠盡可能準確地預測實值輸出標記。在單變量線性回歸中&#xff0c;我們有一個特征&#xff08;或輸入變量&#xff09;和一個目標變量&#xf…

租賃系統|北京租賃系統|租賃軟件開發流程

在數字化時代的浪潮下&#xff0c;小程序成為了各行各業爭相探索的新領域。租賃行業亦不例外&#xff0c;租賃小程序的開發不僅提升了用戶體驗&#xff0c;更為商家帶來了更多商業機會。本文將詳細解析租賃小程序的開發流程&#xff0c;為有志于進軍小程序領域的租賃行業從業者…

Kubeblocks系列2-redis嘗試之出師未捷身先死

背景&#xff1a; 上一節&#xff0c;完成了Kubeblocks系列1-安裝。現在就想拿一個簡單的應用測試一下kubeblocks這個所謂的神器是否好用&#xff0c;是否可以應用與生產&#xff01; Kubeblocks系列2-redis嘗試 參照官方文檔&#xff1a;創建并連接到 Redis 集群 確保 Red…

【教程】Linux部署Android安卓模擬器

轉載請注明出處&#xff1a;小鋒學長生活大爆炸[xfxuezhagn.cn] 如果本文幫助到了你&#xff0c;歡迎[點贊、收藏、關注]哦~ 未完成&#xff0c; 先簡單記錄下指令。 docker-android https://github.com/budtmo/docker-android 檢查系統是否支持&#xff1a; sudo apt instal…

41-3 ddos 應急方法

一、常規DDoS應急辦法 定期掃描和清查安全漏洞:定期對網絡主節點進行掃描,及時清理可能存在的安全漏洞,以及新出現的漏洞。 檢查訪問者來源:通過反向路由器查詢的方法檢查訪問者的IP地址是否真實,如果不真實,則予以屏蔽,以防黑客攻擊使用假IP地址方式迷惑用戶。 在骨干節…

【C++】深入解析C++智能指針:從auto_ptr到unique_ptr與shared_ptr

文章目錄 前言&#xff1a;1. 智能指針的使用及原理2. C 98 標準庫中的 auto_ptr:3. C 11 中的智能指針循環引用&#xff1a;shared_ptr 定制刪除器 4. 內存泄漏總結&#xff1a; 前言&#xff1a; 隨著C語言的發展&#xff0c;智能指針作為現代C編程中管理動態分配內存的一種…

汽車液態電池隔膜的作用

標簽: 汽車液態電池隔膜的作用; 聚乙烯(PE);聚丙烯(PP) 問題:汽車液態電池隔膜的作用? 汽車液態電池隔膜的作用 汽車液態電池中的隔膜是一個至關重要的組件,它在電池的性能、安全性和壽命方面起著關鍵作用。下面詳細講述隔膜的主要功能和作用: 1. 電化學隔離 隔…

【面試干貨】猴子吃桃問題

【面試干貨】猴子吃桃問題 1、實現思想2、代碼實現 &#x1f496;The Begin&#x1f496;點點關注&#xff0c;收藏不迷路&#x1f496; 猴子吃桃問題&#xff1a;猴子第一天摘下若干個桃子&#xff0c;當即吃了一半&#xff0c;還不癮&#xff0c;又多吃了一個 二天早上又將剩…

空調濾網拆洗夠不到如何處理

空調濾網拆洗夠不到如何處理 將插口插好&#xff0c;用空調外殼的開合力把濾網懟進去

牛客小白月賽94 解題報告 | 珂學家 | 茴字有36種寫法

前言 很久沒寫題解了&#xff0c;有幸參加了94小白月賽內測&#xff0c;反饋是很nice&#xff0c;AK場。 爭議的焦點在于哪題最難 D題E題(沒有F題)F題(沒有E題) 你選哪題呢&#xff1f; 題解 歡迎關注 珂朵莉 牛客周賽專欄 珂朵莉 牛客小白月賽專欄 A. 小苯的九宮格 思路…

手機相冊的照片徹底刪除了怎么恢復?刪除照片恢復的5種方法

在數字化時代&#xff0c;手機相冊里裝滿了我們的生活點滴和珍貴回憶。然而&#xff0c;一不小心就可能誤刪那些意義非凡的照片。別擔心&#xff0c;今天小編就給大家介紹5種恢復誤刪照片的方法&#xff0c;讓你的回憶不再丟失&#xff01; 方法一&#xff1a;相冊App的“最近刪…

Docker Compose使用

Docker-Compose是什么 docker建議我們每一個容器中只運行一個服務,因為doker容器本身占用資源極少&#xff0c;所以最好是將每個服務單獨分割開來&#xff0c;但是這樣我們又面臨了一個問題&#xff1a; 如果我需要同時部署好多個服務&#xff0c;難道要每個服務單獨寫Docker…

P4097 【模板】李超線段樹 / [HEOI2013] Segment 題解

題意 有一個平面直角坐標系&#xff0c;總共 n n n 個操作&#xff0c;每個操作有兩種&#xff1a; 給定正整數 x 0 , y 0 , x 1 , y 1 x_0,y_0,x_1,y_1 x0?,y0?,x1?,y1? 表示一條線段的兩個端點。你需要在平面上加入這一條線段&#xff0c;第 i i i 條被插入的線段的標…

Photoshop插件(UXP)編寫過程中,如何更新sp-checkbox的選中狀態

?問題說明 sp-checkbox是uxpSpectrum UXP Widgets下的一個小組件&#xff0c;內置樣式大概是這樣&#xff1a; 那么&#xff0c;如果用js動態的改變選中的狀態&#xff0c;應該如何做呢&#xff1f; 如果直接是html來寫&#xff1a; <sp-checkbox checked>Checked<…

特斯拉FSD的「端到端」到底能不能成?

引言 近年來&#xff0c;特斯拉的全自動駕駛&#xff08;Full Self-Driving&#xff0c;FSD&#xff09;技術備受關注&#xff0c;尤其是其「端到端」的AI軟件框架更是引發了廣泛討論。端到端技術到底是一條正確的路徑嗎&#xff1f;它能否真正實現完全自動駕駛&#xff1f;本…