OAuth三方授权登录_第三方登录-程序员宅基地

技术标签: java  运维  服务器  

一、OAuth简介

1、OAuth2.0介绍

1.1 介绍

OAuth协议:

OAuth(Open Authorization)是一个关于授权(authorization)的开放网络标准,允许用户授权第三方 应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他 们数据的所有内容。OAuth在全世界得到广泛应用,目前的版本是2.0版。

协议特点

  • 简单: 不管是OAuth服务提供者还是应用开发者,都很易于理解与使用;

  • 安全: 没有涉及到用户密钥等信息,更安全更灵活;

  • 开放: 任何服务提供商都可以实现OAuth,任何软件开发商都可以使用OAuth;

1.2 应用场景

  • 原生app授权: app登录请求后台接口,为了安全认证,所有请求都带token信息,如果登录验证、 请求后台数据

  • 前后端分离单页面应用: 前后端分离框架,前端请求后台数据,需要进行oauth2安全认证

  • 第三方应用授权登录: 比如QQ,微博,微信的授权登录

1.3 基本概念

OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务提供商"进行交互

  • Resource owner(资源拥有者):拥有该资源的最终用户,他有访问资源的账号密码;

  • Resource server(资源服务器):受保护资源所在的服务器,如果请求包含正确的访问令牌,就可以访问受保护的资源;

  • Client(客户端):请求访问资源的客户端,可以是浏览器、移动设备或者服务器,客户端会携带访问令牌访问资源服务器上的资源;

  • Authorization server(认证服务器):负责认证客户端身份的服务器,如果客户端认证通过,会给客户端发放访问资源服务器的令牌。

1.4 优缺点

优点

  • 更安全,客户端不接触用户密码,服务器端更易集中保护

  • 广泛传播并被持续采用

  • 短寿命和封装的token

  • 资源服务器和授权服务器解耦

  • 集中式授权,简化客户端

  • HTTP/JSON友好,易于请求和传递token

  • 考虑多种客户端架构场景

  • 客户可以具有不同的信任级别

缺点

  • 协议框架太宽泛,造成各种实现的兼容性和互操作性差

  • 不是一个认证协议,本身并不能告诉你任何用户信息

2、OAuth授权模式

2.1 四种授权模式

不管哪一种授权方式,第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的

  • Authorization Code(授权码模式):正宗的OAuth2的授权模式,客户端先将用户导向认证服务器,认证用户成功后获取授权码,然后进行授权,最后根据授权码获取访问令牌;

  • Implicit(隐藏式):和授权码模式相比,取消了获取授权码的过程,直接获取访问令牌;

  • Password(密码模式):客户端直接向用户获取用户名和密码,之后向认证服务器获取访问令牌;

  • Client Credentials(客户端凭证模式):客户端直接通过客户端认证(比如client_id和client_secret)从认证服务器获取访问令牌。

一般来说,授权码模式和密码模式是两种常用的授权模式

2.2 授权码模式

指应用先申请一个授权码,然后再用这个授权码获取令牌

流程:

  • 客户端将用户导向认证服务器的授权页面;

  • 用户在认证服务器页面登录并授权;

  • 认证服务器返回授权码给客户端;

  • 客户端将授权码传递给客户端所在的后端服务(也可以是自己的认证服务器),由后端服务在后端请求认证服务器获取令牌,并返回给客户端。

2.3 密码模式

如果用户信任应用,应用可以直接携带用户的用户名和密码,直接申请令牌

流程:

  • 客户端要求用户提供用户名和密码;

  • 客户端携带用户名和密码,访问授权服务器;

  • 授权服务器验证用户身份之后,直接返回令牌。

     

二、三方授权登录

1、需求介绍

自研应用需要扩展时,绕不开的就是集成其他社交软件的三方登录,比如微信/QQ/微博/Github等等,而这用到的模式属于OAuth的授权码方式授权,下面我就介绍几种三方授权登录教程,同时给予数据库扩展设计思路

2、第三方授权登录数据库设计

第三方授权登录的时候,第三方的用户信息是存数据库原有的 user 表还是新建一张表呢 ?答案得看具体项目。三方授权登录之后,第三方用户信息一般都会返回用户唯一的标志 openid 或者 unionid 或者 id,具体是什么得看第三方,比如 github 的是 id

2.1 直接通过注册的方式保存到数据库

如果网站没有注册功能的,直接通过第三方授权登录,授权成功之后,可以直接把第三的用户信息注册保存到自己数据库的 user 表里面。典型的例子就是微信公众号的授权登录。

如果网站有注册功能的,也可以通过第三方授权登录,授权成功之后,也可以直接把第三的用户信息注册保存到自己数据库的 user 表里面(但是密码是后端自动生成的,用户也不知道,只能用第三方授权登录),这样子的第三方的用户和原生注册的用户信息都在同一张表了,这种情况得看自己项目的具体情况。

2.2 增加映射表

现实中很多网站都有多种账户登录方式,比如可以用网站的注册 id 登录,还可以用手机号登录,可以用 QQ 登录等等。数据库中都是有映射关系,QQ、手机号等都是映射在网站的注册 id 上。保证不管用什么方式登录,只要去查映射关系,发现是映射在网站注册的哪个 id 上,就让哪个 id 登录成功。

2.3 建立一个 oauth 表

建立一个 oauth 表,一个 id 列,记录对应的用户注册表的 id,然后你有多少个第三方登陆功能,你就建立多少列,记录第三方登陆接口返回的 openid;第三方登陆的时候,通过这个表的记录的 openid 获取 id 信息,如果存在通过 id 读取注册表然后用 session 记录相关信息。不存在就转向用户登陆/注册界面要用户输入本站注册的账户进行 openid 绑定或者新注册账户信息进行绑定。

3、数据库实战举例

  • 用户表分为用户基础信息表 + 用户授权信息表;

  • 所有和授权相关,都放在用户信息授权表,用户信息表和用户授权表是一对多的关系

用户基础信息表

用户授权信息表

三、GitHub 登录

1、概述

Github的OAuth 授权原理大致如下

  • A网站让用户跳转到 GitHub

  • GitHub 要求用户登录,然后询问"A 网站要求获得 xx 权限,你是否同意"

  • 用户同意,GitHub 就会重定向回 A 网站,同时发回一个授权码

  • A 网站使用授权码,向 GitHub 请求令牌

  • GitHub 返回令牌

  • A 网站使用令牌,向 GitHub 请求用户数据

2、应用登记

一个应用要求 OAuth 授权,必须先到对方网站登记,让对方知道是谁在请求,所以要先去 GitHub 登记一下(免费)。GitHub的文档

首先访问Authorized OAuth App,填写登记表进行创建(进入 Github 的 Setting 页面,点击 Developer settings,选择OAuth Apps,选择new OAuth App)

注意回调地址要与我们待会写的接口地址匹配,否则会报错,进入应用后就能看见我们应用了,secrets没有的话可以生成,注意保存

  • Client ID

  • Client secrets

3、Github授权登录原理

3.1 请求用户的 GitHub 身份

它会提示用户使用他们可以用于登录和授权您的应用程序的特定帐户

GET https://github.com/login/oauth/authorize

3.2 用户被 GitHub 重定向回站点

如果用户接受您的请求,GitHub 将重定向回您的站点,其中包含一个临时code的代码参数以及您在上一步中提供的state参数状态。临时代码将在 10 分钟后过期。如果状态不匹配,则第三方创建了请求,您应该中止该过程。

也就是重回到我们的站点,也就是发送了http://localhost:8080/oauth/githubCallback(自定义),并且携带了code将此交换code为访问令牌OAUTH-TOKEN

POST https://github.com/login/oauth/access_token

3.3 使用访问令牌访问API

访问令牌允许代表用户向 API 发出请求,获取用户的基本信息

Authorization: token OAUTH-TOKEN
GET https://api.github.com/user

4、代码实战

4.1 配置环境

引入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.7</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.4</version>
</dependency>

配置application.yml

github:
  clientId: ab3d67630b13025715cf
  clientSecret: 29f8c274c7634aa988f42c6507692da4fe118be8
  directUrl: http://localhost:8080/oauth/githubCallback

server:
  port: 8080

4.2 配置bean类

@Data
@Component
@ConfigurationProperties(prefix = "github")
public class GitHubOAuthInfo {

    private String clientId;

    private String clientSecret;

    private String directUrl;
}

4.3 配置state工具类

@Service
public class OauthService {

    private Set<String> stateSet = new HashSet<>();

    /**
     * 生成随机state字符串,这里可以存入Redis或者Set,返回时进行校验,不过要注意失效时间
    */
    public String genState(){
        String state = UUID.randomUUID().toString();
        stateSet.add(state);
        return state;
    }

    /**
     * 校验state,防止CSRF
     * 校验成功后删除
    */
    public boolean checkState(String state){
        if(stateSet.contains(state)){
            stateSet.remove(state);
            return true;
        }
        return false;
    }


}

4.4 认证与授权

@RestController
@Slf4j
@RequestMapping("/oauth")
public class AuthController {

    @Autowired
    private GitHubOAuthInfo gitHubOAuthInfo;

    @Autowired
    private OauthService oauthService;

    /**
     * Github认证令牌服务器地址
     */
    private static final String ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";

    /**
     * Github认证服务器地址
    */
    private static final String AUTHORIZE_URL = "https://github.com/login/oauth/authorize";

    /**
     * Github资源服务器地址
     */
    private static final String RESOURCE_URL = "https://api.github.com/user";



    /**
     * 前端获取认证的URL,由后端拼接好返回前端进行请求
    */
    @GetMapping("/githubLogin")
    public void githubLogin(HttpServletResponse response) throws IOException {

        // 生成并保存state,忽略该参数有可能导致CSRF攻击
        String state = oauthService.genState();
        // 传递参数response_type、client_id、state、redirect_uri
        String param = "response_type=code&" + "client_id=" + gitHubOAuthInfo.getClientId() + "&state=" + state
                + "&redirect_uri=" + gitHubOAuthInfo.getDirectUrl();

        // 1、请求Github认证服务器
        response.sendRedirect(AUTHORIZE_URL + "?" + param);
    }

    /**
     * GitHub回调方法
     *  code 授权码
     * state 应与发送时一致,防止CSRF攻击
     */
    @GetMapping("/githubCallback")
    public String githubCallback(String code, String state, HttpServletResponse response) throws Exception {
        // 验证state,如果不一致,可能被CSRF攻击
        if(!oauthService.checkState(state)) {
            throw new Exception("State验证失败");
        }

        // 设置JSONObject请求体
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("client_id",gitHubOAuthInfo.getClientId());
        jsonObject.put("client_secret",gitHubOAuthInfo.getClientSecret());
        jsonObject.put("code",code);

        String accessTokenRequestJson = null;
        try{
            long start = System.currentTimeMillis();
            // 请求accessToken,成功获取到后进行下一步信息获取,这里第一次可能会超时
            accessTokenRequestJson = HttpRequest.post(ACCESS_TOKEN_URL)
                    .header("Accept"," application/json")
                    .body(jsonObject.toJSONString())
                    .timeout(30000)
                    .execute().body();
            log.info("请求令牌耗时:{}",System.currentTimeMillis()-start);
        }catch (Exception e){
            log.error("请求令牌API访问异常,异常原因:",e);
            throw new Exception(e);
        }

        log.info("获取到的accessToken为:{}",accessTokenRequestJson);

        JSONObject accessTokenObject = JSONObject.parseObject(accessTokenRequestJson);
        // 如果返回的数据包含error,表示失败,错误原因存储在error_description
        if(accessTokenObject.containsKey("error")) {
            log.error("错误,原因:{}",accessTokenRequestJson);
            throw  new Exception("error_description,令牌获取错误");
        }
        // 如果返回结果中包含access_token,表示成功
        if(!accessTokenObject.containsKey("access_token")) {
            throw  new Exception("获取token失败");
        }

        // 得到token和token_type
        String accessToken = (String) accessTokenObject.get("access_token");
        String tokenType = (String) accessTokenObject.get("token_type");
        String userInfo = null;
        try{
            long start = System.currentTimeMillis();
            // 请求资源服务器获取个人信息
            userInfo = HttpRequest.get(RESOURCE_URL)
                    .header("Authorization", tokenType + " " + accessToken)
                    .timeout(5000)
                    .execute().body();
            log.info("请求令牌耗时:{}",System.currentTimeMillis()-start);
        }catch (Exception e){
            log.error("请求令牌API访问异常,异常原因:",e);
            throw new Exception(e);
        }

        JSONObject userInfoJson = JSONObject.parseObject(userInfo);
        return userInfoJson.toJSONString();
    }

}

最后浏览器访问http://localhost:8080/oauth/githubLogin,即可进入用户授权状态,授权后会进行跳转,自动获取用户的基本信息,后面可以和数据库联动

四、QQ登录

1、概述

官方参考文档:https://wiki.connect.qq.com/oauth2-0简介

大体和Github登录类似,QQ登录OAuth2.0总体处理流程如下

  • 申请接入,获取appid和apikey;

  • 开发应用,并设置协作者帐号进行测试联调;

  • 放置QQ登录按钮;

  • 通过用户登录验证和授权,获取Access Token;

  • 通过Access Token获取用户的OpenID;

  • 调用OpenAPI,来请求访问或修改用户授权的资源。

2、应用创建

首先没有注册的开发者需要先注册并实名,去开发者平台注册并实名,认证通过后进入QQ 互联管理中心,创建一个网站应用新应用(需要先审核个人身份),然后注册应用信息,和 GitHub 的步骤类似

注册后,可以看到应用的 APP ID、APP Key,以及被允许的接口,当然只有一个获取用户信息

3、QQ授权登录原理

参考:https://wiki.connect.qq.com/准备工作_oauth2-0

3.1 获取Authorization Code

打开浏览器,访问如下地址(请将client_id,redirect_uri,scope等参数值替换为你自己的)

GET https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=[YOUR_APPID]&redirect_uri=[YOUR_REDIRECT_URI]&scope=[THE_SCOPE]

如果用户点击 “授权并登录”,则成功跳转到指定的redirect_uri,并跟上Authorization Code(注意此code会在10分钟内过期)

3.2 通过Authorization Code获取Access Token

获取到的access token具有30天有效期,用户再次登录时自动刷新,第三方网站可存储access token信息,以便后续调用OpenAPI访问和修改用户信息时使用

GET https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=[YOUR_APP_ID]&client_secret=[YOUR_APP_Key]&code=[The_AUTHORIZATION_CODE]&redirect_uri=[YOUR_REDIRECT_URI]

3.3 使用Access Token获取用户信息

发送请求到如下地址,获取用户的OpenID

GET https://graph.qq.com/oauth2.0/me?access_token=YOUR_ACCESS_TOKEN

使用Access Token以及OpenID来访问和修改用户数据,建议网站在用户登录后,即调用get_user_info接口,获得该用户的头像、昵称并显示在网站上,使用户体验统一。

GET https://graph.qq.com/user/get_user_info?access_token=YOUR_ACCESS_TOKEN&oauth_consumer_key=YOUR_APP_ID&openid=YOUR_OPENID

4、代码实战

4.1 配置环境

依赖和上面一样,主要配置yml配置文件

qq:
  qqAppId: 101474821
  qqAppKey: 00d91cc7f636d71faac8629d559f9fee
  directUrl: http://localhost:8080/oauth/qqCallback

4.2 配置bean类与工具类

state工具类和上文一样,bean类如下

@Data
@Component
@ConfigurationProperties(prefix = "qq")
public class QqOAuthInfo {

    private String qqAppId;

    private String qqAppKey;

    private String directUrl;
}

4.3 认证与授权

qq的比较麻烦,需要实名认证,创建应用也需要备案域名等

@RestController
@Slf4j
@RequestMapping("/oauth")
public class QqAuthController {

    @Autowired
    private QqOAuthInfo qqOAuthInfo;

    @Autowired
    private OauthService oauthService;

    /**
     * QQ认证服务器地址
     */
    private static final String AUTHORIZE_URL = "https://graph.qq.com/oauth2.0/authorize";

    /**
     * QQ认证令牌服务器地址
     */
    private static final String ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token";

    /**
     * QQ的openId Url
     */
    private static final String OPEN_ID_URL = "https://graph.qq.com/oauth2.0/me";

    /**
     * QQ的用户数据URL
     */
    private static final String USER_INFO_URL = "https://graph.qq.com/user/get_user_info";

    /**
     * 前端获取认证的URL,由后端拼接好返回前端进行请求
     */
    @GetMapping("/qqLogin")
    public void githubLogin(HttpServletResponse response) throws IOException {

        // 生成并保存state,忽略该参数有可能导致CSRF攻击
        String state = oauthService.genState();
        // 传递参数response_type、client_id、state、redirect_uri
        String param = "response_type=code&" + "client_id=" + qqOAuthInfo.getQqAppId() + "&state=" + state
                + "&redirect_uri=" + qqOAuthInfo.getDirectUrl();

        System.out.println(AUTHORIZE_URL + "?" + param);
        // 请求QQ认证服务器
        response.sendRedirect(AUTHORIZE_URL + "?" + param);
    }

    /**
     * QQ回调方法
     * code 授权码
     * state 应与发送时一致
     */
    @GetMapping("/qqCallback")
    public String githubCallback(String code, String state, HttpServletResponse response) throws Exception {
        // 验证state,如果不一致,可能被CSRF攻击
        if(!oauthService.checkState(state)) {
            throw new Exception("State验证失败");
        }

        // 设置请求参数,fmt参数因历史原因,默认是x-www-form-urlencoded格式,如果填写json,则返回json格式
        String param = "grant_type=authorization_code&code=" + code + "&redirect_uri=" +
                qqOAuthInfo.getDirectUrl() + "&client_id=" + qqOAuthInfo.getQqAppId() +
                "&client_secret=" + qqOAuthInfo.getQqAppKey() + "&fmt=json";


        String accessTokenRequestJson = null;
        try{
            long start = System.currentTimeMillis();
            // 请求accessToken,成功获取到后进行下一步信息获取,这里第一次可能会超时
            accessTokenRequestJson = HttpRequest.get(ACCESS_TOKEN_URL)
                    .body(param)
                    .timeout(30000)
                    .execute().body();
            log.info("请求令牌耗时:{}",System.currentTimeMillis()-start);
        }catch (Exception e){
            log.error("请求令牌API访问异常,异常原因:",e);
            throw new Exception(e);
        }
        /**
         * result示例:
         * 成功:access_token=A24B37194E89A0DDF8DDFA7EF8D3E4F8&expires_in=7776000&refresh_token=BD36DADB0FE7B910B4C8BBE1A41F6783
         */
        log.info("获取到的accessToken为:{}",accessTokenRequestJson);

        JSONObject accessTokenObject = JSONObject.parseObject(accessTokenRequestJson);
        // 如果返回的数据包含error,表示失败,错误原因存储在error_description
        if(accessTokenObject.containsKey("error")) {
            log.error("错误,原因:{}",accessTokenRequestJson);
            throw  new Exception("error_description,令牌获取错误");
        }
        // 如果返回结果中包含access_token,表示成功
        if(!accessTokenObject.containsKey("access_token")) {
            throw  new Exception("获取token失败");
        }

        // 得到token和token_type
        String accessToken = (String) accessTokenObject.get("access_token");
        String meParams = "access_token=" + accessToken;
        String meBody = null;
        try{
            long start = System.currentTimeMillis();
            // 请求accessToken,成功获取到后进行下一步信息获取,这里第一次可能会超时
            meBody = HttpRequest.get(OPEN_ID_URL)
                    .body(meParams)
                    .execute().body();
            log.info("请求令牌耗时:{}",System.currentTimeMillis()-start);
        }catch (Exception e){
            log.error("openId访问异常,异常原因:",e);
            throw new Exception(e);
        }

        // 成功返回如下:callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
        JSONObject meJsonObject = JSONObject.parseObject(meBody);
        // 取出openid
        String openid = meJsonObject.getString("openid");

        // 使用Access Token以及OpenID来访问和修改用户数据
        String userInfoParam = "access_token=" + accessToken + "&oauth_consumer_key=" + qqOAuthInfo.getQqAppId() + "&openid=" + openid;
        String userInfo = null;
        try{
            long start = System.currentTimeMillis();
            // 请求accessToken,成功获取到后进行下一步信息获取,这里第一次可能会超时
            userInfo = HttpRequest.get(USER_INFO_URL)
                    .body(userInfoParam)
                    .timeout(5000)
                    .execute().body();
            log.info("请求令牌耗时:{}",System.currentTimeMillis()-start);
        }catch (Exception e){
            log.error("用户数据访问异常,异常原因:",e);
            throw new Exception(e);
        }

        JSONObject userInfoJson = JSONObject.parseObject(userInfo);

        return userInfoJson.toJSONString();
    }
}

五、微信登录

 

官方文档:

  • https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yuechuzhixing/article/details/131043572

智能推荐

什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?_成员内部类和局部内部类的区别-程序员宅基地

文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别

分布式系统_分布式系统运维工具-程序员宅基地

文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具

用Exce分析l数据极简入门_exce l趋势分析数据量-程序员宅基地

文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量

宁盾堡垒机双因素认证方案_horizon宁盾双因素配置-程序员宅基地

文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置

谷歌浏览器安装(Win、Linux、离线安装)_chrome linux debian离线安装依赖-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖

烤仔TVの尚书房 | 逃离北上广?不如押宝越南“北上广”-程序员宅基地

文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...

随便推点

java spark的使用和配置_使用java调用spark注册进去的程序-程序员宅基地

文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序

汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用_uds协议栈 源代码-程序员宅基地

文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码

AUTOSAR基础篇之OS(下)_autosar 定义了 5 种多核支持类型-程序员宅基地

文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型

VS报错无法打开自己写的头文件_vs2013打不开自己定义的头文件-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件

【Redis】Redis基础命令集详解_redis命令-程序员宅基地

文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令

URP渲染管线简介-程序员宅基地

文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线