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.mzph.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 避障相机等多传感器进行安全防护。转运器人…

租赁系统|北京租赁系统|租赁软件开发流程

在数字化时代的浪潮下&#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…

【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编程中管理动态分配内存的一种…

【面试干货】猴子吃桃问题

【面试干货】猴子吃桃问题 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 猴子吃桃问题&#xff1a;猴子第一天摘下若干个桃子&#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;本…

Echarts 实现将X轴放在图表顶部并且自动播放展示提示信息内容

文章目录 需求分析效果预览需求 如下图所示,实现柱状图中反转倒着绘制 分析 使用 ECharts 来实现对 Y 轴的倒序排序时,可以通过设置 yAxis 的 inverse 属性为 true 来实现。以下是一个简单的示例,演示了如何使用 ECharts 来创建一个柱状图,并将 Y 轴进行倒序排序:并且…

前缀和算法:提升编程效率的秘密武器(Java版)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能接…

代码审计--一道简单的文件包含题目的多种利用方式

NO.1 传统方法 首先来看下代码 <?php error_reporting(0); if(isset($_GET["file"])){include($_GET["file"]); }else{highlight_file(__FILE__);phpinfo(); } ?>看完代码后再来学习学习函数吧&#xff0c;毕竟菜啊&#xff01;&#xff01;&…

NASA数据集——阿尔法喷气式大气实验甲醛(HCHO)数据

Alpha Jet Atmospheric eXperiment Formaldehyde Data 简介 阿尔法喷气式大气实验甲醛数据 阿尔法喷气式大气实验&#xff08;AJAX&#xff09;是美国国家航空航天局艾姆斯研究中心与 H211, L.L.C. 公司的合作项目&#xff0c;旨在促进对加利福尼亚、内华达和太平洋沿岸地区的…

【NOIP2014普及组复赛】题4:子矩阵

题3&#xff1a;子矩阵 【题目描述】 给出如下定义&#xff1a; 1.子矩阵&#xff1a;从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵&#xff08;保持行与列的相对顺序&#xff09;被称为原矩阵的一个子矩阵。 例如&#xff0c;下面左图中选取第 2 、 4 2、4 2、…