spring-authorization-server (1.1.1)自定义认证

前言

注意:我本地没有生成公钥和私钥,所以每次启动项目jwkSource都会重新生成,导致之前认证的token都会失效,具体如何生成私钥和公钥以及怎么配置到授权服务器中,网上有很多方法自行实现即可

之前有个项目用的0.0.3的,正好最近想研究研究,所以就去了官网看文档研究了一下,1.1.1基于的事security6.x的版本, security6与5.7之前的版本有很大的差别,废话不多说,直接上代码(代码中也有一些注释)

最基础的配置官网都有,这里不去体现,主要体现功能:

  1. 自定义认证和授权
  2. 自定义端点拦截器
  3. 持久化到数据库

版本

依赖项版本
springboot3.1.2
spring-authorization-server1.1.1
jdk17
dynamic-datasource-spring-boot3-starter4.1.2
mybatis-plus3.5.3

sql文件

在这里插入图片描述

代码实操

目录结构

在这里插入图片描述

pom文件

需要额外引入spring security cas包原因是启动时(logging等级:org.springframework.security: trace)会报错:java.lang.ClassNotFoundException:org.springframework.security.cas.jackson2.CasJackson2Module错误。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>demo</description><properties><java.version>17</java.version></properties><dependencyManagement>
<!--        <dependencies>-->
<!--            &lt;!&ndash; SpringBoot的依赖配置&ndash;&gt;-->
<!--            <dependency>-->
<!--                <groupId>org.springframework.boot</groupId>-->
<!--                <artifactId>spring-boot-dependencies</artifactId>-->
<!--                <version>2.5.14</version>-->
<!--                <type>pom</type>-->
<!--                <scope>import</scope>-->
<!--            </dependency>-->
<!--        </dependencies>--></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-authorization-server</artifactId></dependency><!-- 添加spring security cas支持这里需添加spring-security-cas依赖,否则启动时报java.lang.ClassNotFoundException: org.springframework.security.cas.jackson2.CasJackson2Module错误。--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-cas</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.34</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3</version></dependency><!-- 阿里数据库连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency><!-- SpringBoot Web容器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot3-starter</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

授权服务器配置(包名:authenticationServer )

CustomAuthorizationServerConfiguration

import com.example.demo.config.security.provider.WeChatMiniAppAuthenticationProvider;
import com.example.demo.config.security.provider.converter.WeChatMiniAppAuthenticationConverter;
import com.example.demo.utils.OAuth2ConfigurerUtils;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.util.matcher.RequestMatcher;public class CustomAuthorizationServerConfiguration {public static void applyDefaultSecurity(HttpSecurity http, JWKSource<SecurityContext> jwkSource) throws Exception {OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getBean(http,OAuth2AuthorizationService.class);// 认证过滤器链authorizationServerConfigurer.tokenEndpoint(oAuth2TokenEndpointConfigurer -> {oAuth2TokenEndpointConfigurer.accessTokenRequestConverters( customJwtAuthenticationToken -> {customJwtAuthenticationToken.add(new OAuth2AuthorizationCodeAuthenticationConverter());customJwtAuthenticationToken.add(new OAuth2RefreshTokenAuthenticationConverter());customJwtAuthenticationToken.add(new OAuth2ClientCredentialsAuthenticationConverter());customJwtAuthenticationToken.add(new WeChatMiniAppAuthenticationConverter());})// 返回accessToken的后置处理器 https://docs.spring.io/spring-authorization-server/docs/current/reference/html/protocol-endpoints.html#oauth2-token-endpoint
//            .accessTokenResponseHandler()// 异常返回处理器
//            .errorResponseHandler().authenticationProviders((customProviders) -> {// 自定义认证提供者customProviders.add(new WeChatMiniAppAuthenticationProvider(jwkSource,authorizationService));});});// 端点匹配器RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();http.securityMatcher(endpointsMatcher)// .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())// csrf.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)).apply(authorizationServerConfigurer);}}

认证提供者(包名:provider)

预处理(包名: Converter)

WeChatMiniAppAuthenticationConverter

可以说是预处理类转换token信息

import com.example.demo.config.security.provider.token.WeChatMiniAppAuthenticationToken;
import com.example.demo.config.security.provider.type.AuthorizationGrantTypes;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;import java.util.LinkedHashMap;
import java.util.Map;public final class WeChatMiniAppAuthenticationConverter implements AuthenticationConverter {private final Logger logger = LoggerFactory.getLogger(WeChatMiniAppAuthenticationConverter.class);/*** 参数对象** @param request {@link HttpServletRequest}* @return {@link Authentication}*/@Overridepublic Authentication convert(HttpServletRequest request) {String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);if (!AuthorizationGrantTypes.WECHAT_MINIAPP.getValue().equals(grantType)) {return null;}// 获取参数MultiValueMap<String, String> parameters = getParameters(request);logger.info("微信小程序授权入参:{}", parameters);// 微信CODE// 其他参数Map<String, Object> additionalParameters = getOtherParameters(parameters);// clientPrincipalAuthentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();logger.info("小程序授权全部参数:{}", additionalParameters);return new WeChatMiniAppAuthenticationToken(clientPrincipal, additionalParameters, "code","appid", "encryptedData", "ivStr");}/*** 获取其他参数** @param parameters {@link MultiValueMap}* @return {@link Map}*/private Map<String, Object> getOtherParameters(MultiValueMap<String, String> parameters) {Map<String, Object> additionalParameters = new LinkedHashMap<>(16);parameters.forEach((key, value) -> {if (!key.equals(OAuth2ParameterNames.GRANT_TYPE)&& !key.equals(OAuth2ParameterNames.SCOPE)) {additionalParameters.put(key, value.get(0));}});return additionalParameters;}static MultiValueMap<String, String> getParameters(HttpServletRequest request) {Map<String, String[]> parameterMap = request.getParameterMap();MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());parameterMap.forEach((key, values) -> {for (String value : values) {parameters.add(key, value);}});return parameters;}}

自定义token (包名: token)

WeChatMiniAppAuthenticationToken

@Setter
@Getter
public class WeChatMiniAppAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {/*** appId 应用ID*/private final String appId;/*** code 小程序CODE*/private final String code;private final String encryptedData;private final String ivStr;/*** Sub-class constructor.* @param clientPrincipal        the authenticated client principal* @param additionalParameters   the additional parameters* @param code {@link String } 小程序code* @param appId {@link String } 平台appid* @param encryptedData {@link String } encryptedData* @param ivStr {@link String } ivStr*/public WeChatMiniAppAuthenticationToken(Authentication clientPrincipal,Map<String, Object> additionalParameters, String code,String appId, String encryptedData, String ivStr) {super(AuthorizationGrantTypes.WECHAT_MINIAPP, clientPrincipal, additionalParameters);this.code = code;this.appId = appId;this.encryptedData = encryptedData;this.ivStr = ivStr;}
}

定义认证type (包名:type)

AuthorizationGrantTypes

人获取access_token时,会用到grantType

public class AuthorizationGrantTypes {public static final AuthorizationGrantType WECHAT_MINIAPP = new AuthorizationGrantType("wechat_miniapp");}

资源服务器 (包名:resourceServer)

CustomAuthenticationTokenConverter

自定义jwt 预处理器

public class CustomAuthenticationToken implements Converter<Jwt, AbstractAuthenticationToken> {private final Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();private static final String PRINCIPAL_CLAIM_NAME = "data";private final Logger logger = LoggerFactory.getLogger(CustomAuthenticationToken.class);public CustomAuthenticationToken() {}public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {this.logger.info("convert ->jwt:{}", JSONObject.toJSONString(jwt));Collection<GrantedAuthority> authorities = this.extractAuthorities(jwt);// 获取个性化的token信息String principalClaimValue = jwt.getClaimAsString(PRINCIPAL_CLAIM_NAME);if (principalClaimValue == null) {return new JwtAuthenticationToken(jwt, authorities);} else {UserDto user = this.extractUserInfo(jwt);user.setToken(jwt.getTokenValue());CustomJwtAuthenticationToken jwtAuthenticationToken = new CustomJwtAuthenticationToken(jwt, user, authorities);SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);return jwtAuthenticationToken;}}protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {return this.jwtGrantedAuthoritiesConverter.convert(jwt);}protected UserDto extractUserInfo(Jwt jwt) {String principalClaimValue = jwt.getClaimAsString("data");return JSONObject.parseObject(principalClaimValue, UserDto.class);}
}

CustomJwtAuthenticationToken

自定义的jwt

@Transient
public class CustomJwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken<Jwt> {private static final long serialVersionUID = 560L;private final String name;public CustomJwtAuthenticationToken(Jwt jwt, Object principal, Collection<? extends GrantedAuthority> authorities) {super(jwt, principal, "", authorities);this.setAuthenticated(true);this.name = jwt.getSubject();}public Map<String, Object> getTokenAttributes() {return ((Jwt)this.getToken()).getClaims();}public String getName() {return this.name;}
}

CustomOauth2AuthenticationEntryPoint

自定义协议端点

public class CustomOauth2AuthenticationEntryPoint implements AuthenticationEntryPoint {private static final Logger logger = LoggerFactory.getLogger(CustomOauth2AuthenticationEntryPoint.class);private String realmName;public CustomOauth2AuthenticationEntryPoint() {}public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {logger.error(e.getLocalizedMessage(), e);HttpStatus status = HttpStatus.UNAUTHORIZED;Map<String, String> parameters = new LinkedHashMap<>();if (Objects.nonNull(this.realmName)) {parameters.put("realm", this.realmName);}if (e instanceof OAuth2AuthenticationException oAuth2AuthenticationException) {OAuth2Error error = oAuth2AuthenticationException.getError();parameters.put("error", error.getErrorCode());if (StringUtils.hasText(error.getDescription())) {String errorMessage = error.getDescription();parameters.put("error_description", errorMessage);}if (StringUtils.hasText(error.getUri())) {parameters.put("error_uri", error.getUri());}if (error instanceof BearerTokenError bearerTokenError) {if (StringUtils.hasText(bearerTokenError.getScope())) {parameters.put("scope", bearerTokenError.getScope());}status = ((BearerTokenError)error).getHttpStatus();}}ResponseEntity<String> unauthenticated = new ResponseEntity<String>("Unauthenticated", HttpStatusCode.valueOf(status.value()));String message = JSON.toJSONString(unauthenticated);String wwwAuthenticate = WwwAuthenticateHeaderBuilder.computeWwwAuthenticateHeaderValue(parameters);response.addHeader("WWW-Authenticate", wwwAuthenticate);response.setStatus(status.value());response.setContentType("application/json");response.getWriter().write(message);}
}

WwwAuthenticateHeaderBuilder

public final class WwwAuthenticateHeaderBuilder {public WwwAuthenticateHeaderBuilder() {}public static String computeWwwAuthenticateHeaderValue(Map<String, String> parameters) {StringJoiner wwwAuthenticate = new StringJoiner(", ", "Bearer ", "");if (!parameters.isEmpty()) {parameters.forEach((k, v) -> {wwwAuthenticate.add(k + "=\"" + v + "\"");});}return wwwAuthenticate.toString();}
}

Oauth2ResourceServerConfigurer

资源服务器配置

public final class Oauth2ResourceServerConfigurer {public Oauth2ResourceServerConfigurer() {}public static void applyDefaultSecurity(HttpSecurity http) throws Exception {http.oauth2ResourceServer((oauth2ResourceServerConfigurer) ->oauth2ResourceServerConfigurer// 无权限处理器
//                 .accessDeniedHandler()// 自定义协议端点.authenticationEntryPoint(new CustomOauth2AuthenticationEntryPoint()).jwt((jwtConfigurer) -> jwtConfigurer.jwtAuthenticationConverter(new CustomAuthenticationToken())));}
}

SecurityConfig

实现授权及资源服务器

package com.example.demo.config.security;import com.example.demo.config.security.authenticationServer.CustomAuthorizationServerConfiguration;
import com.example.demo.config.security.resourceServer.Oauth2ResourceServerConfigurer;
import com.example.demo.service.CustomUserDetailsService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.jackson2.CoreJackson2Module;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.*;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.web.SecurityFilterChain;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.UUID;/***** @author qb* @since 2023/7/21 15:14* @version 1.0*/
@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig {// 协议端点过滤器链@Bean@Order(1)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {CustomAuthorizationServerConfiguration.applyDefaultSecurity(http,jwkSource());
//        // 开启oidc connect 1.0
//        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());
//        // 重定向到未通过身份验证的登录页面 授权终结点
//        http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(
//                        new LoginUrlAuthenticationEntryPoint("/login"),
//                        new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
//                ))
//                // 授权服务 接受的用户信息和/或客户端注册的访问令牌
//                .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));return http.build();}// 协议认证筛选器@Bean@Order(2)public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {Oauth2ResourceServerConfigurer.applyDefaultSecurity(http);http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth ->auth.requestMatchers("/login","/callback","/oauth2/client/**").permitAll().anyRequest().authenticated());return http.build();}// 自定义认证service@Beanpublic UserDetailsService userDetailsService() {return new CustomUserDetailsService();}// 密码加密@Beanpublic PasswordEncoder passwordEncoder(){
//        return PasswordEncoderFactories.createDelegatingPasswordEncoder();return new BCryptPasswordEncoder();}/*** 管理客户端* @return /*/@Beanpublic RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
//        RegisteredClient oidcClient = defaultClient();// 配置模式
//        JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
//        if (null == jdbcRegisteredClientRepository.findByClientId("client")) {
//            jdbcRegisteredClientRepository.save(oidcClient);
//        }return new JdbcRegisteredClientRepository(jdbcTemplate);}private static RegisteredClient defaultClient() {// 方便测试,先注册一个测试客户端RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("client")// 123456.clientSecret("$2a$10$gJUJo9Ad3wDIhBGVH.8/i.Ox82tSCR4.UkbiDWEDUVQnIzcTMPjKK")// 可以基于 basic 的方式和授权服务器进行认证.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)// 授权码.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)// 刷新token.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)// 客户端模式.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)// 密码模式.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)// 重定向url.redirectUri("http://127.0.0.1:9000/callback").postLogoutRedirectUri("http://127.0.0.1:9000/")// 客户端申请的作用域,也可以理解这个客户端申请访问用户的哪些信息,比如:获取用户信息,获取用户照片等.scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).clientSettings(ClientSettings.builder()// 是否需要用户确认一下客户端需要获取用户的哪些权限// 比如:客户端需要获取用户的 用户信息、用户照片 但是此处用户可以控制只给客户端授权获取 用户信息。.requireAuthorizationConsent(true).build()).tokenSettings(TokenSettings.builder()// accessToken 的有效期.accessTokenTimeToLive(Duration.ofHours(1))// refreshToken 的有效期.refreshTokenTimeToLive(Duration.ofDays(3))// 是否可重用刷新令牌.reuseRefreshTokens(true).build()).build();return oidcClient;}/*** 自定义授权service* @return /*/@Beanpublic OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
//        JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
//        JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
//        ClassLoader classLoader = JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper.class.getClassLoader();
//        ObjectMapper objectMapper = new ObjectMapper();
//        objectMapper.registerModules(new CoreJackson2Module());
//        objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));
//        objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
//        rowMapper.setObjectMapper(objectMapper);
//        authorizationService.setAuthorizationRowMapper(rowMapper);
//        return authorizationService;return  new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);}/*** 自定义确认授权 service 配置**/@Beanpublic OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,RegisteredClientRepository registeredClientRepository) {return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);}/*** 签名实例* 此处注意,目前重启项目就会导致之前已存在的token失效,需要改为固定私钥和公钥,生成到项目目录下* @return /*/@Beanpublic JWKSource<SecurityContext> jwkSource(){var keyPair = generateRsaKeyPair();// 公钥RSAPublicKey publicKey = (RSAPublicKey)  keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();var jwkSet =  new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}// 解析JWKSource访问令牌,构建 JwtDecoder 实例@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource){return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}// 启动时生成的 with 密钥的实例,用于创建上述内容。java.security.KeyPairJWKSourceprivate static KeyPair generateRsaKeyPair(){KeyPair  keyPair ;try {KeyPairGenerator keyPairGenerator  = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();}catch (Exception e) {throw new IllegalStateException(e);}return keyPair;}/*** 自定义jwt信息,全局(自定义jwt实现例外)* @return /*/@Beanpublic OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {return context -> {context.getJwsHeader().header("client-id", context.getRegisteredClient().getClientId());context.getClaims().claim("test","哈哈哈").build();log.info("jwtCustomizer -> getJwsHeader:{}",context.getJwsHeader());log.info("jwtCustomizer -> claim:{}", context.getClaims());// Customize claims};}/***  自定义token属性 预留* @return /*/@Beanpublic OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer() {return context -> {OAuth2TokenClaimsSet.Builder claims = context.getClaims();claims.claim("test200","哈哈哈哈哈").build();log.info("accessTokenCustomizer -> claims:{}",claims);// Customize claims};}// Jwt编码上下文 拓展token
//    @Bean
//    public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
//        return context -> {
//            JwsHeader.Builder headers = context.getJwsHeader();
//            JwtClaimsSet.Builder claims = context.getClaims();
//            headers.header("client-id", context.getRegisteredClient().getClientId());
//            log.info("jwtCustomizer headers:{}",headers);
//            if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
//                // Customize headers/claims for access_token
//
//            } else if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
//                // Customize headers/claims for id_token
//            }
//        };
//    }// 自定义 授权服务器的实例,设置用于配置spring授权服务器@Beanpublic AuthorizationServerSettings authorizationServerSettings() {// 总之server服务,例如:个性化认证及授权相关的路径return AuthorizationServerSettings.builder().build();}// 会话管理
//    @Bean
//    public SessionRegistry sessionRegistry() {
//        return new SessionRegistryImpl();
//    }//    @Bean
//    public HttpSessionEventPublisher httpSessionEventPublisher() {
//        return new HttpSessionEventPublisher();
//    }}

MybatisPlusConfig

@Configuration
public class MybatisPlusConfig {/*** 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}}

controller包

ClientController

注册客户端

@RequestMapping("/oauth2/client")
@RestController
@RequiredArgsConstructor
public class ClientController {private final RegisteredClientRepository registeredClientRepository;private final PasswordEncoder passwordEncoder;/*** 注册客户端*/@PostMappingpublic ResponseEntity<Boolean> registeredClientRepository(@RequestBody RegisteredOauth2Client registeredClient) {// @formatter:offRegisteredClient entity = RegisteredClient.withId(UUID.randomUUID().toString())// ID.clientId(registeredClient.getClientId())// 秘钥.clientSecret(passwordEncoder.encode(registeredClient.getClientSecret()))// POST 请求方法.clientAuthenticationMethods(clientAuthenticationMethods ->clientAuthenticationMethods.addAll(registeredClient.getClientAuthenticationMethods()))// CLIENT_CREDENTIALS.authorizationGrantTypes(authorizationGrantTypes ->authorizationGrantTypes.addAll(registeredClient.getAuthorizationGrantTypes()))// 范围.scopes(strings -> {// 超级strings.addAll(registeredClient.getScopes());}).tokenSettings(registeredClient.getTokenSettings())// 客户端配置.clientSettings(registeredClient.getClientSettings()).build();// Save registered client in dbregisteredClientRepository.save(entity);return  ResponseEntity.ok(true);}}

InfoController

测试 当前认证上下文信息

@RestController
@RequestMapping("/info")
public class InfoController {@GetMapping("/token")public Object token(){return SecurityContextHolder.getContext().getAuthentication().getPrincipal();}}

domain

BaseResponseDto

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BaseResponseDto {private String code;private String message;}

LoginRequest

@Data
public class LoginRequest {private String username;private String password;}

UserDto

@Data
@Accessors(chain = true)
public class UserDto {private String id;private String hosId;private String token;}

RegisteredOauth2Client 重要

这个类可以着重看一下

@Data
@NoArgsConstructor
public class RegisteredOauth2Client implements Serializable {/*** 客户端ID*/private String clientId;/*** 客户端秘钥*/private String clientSecret;/*** 客户端名称*/private String clientName;/*** 权限范围*/private Set<String> scopes;private Instant clientIdIssuedAt;private Instant clientSecretExpiresAt;private Set<ClientAuthenticationMethod> clientAuthenticationMethods;private Set<AuthorizationGrantType> authorizationGrantTypes;private Set<String> redirectUris;private ClientSettings clientSettings = ClientSettings.builder()// 是否需要用户确认一下客户端需要获取用户的哪些权限// 比如:客户端需要获取用户的 用户信息、用户照片 但是此处用户可以控制只给客户端授权获取 用户信息。.requireAuthorizationConsent(true).build();private TokenSettings tokenSettings = TokenSettings.builder()// accessToken 的有效期.accessTokenTimeToLive(Duration.ofHours(1))// refreshToken 的有效期.refreshTokenTimeToLive(Duration.ofDays(3))// 是否可重用刷新令牌.reuseRefreshTokens(true).build();
}

UserEntity

@Data
@TableName("user")
public class UserEntity {@TableId("id_")private Long id;@TableField("username")private String username;@TableField("password")private String password;}

mapper

UserMapper

public interface UserMapper extends BaseMapper<UserEntity> {}

service

IUserService

public interface IUserService extends IService<UserEntity> {
}

UserServiceImpl

@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements IUserService {}

utils

HelperUtils

public class HelperUtils {public static final ObjectWriter JSON_WRITER = new ObjectMapper().writer().withDefaultPrettyPrinter();}

JwtUtils


public final class JwtUtils {private JwtUtils() {}public static JwsHeader.Builder headers() {return JwsHeader.with(SignatureAlgorithm.RS256);}public static JwtClaimsSet.Builder accessTokenClaims(RegisteredClient registeredClient,String issuer, String subject,Set<String> authorizedScopes) {Instant issuedAt = Instant.now();Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());/*** iss (issuer):签发人/发行人* sub (subject):主题* aud (audience):用户* exp (expiration time):过期时间* nbf (Not Before):生效时间,在此之前是无效的* iat (Issued At):签发时间* jti (JWT ID):用于标识该 JWT*/// @formatter:offJwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();if (StringUtils.hasText(issuer)) {claimsBuilder.issuer(issuer);}claimsBuilder.subject(subject).audience(Collections.singletonList(registeredClient.getClientId())).issuedAt(issuedAt).expiresAt(expiresAt).notBefore(issuedAt);if (!CollectionUtils.isEmpty(authorizedScopes)) {claimsBuilder.claim(OAuth2ParameterNames.SCOPE, authorizedScopes);}// @formatter:onreturn claimsBuilder;}public static JwtClaimsSet.Builder idTokenClaims(RegisteredClient registeredClient,String issuer, String subject, String nonce) {Instant issuedAt = Instant.now();// TODO Allow configuration for ID Token time-to-liveInstant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);// @formatter:offJwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();if (StringUtils.hasText(issuer)) {claimsBuilder.issuer(issuer);}claimsBuilder.subject(subject).audience(Collections.singletonList(registeredClient.getClientId())).issuedAt(issuedAt).expiresAt(expiresAt).claim(IdTokenClaimNames.AZP, registeredClient.getClientId());if (StringUtils.hasText(nonce)) {claimsBuilder.claim(IdTokenClaimNames.NONCE, nonce);}// TODO Add 'auth_time' claim// @formatter:onreturn claimsBuilder;}}

OAuth2ConfigurerUtils

/*** 复制security底层的工具类*/
public class  OAuth2ConfigurerUtils {private OAuth2ConfigurerUtils() {}public static RegisteredClientRepository getRegisteredClientRepository(HttpSecurity httpSecurity) {RegisteredClientRepository registeredClientRepository = (RegisteredClientRepository)httpSecurity.getSharedObject(RegisteredClientRepository.class);if (registeredClientRepository == null) {registeredClientRepository = (RegisteredClientRepository)getBean(httpSecurity, RegisteredClientRepository.class);httpSecurity.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);}return registeredClientRepository;}public static OAuth2AuthorizationService getAuthorizationService(HttpSecurity httpSecurity) {OAuth2AuthorizationService authorizationService = (OAuth2AuthorizationService)httpSecurity.getSharedObject(OAuth2AuthorizationService.class);if (authorizationService == null) {authorizationService = (OAuth2AuthorizationService)getOptionalBean(httpSecurity, OAuth2AuthorizationService.class);if (authorizationService == null) {authorizationService = new InMemoryOAuth2AuthorizationService();}httpSecurity.setSharedObject(OAuth2AuthorizationService.class, authorizationService);}return (OAuth2AuthorizationService)authorizationService;}public static OAuth2AuthorizationConsentService getAuthorizationConsentService(HttpSecurity httpSecurity) {OAuth2AuthorizationConsentService authorizationConsentService = (OAuth2AuthorizationConsentService)httpSecurity.getSharedObject(OAuth2AuthorizationConsentService.class);if (authorizationConsentService == null) {authorizationConsentService = (OAuth2AuthorizationConsentService)getOptionalBean(httpSecurity, OAuth2AuthorizationConsentService.class);if (authorizationConsentService == null) {authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService();}httpSecurity.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);}return (OAuth2AuthorizationConsentService)authorizationConsentService;}public static OAuth2TokenGenerator<? extends OAuth2Token> getTokenGenerator(HttpSecurity httpSecurity) {OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = (OAuth2TokenGenerator)httpSecurity.getSharedObject(OAuth2TokenGenerator.class);if (tokenGenerator == null) {tokenGenerator = (OAuth2TokenGenerator)getOptionalBean(httpSecurity, OAuth2TokenGenerator.class);if (tokenGenerator == null) {JwtGenerator jwtGenerator = getJwtGenerator(httpSecurity);OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(httpSecurity);if (accessTokenCustomizer != null) {accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer);}OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();if (jwtGenerator != null) {tokenGenerator = new DelegatingOAuth2TokenGenerator(new OAuth2TokenGenerator[]{jwtGenerator, accessTokenGenerator, refreshTokenGenerator});} else {tokenGenerator = new DelegatingOAuth2TokenGenerator(new OAuth2TokenGenerator[]{accessTokenGenerator, refreshTokenGenerator});}}httpSecurity.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);}return (OAuth2TokenGenerator)tokenGenerator;}private static JwtGenerator getJwtGenerator(HttpSecurity httpSecurity) {JwtGenerator jwtGenerator = (JwtGenerator)httpSecurity.getSharedObject(JwtGenerator.class);if (jwtGenerator == null) {JwtEncoder jwtEncoder = getJwtEncoder(httpSecurity);if (jwtEncoder != null) {jwtGenerator = new JwtGenerator(jwtEncoder);OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(httpSecurity);if (jwtCustomizer != null) {jwtGenerator.setJwtCustomizer(jwtCustomizer);}httpSecurity.setSharedObject(JwtGenerator.class, jwtGenerator);}}return jwtGenerator;}private static JwtEncoder getJwtEncoder(HttpSecurity httpSecurity) {JwtEncoder jwtEncoder = (JwtEncoder)httpSecurity.getSharedObject(JwtEncoder.class);if (jwtEncoder == null) {jwtEncoder = (JwtEncoder)getOptionalBean(httpSecurity, JwtEncoder.class);if (jwtEncoder == null) {JWKSource<SecurityContext> jwkSource = getJwkSource(httpSecurity);if (jwkSource != null) {jwtEncoder = new NimbusJwtEncoder(jwkSource);}}if (jwtEncoder != null) {httpSecurity.setSharedObject(JwtEncoder.class, jwtEncoder);}}return (JwtEncoder)jwtEncoder;}public static JWKSource<SecurityContext> getJwkSource(HttpSecurity httpSecurity) {JWKSource<SecurityContext> jwkSource = (JWKSource)httpSecurity.getSharedObject(JWKSource.class);if (jwkSource == null) {ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, new Class[]{SecurityContext.class});jwkSource = (JWKSource)getOptionalBean(httpSecurity, type);if (jwkSource != null) {httpSecurity.setSharedObject(JWKSource.class, jwkSource);}}return jwkSource;}private static OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(HttpSecurity httpSecurity) {ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, new Class[]{JwtEncodingContext.class});return (OAuth2TokenCustomizer)getOptionalBean(httpSecurity, type);}private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(HttpSecurity httpSecurity) {ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, new Class[]{OAuth2TokenClaimsContext.class});return (OAuth2TokenCustomizer)getOptionalBean(httpSecurity, type);}public static AuthorizationServerSettings getAuthorizationServerSettings(HttpSecurity httpSecurity) {AuthorizationServerSettings authorizationServerSettings = (AuthorizationServerSettings)httpSecurity.getSharedObject(AuthorizationServerSettings.class);if (authorizationServerSettings == null) {authorizationServerSettings = (AuthorizationServerSettings)getBean(httpSecurity, AuthorizationServerSettings.class);httpSecurity.setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings);}return authorizationServerSettings;}public static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) {return ((ApplicationContext)httpSecurity.getSharedObject(ApplicationContext.class)).getBean(type);}public static <T> T getBean(HttpSecurity httpSecurity, ResolvableType type) {ApplicationContext context = (ApplicationContext)httpSecurity.getSharedObject(ApplicationContext.class);String[] names = context.getBeanNamesForType(type);if (names.length == 1) {return (T) context.getBean(names[0]);} else if (names.length > 1) {throw new NoUniqueBeanDefinitionException(type, names);} else {throw new NoSuchBeanDefinitionException(type);}}public static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) {Map<String, T> beansMap = BeanFactoryUtils.beansOfTypeIncludingAncestors((ListableBeanFactory)httpSecurity.getSharedObject(ApplicationContext.class), type);if (beansMap.size() > 1) {int var10003 = beansMap.size();String var10004 = type.getName();throw new NoUniqueBeanDefinitionException(type, var10003, "Expected single matching bean of type '" + var10004 + "' but found " + beansMap.size() + ": " + StringUtils.collectionToCommaDelimitedString(beansMap.keySet()));} else {return !beansMap.isEmpty() ? beansMap.values().iterator().next() : null;}}public static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) {ApplicationContext context = (ApplicationContext)httpSecurity.getSharedObject(ApplicationContext.class);String[] names = context.getBeanNamesForType(type);if (names.length > 1) {throw new NoUniqueBeanDefinitionException(type, names);} else {return names.length == 1 ? (T) context.getBean(names[0]) : null;}}
}

效果截图

注册客户端

{"clientId":"123456",
"clientSecret":"8b30c1482ff973cfc92e51e1ec636966",
"clientName":"test",
"scopes":[
"super"
],
"clientAuthenticationMethods":["client_secret_post"],
"authorizationGrantTypes": ["refresh_token", "client_credentials", "sms_app,wechat_miniapp"]
}

在这里插入图片描述

客户端授权

grant_type:wechat_miniapp
scope:super
client_id:123456
client_secret:8b30c1482ff973cfc92e51e1ec636966
appId:123456789

在这里插入图片描述

不带access_token访问资源

在这里插入图片描述

带access_token访问资源

在这里插入图片描述

数据库截图

客户端

在这里插入图片描述

授权

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/16175.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue(待续)

概念 一套用于构建用户界面的渐进式JavaScript框架 Vue可以自底向上逐层的应用&#xff1a; 简单应用:只需一个轻量小巧的核心库。 复杂应用:可以引入各式各样的Vue插件。 1.采用组件化模式&#xff0c;提高代码复用率、且让代码更好维护。 2.声明式编码&#xff0c;让编码人员…

【设计模式——学习笔记】23种设计模式——装饰器模式Decorator(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 生活案例咖啡厅 咖啡定制案例 装饰者模式介绍介绍出场角色 案例实现案例一&#xff08;咖啡厅问题&#xff09;类图代码实现咖啡样式拓展代码实现 案例二类图代码实现 装饰着模式在IO流源码的应用总结什么是父类和子类的一致性如何让自己和被委托对象有一致性 文章说明…

深度学习和神经网络

人工神经网络分为两个阶段&#xff1a; 1 &#xff1a;接收来自其他n个神经元传递过来的信号&#xff0c;这些输入信号通过与相应的权重进行 加权求和传递给下个阶段。&#xff08;预激活阶段&#xff09; 2&#xff1a;把预激活的加权结果传递给激活函数 sum :加权 f:激活…

【Linux】UDP协议

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录 &#x1f449;传输层&a…

Mysql的锁

加锁的目的 对数据加锁是为了解决事务的隔离性问题&#xff0c;让事务之前相互不影响&#xff0c;每个事务进行操作的时候都必须先加上一把锁&#xff0c;防止其他事务同时操作数据。 事务的属性 &#xff08;ACID&#xff09; 原子性 一致性 隔离性 持久性 事务的隔离级别 锁…

Python入门【__init__ 构造方法和 __new__ 方法、类对象、类属性、类方法、静态方法、内存分析实例对象和类对象创建过程(重要)】(十四)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

[C++] 类与对象(上)

目录 1、前言 2、类的引入 3、类的定义 3.1 类的两种定义方式 4、类的访问限定符 5、类的作用域 6、类的实例化 7、类对象模型 7.1 内存对齐规则 7.1 类对象的存储方式 8、this指针 8.1 this指针的特性 8.2 this指针是否可以为空 1、前言 C语言是面向过程的&#…

DUBBO服务多网卡,服务调用失败

如果服务器是多网卡的&#xff0c;比如安装了docker&#xff0c;有一个docker虚拟网卡&#xff0c;一个实体网卡eth0&#xff0c;当我们运行springboot应用后&#xff0c;dubbo注入到zk的地址是 docker虚拟网卡的地址172网段&#xff0c;而不是实际内网地址192网段&#xff0c;…

类的封装和包(JAVA)

封装 所有的OOP语言都会有三个特征&#xff1a; 封装&#xff1b;继承&#xff1b;多态。 本篇文章会为大家带来有关封装的知识。 在我们日常生活中可以看到电视就只有那么几个按键&#xff08;开关&#xff0c;菜单……&#xff09;和一些接口&#xff0c;而而我们通过这些东…

【计算机视觉|人脸建模】SOFA:基于风格、由单一示例的2D关键点驱动的3D面部动画

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;SOFA: Style-based One-shot 3D Facial Animation Driven by 2D landmarks 链接&#xff1a;SOFA: Style-based One-shot 3D Facial Animation Driven by 2D landmarks | Proceedings of …

jmeter压力测试指标解释

目录 RT(response time) Throughput 吞吐量 并发用户数 QPS (query per seconds) TPS (transition per seconds) PV和UV 聚合报告&#xff1a; RT(response time) 什么是RT? RT就是指系统在接收到请求和做出相应这段时间跨度 但是值得一提的是RT的值越高,并不真的就能…

什么是云原生和 CNCF?

一、CNCF简介 CNCF&#xff1a;全称Cloud Native Computing Foundation&#xff08;云原生计算基金会&#xff09;&#xff0c;成立于 2015 年 12 月 11 日&#xff0c;是一个开源软件基金会&#xff0c;它致力于云原生&#xff08;Cloud Native&#xff09;技术的普及和可持续…

Klipper seria.c 文件代码分析

一. 前言 Klipper 底层硬件的串口模块程序写的是否正确是决定下位机与上位机能否正常通信的前提&#xff0c;如果这个文件的驱动没写好&#xff0c;那上位机控制下位机就无从谈起&#xff0c;更无法通过上位机去验证下位机程序的正确性。 本篇博文将详细解析 Klipper src 文件夹…

809协议

809协议 目录概述需求&#xff1a; 设计思路实现思路分析1.809协议数据流——链路管理类 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,…

在idea中添加try/catch的快捷键

在idea中添加try/catch的快捷键 在idea中添加try/catch的快捷键 ctrlaltt 选中想被try/catch包围的语句&#xff0c;同时按下ctrlaltt&#xff0c; 出现下图 选择try/catch即可。

Elasticsearch搜索引擎系统入门

目录 【认识Elasticsearch】 Elasticsearch主要应用场景 Elasticsearch的版本与升级 【Elastic Stack全家桶】 Logstash Kibana Beats Elasticsearch在日志场景的应用 Elasticsearch与数据库的集成 【安装Elasticsearch】 安装插件 安装Kibana 安装Logstash 【认…

C# 2的幂

231 2的幂 给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方。 示例 1&#xff1a; 输入&#xff1a;n 1 输出&a…

【图论】三种中心性 —— 特征向量、katz 和 PageRank

维基百科&#xff1a;在图论和网络分析中&#xff0c;中心性指标为图中相应网络位置的节点分配排名或数值。中心性这一概念最初起源于社交网络分析&#xff0c;因此很多衡量中心性的术语也反映了其社会学背景。 不同中心性指标对 “重要” 的衡量方式不同&#xff0c;因此适用于…

惊喜!1行Python代码,瞬间测你工作量,分享一个统计代码行数的神器

大家好&#xff0c;这里是程序员晚枫。 **你想不想知道一个项目中&#xff0c;自己写了多少行代码&#xff1f;**我用今天的工具统计了一下开源项目&#xff1a;python-office的代码行数&#xff0c;竟然有21w行&#xff01; 我们一起看一下怎么用最简单的方法&#xff0c;统…

mac下安装vue cli脚手架并搭建一个简易项目

目录 1、确定本电脑下node和npm版本是否为项目所需版本。 2、下载vue脚手架 3、创建项目 1、下载node。 如果有node&#xff0c;打开终端&#xff0c;输入node -v和npm -v , 确保node和npm的版本&#xff0c;(这里可以根据自己的需求去选择&#xff0c;如果对最新版本的内容有…