自定义实现OAuth2.0 授权码模式

2023-11-13

OAuth2.0 授权码模式 实践

本篇文章不适合作为授权码模式的入门文章来阅读,适合想要自己实现授权码模式或体验授权码模式的开发者阅读

依赖知识

术语

  • Resource Owner-用户
  • Resource Server-负责处理对用户资源的请求
  • Authorization Server-获取用户的授权
  • Scope-对用户资源可操作的范围
  • Client-第三方应用

授权码流程

  1. Resource Server提供Resource,Resource Owner具备对这些Resource的访问权限。

    注意 :请不要简单的把Resource想象成用户的信息、头像。因为在RESTful架构中,每一个URI代表一种Resource,理解这一点至关重要

  2. 当第三方应用Client想要访问Resource Owner的Resource时,需要经过Authorization Server的许可,才可以访问Resource Server上有关Resource Owner的Resource
    在这里插入图片描述

详细流程如下:

  • 当Client想要访问Resource Owner 的Resource时,请求Authorization Server。response_type为code,代表为授权码流程,redirect_uri为用户授权后回调Client的url。

    Authorization Server接收到请求后,返回授权范围Scope,并拉起用户授权页面,请求Resource Owner授权

    https://authorization-server.com/auth
     ?response_type=code
     &client_id=29352915982374239857
     &redirect_uri=https://thirdparty-server/callback
     &state=xcoiv98y2kd22vusuye3kch
    
  • Resource Owner允许授权,并将授权范围Scope一并提交请求到Authorization Server。Authorization Server生成授权码,并设置过期时间,并存储在Authorization Server上。同时回调上一步请求中的redirect_uri对应的地址

    https://thirdparty-server/callback
     ?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
     &state=xcoiv98y2kd22vusuye3kch
    
  • 第三方应用Client收到请求,比较state是否一致(防止CSRF攻击)。如果一致,则再次向Authorization Server发起请求,通过code换取access_token。

    grant_type=authorization_code
    code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
    client_id=client_id
    client_secret=client_secret
    

认证服务器

主要职责是请求Resource Owner授权和下发token

拉起请求用户授权页面

    @GET
    @DenyAll
    public Response applyForUserAuthorization(@Context HttpServletRequest request,
                                      @Context HttpServletResponse response,
                                      @Context UriInfo uriInfo) throws ServletException, IOException {
        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
        String state = params.getFirst("state");
        // TODO: 实际业务中使用其他方式存储
        if (state.length() > 0) {
            stateMap.put(1, state);
        }
        //1. client_id
        String clientId = params.getFirst("client_id");
        if (clientId == null || clientId.isEmpty()) {
            return informUserAboutError(request, response, "Invalid client_id :" + clientId);
        }
        // 判断clientId是否在认证服务器中
        Client client = appDataRepository.getClient(clientId);
        if (client == null) {
            return informUserAboutError(request, response, "Invalid client_id :" + clientId);
        }
        //2. Client Authorized Grant Type
        if (client.getAuthorizedGrantTypes() != null && !client.getAuthorizedGrantTypes().contains("authorization_code")) {
            return informUserAboutError(request, response, "Authorization Grant type, authorization_code, is not allowed for this client :" + clientId);
        }
        //3. redirectUri
        String redirectUri = params.getFirst("redirect_uri");
        if (client.getRedirectUrl() != null && !client.getRedirectUrl().isEmpty()) {
            if (redirectUri != null && redirectUri.isEmpty() && !client.getRedirectUrl().equals(redirectUri)) {
                //sould be in the client.redirectUri
                return informUserAboutError(request, response, "redirect_uri is pre-registred and should match");
            }
            redirectUri = client.getRedirectUrl();
//            params.putSingle("resolved_redirect_uri", redirectUri);
        } else {
            if (redirectUri == null || redirectUri.isEmpty()) {
                return informUserAboutError(request, response, "redirect_uri is not pre-registred and should be provided");
            }
            params.putSingle("resolved_redirect_uri", redirectUri);
        }
        request.setAttribute("client", client);

        //4. response_type
        String responseType = params.getFirst("response_type");
        if (!"code".equals(responseType) && !"token".equals(responseType)) {
            return informUserAboutError(request, response, "invalid_grant :" + responseType + ", response_type params should be code or token:");
        }
        //Save params in session
        request.getSession().setAttribute("ORIGINAL_PARAMS", params);

        //4.scope: Optional
        String requestedScope = request.getParameter("scope");
        if (requestedScope == null || requestedScope.isEmpty()) {
            requestedScope = client.getScopes();
        }
        //5. user principal, common userId
        Principal principal = securityContext.getUserPrincipal();
        User user = appDataRepository.getUser(principal.getName());
        String allowedScopes = checkUserScopes(user.getScopes(), requestedScope);
        request.setAttribute("scopes", allowedScopes);
        // 转发至授权页面
        request.getRequestDispatcher("/authorize.jsp").forward(request, response);
        return null;
    }

用户手动授权

在这里插入图片描述

提交授权、生成code

    @DenyAll
    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public void userAuthorization(@Context HttpServletRequest request,
                                      @Context HttpServletResponse response,
                                      MultivaluedMap<String, String> params) throws ServletException, IOException {
        MultivaluedMap<String, String> originalParams = (MultivaluedMap<String, String>) request.getSession().getAttribute("ORIGINAL_PARAMS");
        if (originalParams == null) {
             informUserAboutError(request, response, "No pending authorization request.");
        }
//        String redirectUri = originalParams.getFirst("resolved_redirect_uri");
        String redirectUri = "http://localhost:8080/thirdparty-server/third/apply/callback";
        StringBuilder sb = new StringBuilder(redirectUri);
        sb.append("?state=").append(stateMap.get(1));
        String approvalStatus = params.getFirst("approval_status");
        if ("NO".equals(approvalStatus)) {
            URI location = UriBuilder.fromUri(sb.toString())
                    .queryParam("error", "User doesn't approved the request.")
                    .queryParam("error_description", "User doesn't approved the request.")
                    .build();
             Response.seeOther(location).build();
        }

        //==> YES
        List<String> approvedScopes = params.get("scope");
        if (approvedScopes == null || approvedScopes.isEmpty()) {
            URI location = UriBuilder.fromUri(sb.toString())
                    .queryParam("error", "User doesn't approved the request.")
                    .queryParam("error_description", "User doesn't approved the request.")
                    .build();
             Response.seeOther(location).build();
        }
        String responseType = originalParams.getFirst("response_type");
        String clientId = originalParams.getFirst("client_id");
        if ("code".equals(responseType)) {
            String userId = securityContext.getUserPrincipal().getName();
            AuthorizationCode authorizationCode = new AuthorizationCode();
            authorizationCode.setCode(RandomString.make(15));
            authorizationCode.setClientId(clientId);
            authorizationCode.setUserId(userId);
            authorizationCode.setApprovedScopes(String.join(" ", approvedScopes));
            authorizationCode.setExpirationDate(LocalDateTime.now().plusMinutes(10));
            authorizationCode.setRedirectUrl(redirectUri);
            appDataRepository.save(authorizationCode);
            String code = authorizationCode.getCode();
            sb.append("&code=").append(code);
        }
        // 回调第三方应用
        response.sendRedirect(sb.toString());
    }

下发Token

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.APPLICATION_JSON)
    @DenyAll
    public Response token(@HeaderParam(HttpHeaders.AUTHORIZATION) String authHeader,
                          MultivaluedMap<String, String> params) {
        //Check grant_type params
        String grantType = params.getFirst("grant_type");
        if (grantType == null || grantType.isEmpty()) {
            return responseError("Invalid_request", "grant_type is required", Response.Status.BAD_REQUEST);
        }
        if (!supportedGrantTypes.contains(grantType)) {
            return responseError("unsupported_grant_type", "grant_type should be one of :" + supportedGrantTypes, Response.Status.BAD_REQUEST);
        }

        //Client Authentication
        String[] clientCredentials = extract(authHeader);
        if (clientCredentials.length != 2) {
            return responseError("Invalid_request", "Bad Credentials client_id/client_secret", Response.Status.BAD_REQUEST);
        }
        String clientId = clientCredentials[0];
        Client client = appDataRepository.getClient(clientId);
        if (client == null) {
            return responseError("Invalid_request", "Invalid client_id", Response.Status.BAD_REQUEST);
        }
        String clientSecret = clientCredentials[1];
        if (!clientSecret.equals(client.getClientSecret())) {
            return responseError("Invalid_request", "Invalid client_secret", Response.Status.UNAUTHORIZED);
        }
        AuthorizationGrantTypeHandler authorizationGrantTypeHandler = authorizationGrantTypeHandlers.select(NamedLiteral.of(grantType)).get();
        TokenVO tokenResponse = null;
        try {
            tokenResponse = authorizationGrantTypeHandler.createAccessToken(clientId, params);
        }catch (Exception ex) {
            log.log(Level.WARNING, "acquire token failed", ex);
        }
        return Response.ok(tokenResponse)
                .header("Cache-Control", "no-store")
                .header("Pragma", "no-cache")
                .build();
    }

这里只给出生成access_token的简单代码

@Named("authorization_code")
public class AuthorizationCodeGrantTypeHandler extends AbstractGrantTypeHandler{

    private EntityManager entityManager = JPAUtil.acquireEntityManager();

    @Inject
    private AppDataRepository appDataRepository;

    @Override
    public TokenVO createAccessToken(String clientId, MultivaluedMap<String, String> params) throws Exception {
        //1. code is required
        String code = params.getFirst("code");
        if (code == null || "".equals(code)) {
            throw new WebApplicationException("invalid_grant");
        }
        AuthorizationCode authorizationCode = entityManager.find(AuthorizationCode.class, code);
        if (!authorizationCode.getExpirationDate().isAfter(LocalDateTime.now())) {
            throw new WebApplicationException("code Expired !");
        }
        String redirectUri = params.getFirst("redirect_uri");
        //redirecturi match
        if (authorizationCode.getRedirectUrl() != null && !authorizationCode.getRedirectUrl().equals(redirectUri)) {
            //redirectUri params should be the same as the requested redirectUri.
            throw new WebApplicationException("invalid_grant");
        }
        //client match
        if (!clientId.equals(authorizationCode.getClientId())) {
            throw new WebApplicationException("invalid_grant");
        }
        String accessToken = generateAccessToken(clientId, authorizationCode.getUserId(), authorizationCode.getApprovedScopes());
        String refreshToken = generateRefreshToken(clientId, authorizationCode.getUserId(), authorizationCode.getApprovedScopes());
        TokenVO result = new TokenVO();
        result.setAccess_token(accessToken);
        result.setExpires_in(expiresInMilliseconds);
        result.setScope(authorizationCode.getApprovedScopes());
        result.setRefresh_token(refreshToken);
        return result;
    }
}

第三方应用

收到code并请求Token

    @GET
    @Path("callback")
    @Produces(MediaType.APPLICATION_JSON)
    @SneakyThrows
    public Response callback(@Context HttpServletRequest request,
                             @Context HttpServletResponse response) {
    
        String clientId = "webappclient";
        String clientSecret = "webappclientsecret";
    
        //Error:
        String error = request.getParameter("error");
        if (error != null) {
            request.setAttribute("error", error);
            return Response.status(Status.INTERNAL_SERVER_ERROR).entity("获取access_token失败").build();
        }
        String localState = (String) request.getSession().getAttribute("CLIENT_LOCAL_STATE");
        if (!localState.equals(request.getParameter("state"))) {
            request.setAttribute("error", "The state attribute doesn't match !!");
            return Response.status(Status.INTERNAL_SERVER_ERROR).entity("校验state失败").build();
        }
    
        String code = request.getParameter("code");
        // 内部直接调用authorization-server获取token
        Client client = ClientBuilder.newClient();
        WebTarget target = client.target("http://localhost:8080/authorization-server/auth/token");
    
        Form form = new Form();
        form.param("grant_type", "authorization_code");
        form.param("code", code);
        form.param("redirect_uri", redirectUri);
        TokenVO tokenResponse = target.request(MediaType.APPLICATION_JSON_TYPE)
            .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeaderValue(clientId, clientSecret))
            .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), TokenVO.class);

        request.setAttribute("token", tokenResponse);
        // 将获取到的token显示在页面上
        request.getRequestDispatcher("/success.jsp").forward(request, response);
        return null;
    
    }

访问受保护的资源

利用刚才拿到的token,访问 Resource Server。Resource Server会根据token中的scope进行权限的校验,一般使用一个全局Filter来实现。通过对JWT的解析来判断该请求是否拥有对Resource访问的权限。

@Log
@Provider
public class SecurityFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Override
    @SneakyThrows
    public void filter(ContainerRequestContext containerRequestContext) throws IOException {
        Method method = resourceInfo.getResourceMethod();
         // no need to check permissions
        if (method.isAnnotationPresent(DenyAll.class)) {
            log.info("no need to check permission");
            return;
        }
        // 特殊情况都处理完毕之后,开始正常处理token的解析和权限的校验
        verifyTokenAndPermission(containerRequestContext, method);
    }

    @SneakyThrows
    private void verifyTokenAndPermission(final ContainerRequestContext containerRequestContext, final Method method) {
        MultivaluedMap<String, String> headers = containerRequestContext.getHeaders();
        List<String> authorization = headers.get("Authorization");
        String token = authorization.get(0).substring("Bearer".length()).trim();
        // verify token
        JWSVerifier verifier = generateRsaJwsVerifier();
        SignedJWT jwt = SignedJWT.parse(token);
        if (!jwt.verify(verifier)) {
            containerRequestContext.abortWith(buildResponse(Response.Status.FORBIDDEN));
        }
        Map<String, Object> claims = jwt.getJWTClaimsSet().getClaims();
        // 这里第三方平台和认证服务器之间的user通过unionId关联,第三方平台数据库的用户表的关系 user_id , union_id
        // 如果认证服务器只是内部使用的话,也可以是userId
        String unionId= jwt.getJWTClaimsSet().getSubject();
        String scopes = (String) claims.get("scope");
        log.info("scopes is:\n" + scopes);
        List<String> parsedScopes = Arrays.asList(scopes.split("\\s+"));


        // verify permission
        if (method.isAnnotationPresent(RolesAllowed.class)) {
            RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
            String[] roles = rolesAnnotation.value();
            if (!parsedScopes.containsAll(Arrays.asList(roles))) {
                containerRequestContext.abortWith(buildResponse(Response.Status.FORBIDDEN));
            }
            containerRequestContext.setSecurityContext(new SecurityContext() {
                @Override
                public Principal getUserPrincipal() {
                    return () -> unionId;
                }

                @Override
                public boolean isUserInRole(String role) {
                    return parsedScopes.contains(role);
                }

                @Override
                public boolean isSecure() {
                    return true;
                }

                @Override
                public String getAuthenticationScheme() {
                    return "CLIENT_CERT";
                }
            });
        }
    }

    private JWSVerifier generateRsaJwsVerifier() throws Exception{
        String pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString("rsa/publish-key.pem");
        RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey);
        return new RSASSAVerifier(rsaKey);
    }

    private Response buildResponse(Response.Status status) {
        return Response
                .status(status)
                .entity("{\"errmsg\": \"\"}")
                .type(MediaType.APPLICATION_JSON_TYPE)
                .build();
    }
}

真正访问Resource上的Resource

@Path("user/protect")
@RequestScoped
@Log
public class UserResource {

    @GET
    @Path("read")
    @Produces(MediaType.TEXT_PLAIN)
    @RolesAllowed("resource.read")
    public String readProtectedInfo(@Context SecurityContext securityContext) {
        log.info( "unionId: " + securityContext.getUserPrincipal().getName());
        return "Read Success";
    }

    @POST
    @Path("write")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    @RolesAllowed("resource.write")
    public String writeProtectedInfo(@FormParam("writeInfo") String writeInfo, @Context SecurityContext securityContext) {
    	log.info( "unionId: " + securityContext.getUserPrincipal().getName());
        return "Write Success \n" + writeInfo;
    }
}

项目结构

在这里插入图片描述

Tomcat配置

  • 首先确保本地H2数据库已启动并可以正常访问
  • 下载H2相关的jdbc jar包,将其放在$TOMCAT_HOME/lib 下

项目部署

先在authorization-common下执行

mvn install

再使用Tomcat9进行部署
在这里插入图片描述
Tomcat启动时Hibernate会自动执行创建表结构和初始化表数据的工作,具体配置见 authorization-server/src/main/resources/META-INF/persistence.xml

启动成功之后访问 http://localhost:8080/thirdparty-server/ 接口体验,获取token之后使用curl或者PostMan 方式访问resource-server,查看是否预期

项目完整代码

仓库地址

相关文章

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自定义实现OAuth2.0 授权码模式 的相关文章

  • 将特定项目移至列表末尾

    我有一个ArrayList in Java deleteItem createitem exportitem deleteItems createItems 我想移动包含的所有字符串delete到列表的末尾 所以我会得到下一个 create
  • Eclipse 调试“未找到源”

    我刚刚开始使用 Eclipse 所以慢慢来吧 但是 当尝试调试 JUnit 测试用例时 我会收到一个对话框 指出当我在测试方法中的代码中找到此行时 未找到源代码 Assert assertEquals 1 contents size 我知道
  • Spring JSON序列化、Gson反序列化

    我目前在某些内部对象的反序列化方面遇到问题 在春天 我在使用输出之前初始化所有对象 ResponseBody 例如 这是一个响应 id 1 location id 1 extra location data id 2 location 1
  • 无法解析 Java 中的方法

    我有一个Question具有 4 个的对象Answer里面的物体 在 Question java 我有一个方法是 public Answer getA return a 在另一种方法中我有 if questions get randomNu
  • Java ASN.1 编译器

    现在我正在使用二进制笔记 http bnotes sourceforge net 解析 ASN 1 文件以在 Java 项目中使用 它采用 ASN 1 定义并生成 Java 类 让我可以操作 ASN 1 文件 我用扩展标记碰壁了 因为它不支
  • 由于 maven-surefire-plugin,Maven 构建失败

    我这里有类似的问题eclipse 中缺少 maven surefire plugin https stackoverflow com questions 23588957 maven surefire plugin missing in e
  • 使用 Jquery Ajax 将数据从 jsp 发送到 struts2 操作类

    我需要使用 jquery Ajax 将表单数据从 jsp 传递到 struts2 并从 Struts2 操作类接收回 JSON 数据 我已经给出了下面的代码 当我传递 AJAX 数据时 url search action searchTex
  • 在 Spring Boot 异常处理期间保留自定义 MDC 属性

    简短版本 有足够的细节 如何保留添加在MDC中的属性doFilter 的方法javax servlet Filter执行 public void doFilter ServletRequest request ServletResponse
  • “传输协议线程失败” – “套接字为 EOF”,使用 Java 进行 J2SSH 连接

    我正在尝试通过我的 Java 代码建立 SSH 连接 但遇到异常 我通过 Putty Winscp 工具测试了我的连接 它工作正常 问题出在我的 Java 代码上 SEVERE The Transport Protocol thread f
  • 如何在Java中实现复合模式?

    我想实现一个复合模式Java以便绘制软件开发组织图 因此 我们假设有多个项目经理和多个开发人员 每个开发人员都被分配给一位项目经理 并且每个开发人员都能够使用各种编程语言进行编码 项目经理领导开发人员并准确了解他们的工作量 我对这个设计模式
  • 版本差异? Java 中的正则表达式转义

    看来正则表达式转义在不同版本的 Java 中的工作方式不同 在 Java openjdk 16 0 1 中编译工作正常 在 Java openjdk 11 0 11 中抛出此编译错误 test java 15 error illegal e
  • 更改 WireMock __files 目录

    来自docs http wiremock org docs stubbing 要从文件中读取正文内容 请将文件放在 files 下 目录 默认情况下 这应该位于 src test resources 下 从 JUnit 规则运行时 当独立运
  • Jersey/JAX-RS:如何自动使用@Valid递归级联bean验证?

    我正在 Jersey 的 REST 资源端点中验证我的 POJO public class Resource POST public Response post NotNull Valid final POJO pojo public cl
  • AWS Lambda 和 S3 - 上传的 pdf 文件为空/损坏

    我有一个 Spring 应用程序 在 AWS Lambda 上运行 它获取文件并将其上传到 AWS S3 Spring控制器发送一个MultipartFile到我的方法 使用 Amazon API Gateway 将其上传到 AWS S3
  • 无法实例化类对象的类型 (Java)

    这是我收到错误的代码 在 new 之后的第二个 Killer 处 String classes new String 5 kills 0 Brian Moser kills 1 James Doakes kills 2 Lila Tourn
  • JPA中如何连接多个数据库?

    我有一个 Spring Boot 应用程序 当前使用 JPA 连接到单个数据库 application properties 文件中的连接详细信息 spring datasource url jdbc oracle thin localho
  • 如何使用 Java 1.4 和 SAX 将任意数据编码为 XML?

    我们使用 SAX 来解析 XML 因为它不需要将整个 XML 文档读入内存来解析单个值 我读过很多文章 坚持认为 SAX 只能用于解析 解码 XML 而不能创建它 这是真的 不 这不是真的 您可以使用类似于以下内容的方式将 XML 编码为
  • 异步方法的同步版本

    在 Java 中创建异步方法的同步版本的最佳方法是什么 假设您有一个包含这两种方法的类 asyncDoSomething Starts an asynchronous task onFinishDoSomething Called when
  • Spring Boot 和安全性以及自定义 AngularJS 登录页面

    我正在为 Spring Security 实现一个自定义 AngularJS 登录页面 但遇到身份验证问题 遵循本教程 示例 以及他们的示例在本地运行良好 https github com dsyer spring security ang
  • 切换按钮形状不变

    我正在尝试制作一个带有绿色背景的圆形切换按钮 我用了

随机推荐

  • 正交矩阵的列向量组是标准正交向量组

  • Android+SpringBoot前后端分离实现登录注册

    Android SpringBoot前后端分离实现登录注册 一 登录 1 界面设计 2 Android端 1 布局文件 activity login 2 java文件 LoginActivity 下面给出找到IPv4地址的步骤 1 Win
  • postman和jmeter哪个是更好的接口测试工具?

    1 关于在哪个阶段使用哪个接口测试工具 对于单个的接口测试请求 我更喜欢用postman 可能比jmeter更好定位问题 在接口调试好后 我则更喜欢用jmeter进行一些简单的自动化请求 可能代替很多手工造数据和流程性的操作 列如 在开发交
  • 从入门到放弃系列--如何成为全栈工程师02

    未来3个月 你需要的html全部在这里 用html语言 可以写出目前你见到的所有类型的电脑界面 包括app 小程序 网页 注意 我说的是界面 说这个的意思是告诉你html是基础也很实用 是帮你打开全栈工程师的大门的第一步 所以 你需要熟练掌
  • 【torch】如何把把几个 tensor 连接起来?(含源代码)

    一 cat 在 PyTorch 中 要向一个 tensor 中添加元素 你通常需要创建一个新的 tensor 然后将元素添加到新的 tensor 中 PyTorch tensors 是不可变的 所以不能像列表一样直接追加元素 以下是如何实现
  • keytool 错误: java.io.IOException: Keystore was tampered with, or password was incorrect

    这里需要输入的密码不是证书的密码执行keytool import keystore file 这个命令提示需要输入密码 输入 changeit 信任证书 OK
  • opencv 通过网络连接工业相机_相机标定与测距

    0 概述 硬件 Realsense D435i 含imu AprilTag或棋盘格标定板 本文均使用棋盘格 说明 本文非手把手教你如何教程 需要一定的ROS基础和D435i相机调试基础 当然玩过其他相机也可以 写作过程参考了部分作者成果 如
  • 函数式编程—柯里化

    纯函数的作用和优势 1 可以安心的编写和使用 2 写的时候保证了函数的纯度 只是单纯实现自己的业务逻辑即可 不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否已经发生了修改 3 在用的时候 确定输入的内容不会被任意篡改 并且自己确定
  • 玩具蛇

    include
  • 数据库SQLite

    数据库SQLite 了解最轻巧的数据库SQLite SQLite 是一款轻型的数据库 占用资源非常低 它的源代码不受版权限制 能够支持Windows Linux Unix等等主流的操作系统 同时能够跟很多程序语言相结合 比如 Tcl C P
  • 29 KVM管理系统资源-调整虚拟CPU绑定关系

    文章目录 29 KVM管理系统资源 调整虚拟CPU绑定关系 29 1 概述 29 2 操作步骤 29 KVM管理系统资源 调整虚拟CPU绑定关系 29 1 概述 把虚拟机的vCPU绑定在物理CPU上 即vCPU只在绑定的物理CPU上调度 在
  • 2-问过 chatgpt 的问题(天马行空想问什么问什么)

    目录 一 信号序列中大部分为 0 时 FFT 运算复杂度的计算 1 当fft运算时 大部分信号点为0的情况下 对fft的运算时间会有影响吗 2 大部分信号点为0的情况下 fft的运算复杂度计算 3 这里的时间复杂度 O N log
  • HTML5语音合成功能

    这篇文章主要介绍了HTML5语音合成功能的实现代码 本文通过实例代码给大家介绍的非常详细 具有一定的参考借鉴价值 需要的朋友参考下吧 可将该代码复制到chrome控制台中体验 let msg new SpeechSynthesisUtter
  • this和super

    this this总是置于当前对象的成员类作区分 this是当前对象的引用 就是说当前用构造函数建的对象是谁 这个this就代表谁 它是一个引用 this 总是指向自己本事 super 子类在重写了父类的方法后 常常还需要使用到父类中被重写
  • do while循环语句的学习以及练习

    今天学的是do while循环语句 先执行循环体 直到条件的表达式为false 与while循环语句的区别 while语句先判断条件 满足时执行循环体 do while语句先执行循环体 满足条件在执行 语法 do 循环体 while 条件
  • kinova-jaco2使用Moveit!控制真实机械臂抓取固定点物体

    kinova jaco2使用Moveit 控制真实机械臂抓取固定点物体 一 机械臂坐标系 坐标系方向 位姿方向 轴的起始点 二 启动机械臂和Moveit 三 实现抓取 python代码 python文件建议直接用python启动 四 遇到的
  • react hook之useMemo

    useMemo的作用 Pass a create function and an array of dependencies useMemo will only recompute the memoized value when one o
  • LogisticRegression(逻辑回归)

    LogisticRegression定义 logistic回归 是一种广义的线性回归分析模型 常用于数据挖掘 疾病自动诊断 经济预测等领域 例如 探讨引发疾病的危险因素 并根据危险因素预测疾病发生的概率等 以胃癌病情分析为例 选择两组人群
  • SVG转为Png

    1 pom中引入maven依赖
  • 自定义实现OAuth2.0 授权码模式

    文章目录 OAuth2 0 授权码模式 实践 依赖知识 术语 授权码流程 认证服务器 拉起请求用户授权页面 用户手动授权 提交授权 生成code 下发Token 第三方应用 收到code并请求Token 访问受保护的资源 项目结构 Tomc