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

news2024/11/23 21:09:57

前言

注意:我本地没有生成公钥和私钥,所以每次启动项目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}
     */
    @Override
    public 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);
        // clientPrincipal
        Authentication 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
    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserDetailsService();
    }

    // 密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
//        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
        return new BCryptPasswordEncoder();
    }

    /**
     * 管理客户端
     * @return /
     */
    @Bean
    public 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 /
     */
    @Bean
    public 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 配置
     *
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
                                                                         RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 签名实例
     * 此处注意,目前重启项目就会导致之前已存在的token失效,需要改为固定私钥和公钥,生成到项目目录下
     * @return /
     */
    @Bean
    public 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 实例
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource){
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }


    // 启动时生成的 with 密钥的实例,用于创建上述内容。java.security.KeyPairJWKSource
    private 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 /
     */
    @Bean
    public 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 /
     */
    @Bean
    public 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授权服务器
    @Bean
    public 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 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public 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;

    /**
     * 注册客户端
     */
    @PostMapping
    public ResponseEntity<Boolean> registeredClientRepository(@RequestBody RegisteredOauth2Client registeredClient) {
        // @formatter:off
        RegisteredClient 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 db
        registeredClientRepository.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:off
		JwtClaimsSet.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:on

        return 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-live
        Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);

        // @formatter:off
		JwtClaimsSet.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:on

        return 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.coloradmin.cn/o/796394.html

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

相关文章

ATA-2021B高压放大器经典应用合集(内附技术指标)

多年来Aigtek安泰电子一直潜心于电子测试仪器的研发&#xff0c;已拥有完善的功率放大器产品线&#xff0c;并针对超声声学、无损检测、电磁驱动、生物医疗、微流控、材料测试等主流各行业领域测试建立的了专属测试方案&#xff0c;在国内功率放大器行业及市场中获得认可&#…

【智能可视化---01】揭示Python AI中Matplotlib的魅力,提升数据洞察力!探索AI 中的Matplotlib,这一篇就够了!

Success keeps eluding me yet my passion abides. 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌟[2] 2022年度博客之星人工智能领域TOP4🌟 🏅[3] 阿里云社区特邀专家博主🏅 🏆[4] CSDN-人工智能领域优质创作者…

团队运营能力不足?「企业学院」3大板块全面提升运营技能

『矩阵通』本期上线「企业学院」功能&#xff0c;内容包含新媒体培训课程、行业动态资讯及新媒体营销、矩阵运营的干货指南等&#xff0c;帮助企业提高团队运营能力&#xff0c;对此功能感兴趣的客户可前往矩阵通官网&#xff08;matrix.newrank.cn&#xff09;体验。 在当今数…

太绝了!医疗行业管理,原来可以这么简单!

在现代医疗领域&#xff0c;科技的不断进步和创新为医疗设备的发展带来了前所未有的突破和提升。然而&#xff0c;这些先进的医疗设备往往对其运行环境的要求非常严格&#xff0c;尤其是温度和湿度等参数的稳定性。 因此&#xff0c;为了确保医疗设备的高效、可靠运行&#xff…

【学会动态规划】删除并获得点数(13)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

百度智能云连拿四年第一,为什么要深耕AI公有云市场

AI是过去几年云计算市场中的最大变量&#xff0c;而大模型的成熟&#xff0c;毫无疑问将指数级增强这个变量。 记得在2022年年底&#xff0c;生成式AI与大模型开始爆火的时候&#xff0c;我们就曾讨论过一个问题&#xff1a;这轮AI浪潮中&#xff0c;最先受到深刻影响的将是云计…

利用spss进行多元逐步回归

1.首先用代码将变量归一化 我这第5列及后是我要归一化的数据&#xff0c;将归一化后的数据保存为nor_result.csv文件 import pandas as pd from sklearn.preprocessing import MinMaxScalerdf pd.read_csv(E:\\Sentinel12\\yangben\\result.csv) cols_to_normalize df.colu…

vue3 + TS + VueUse 快捷存取操作cookie,存储用户token流程

1.依赖安装 VueUse可以为Vue开发提供方便和高效的支持&#xff0c;提高开发效率和代码质量。 VueUse网址&#xff1a;https://vueuse.org/integrations/useCookies/#usecookies 依赖1(主)&#xff1a;npm i vueuse/integrations 依赖2(模块)&#xff1a;npm i universal-cooki…

PCB设计中常见的走线等长要求

1、在做 PCB 设计时&#xff0c;为了满足某一组所有信号线的总长度满足在一个公差范围内&#xff0c;通常要使用蛇形走线将总长度较短的信号线绕到与组内最长的信号线长度公差范围内&#xff0c;这个用蛇形走线绕长信号线的处理过程&#xff0c;就是我们俗称的 PCB 信号等长处理…

GPT一键化身「AI助理」——自定义指令功能

最近GPT又更新了一个超实用的功能——自定义指令&#xff0c;启用后&#xff0c;你可以给GPT设置一些固定指令&#xff0c;让它记住或扮演某个角色&#xff0c;比如客服、律师、投资管理师、老师、营养师...... 这样&#xff0c;我们就不再需要每次都要打开新的聊天&#xff0c…

AT15透明屏有哪些特点?

AT15透明屏是一种新型的显示技术&#xff0c;它采用了透明材料制成的屏幕&#xff0c;可以实现透明显示效果。这种屏幕可以广泛应用于各种领域&#xff0c;如商业广告、展览展示、智能家居等。 AT15透明屏的特点之一是其高透明度。 由于采用了透明材料制成&#xff0c;AT15透明…

生成式人工智能新政策发布:AI进入规范时代!

最近&#xff0c;国家发布了《生成式人工智能服务管理暂行办法》。该条例于8月15日开始施行&#xff0c;使得AI应用进入规范化管理。该法明确了适用于提供生成文本、图片、音频、视频等内容的服务&#xff0c;鼓励人工智能技术在各行业、各领域的创新应用&#xff0c;生成积极健…

深度学习:常用优化器Optimizer简介

深度学习&#xff1a;常用优化器Optimizer简介 随机梯度下降SGD带动量的随机梯度下降SGD-MomentumSGDWAdamAdamW 随机梯度下降SGD 梯度下降算法是使权重参数沿着整个训练集的梯度方向下降&#xff0c;但往往深度学习的训练集规模很大&#xff0c;计算整个训练集的梯度需要很大…

【C++】 函数模板和类模板

文章目录 一、模板1.1 函数模板和类模板1.2 函数模板1.2.1 普通函数和函数模板区别1.2.2 普通函数和函数模板调用规则1.2.3 模板局限性 1.3 类模板1.3.1 类模板对象做函数参数1.3.2 类模板的继承1.3.3 类模板成员函数的类外实现1.3.4 类模板分文件编写1.3.5 类模板全局函数类内…

PaddleOCR C++编译出错解决方案

文章目录 前言一、环境准备1、主要环境2、源码下载3、C推理库下载 二、报错信息1.静态库调用错误2.ld returned 1 exit status 总结 前言 最近&#xff0c;想尝试下PaddleOCR的C推理&#xff0c;但是过程不如人所愿&#xff0c;除了很多问题&#xff0c;这里捡重点的说下吧&…

【Python 实战】---- 批量识别图片中的文字,存入excel中【使用百度的通用文字识别】

分析 1. 获取信息图片示例 2. 运行实例 3. 运行结果 4. 各个文件的位置 实现 1. 需求分析 识别图片中的文字【采用百度的通用文字识别】;文字筛选,按照分类获取对应的文本;采用 openpyxl 实现将数据存入 excel 中。2. 获取 access_token 获取本地缓存的

双虚拟机实现数据库自动备份

FTP的使用&#xff1a; 1.安装FTP 1、检测系统有没有安装ftp&#xff0c;执行命令&#xff1a; rpm -qa | grep ftp若存在用rpm命令移除后再行安装&#xff0c;执行命令&#xff1a; rpm -e vsftpd-3.0.2-9.e17.x86_642、如果没有安装&#xff0c;则在线安装ftp&#xff0c…

[深度学习实战]基于PyTorch的深度学习实战(补充篇)[RNN和LSTM基本原理、PyTorch中的LSTM、Embedding层]

目录 一、前言二、RNN和LSTM基本原理2.1 长期依赖问题2.2 LSTM 网络2.3 LSTM 的核心思想2.4 逐步理解 LSTM2.5 LSTM 的变体2.5.1 coupled 忘记门和输入门2.5.2 GRU 三、PyTorch中的LSTM四、Embedding层五、后记 PyTorch——开源的Python机器学习库 一、前言 写这部分的文章很耗…

汽配企业如何把MES管理系统的价值利用到最大化

随着信息技术的快速发展&#xff0c;越来越多的汽配企业开始引入MES生产管理系统&#xff0c;以提高生产效率、优化资源利用和提升产品质量。然而&#xff0c;要想实现MES系统的最大化价值&#xff0c;汽配企业需要从以下几个方面入手。 首先&#xff0c;汽配企业应该充分了解M…

Android性能优化之游戏 OutOfMemoryError: pthread_create探究真相

近期&#xff0c;着手分析游戏的OOM问题&#xff0c;该问题在bugly上的量级&#xff0c;恐怖吓人的百万级&#xff0c;处于java 异常的top 1, 如下所示&#xff1a; 发生的设备&#xff0c;基本上都是32位的cpu架构 分析过程 先来看下报错的堆栈&#xff0c;基本上都是发生…