1. 简介

  随着科技时代日渐繁荣,越来越多的应用融入我们的生活。不同的应用系统不同的用户密码,造成了极差的用户体验。要是能使用常见的应用账号实现全应用的认证登录,将会更加促进应用产品的推广,为生活增加无限乐趣。

  本文基于JustAuth实现第三方授权登录,选择JustAuth原因如下:

  • 支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软、今日头条、Teambition、StackOverflow、Pinterest、人人、华为、企业微信、酷家乐、Gitlab、美团、饿了么和推特等第三方平台的授权登录;
  • 无需去第三方平台找寻繁杂而庞大的SDK文档,JustAuth提供了及其简单的接入方式;
  • 支持自定义OAuth平台;
  • 支持多种方式的State缓存;
  • 支持自定义Http实现;
  • 自定义 Scope,支持更完善的授权体系;
  • 与SpringBoot完美集成,也可整合Spring Security等安全框架;
  • 官网文档详细、完善,按照文档即可完成需求落地。

    官网:https://justauth.wiki/#/

2. 初始化数据库

  创建数据库justauth,并初始化表结构:

DROP TABLE IF EXISTS `justauth`.`t_ja_user`;
CREATE TABLE `justauth`.`t_ja_user` (
`uuid` varchar(64) NOT NULL COMMENT '用户第三方系统的唯一id',
`username` varchar(100) DEFAULT NULL COMMENT '用户名',
`nickname` varchar(100) DEFAULT NULL COMMENT '用户昵称',
`avatar` varchar(255) DEFAULT NULL COMMENT '用户头像',
`blog` varchar(255) DEFAULT NULL COMMENT '用户网址',
`company` varchar(50) DEFAULT NULL COMMENT '所在公司',
`location` varchar(255) DEFAULT NULL COMMENT '位置',
`email` varchar(50) DEFAULT NULL COMMENT '用户邮箱',
`gender` varchar(10) DEFAULT NULL COMMENT '性别',
`remark` varchar(500) DEFAULT NULL COMMENT '用户备注(各平台中的用户个人介绍)',
`source` varchar(20) DEFAULT NULL COMMENT '用户来源',
PRIMARY KEY (`uuid`)
);

3. 示例代码

  使用SpringBoot插件justauth-spring-boot-starter搭建工程。

  • 创建项目

  • 修改pom.xml
<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>
<groupId>com.c3stones</groupId>
<artifactId>spring-boot-justauth-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-justauth-demo</name>
<description>Spring Boot JustAuth Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.8.RELEASE</version>
<relativePath />
</parent> <dependencies>
<dependency>
<groupId>com.xkcoding.justauth</groupId>
<artifactId>justauth-spring-boot-starter</artifactId>
<version>1.3.4.beta</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> <repositories>
<!--阿里云私服 -->
<repository>
<id>aliyun</id>
<name>aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</repository>
<!--中央仓库 -->
<repository>
<id>oss snapshot</id>
<name>oss snapshot</name>
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories> </project>
  • 创建响应实体
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; /**
* 响应实体
*
* @author CL
*
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Response { /**
* 响应码
*/
private int code; /**
* 响应消息体
*/
private String msg; /**
* 失败响应
*
* @param msg 响应消息体
* @return
*/
public static Response error(String msg) {
return new Response(500, msg);
} /**
* 成功响应
*
* @param msg 响应消息体
* @return
*/
public static Response success(String msg) {
return new Response(200, msg);
} }
  • 创建Redis配置类
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer; /**
* Redis配置类
*
* @author CL
*
*/
@Slf4j
@EnableCaching
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport { /**
* 注入RedisTemplate
*
* @param redisConnectionFactory Redis连接工厂
* @return
*/
@Bean(name = "redisTemplate")
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
} /**
* 缓存错误处理
*/
@Bean
@Override
public CacheErrorHandler errorHandler() {
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
} @Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
} @Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
} @Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error("Redis occur handleCacheClearError:", e);
}
};
} /**
* 注入Redis缓存配置类
*
* @return
*/
@Bean(name = { "redisCacheConfiguration" })
public RedisCacheConfiguration redisCacheConfiguration() {
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)));
return configuration;
} }
  • 创建Token缓存到Redis工具类
import java.util.LinkedList;
import java.util.List;
import java.util.Objects; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.model.AuthToken; /**
* 授权Token缓存Redis
*
* @author CL
*
*/
@Configuration
public class JustAuthTokenCache { @SuppressWarnings("rawtypes")
@Autowired
private RedisTemplate redisTemplate; private BoundHashOperations<String, String, AuthToken> valueOperations; @SuppressWarnings("unchecked")
@PostConstruct
public void init() {
valueOperations = redisTemplate.boundHashOps("JUSTAUTH::TOKEN");
} /**
* 保存Token
*
* @param uuid 用户uuid
* @param authUser 授权用户
* @return
*/
public AuthToken saveorUpdate(String uuid, AuthToken authToken) {
valueOperations.put(uuid, authToken);
return authToken;
} /**
* 根据用户uuid查询Token
*
* @param uuid 用户uuid
* @return
*/
public AuthToken getByUuid(String uuid) {
Object token = valueOperations.get(uuid);
if (null == token) {
return null;
}
return JSONObject.parseObject(JSONObject.toJSONString(token), AuthToken.class);
} /**
* 查询所有Token
*
* @return
*/
public List<AuthToken> listAll() {
return new LinkedList<>(Objects.requireNonNull(valueOperations.values()));
} /**
* 根据用户uuid移除Token
*
* @param uuid 用户uuid
* @return
*/
public void remove(String uuid) {
valueOperations.delete(uuid);
} }
  • 创建实体类
import java.io.Serializable;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; /**
* 授权用户信息
*
* @author CL
*
*/
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "t_ja_user")
public class JustAuthUser extends AuthUser implements Serializable { private static final long serialVersionUID = 1L; /**
* 用户第三方系统的唯一id。在调用方集成该组件时,可以用uuid + source唯一确定一个用户
*/
@TableId(type = IdType.INPUT)
private String uuid; /**
* 用户授权的token信息
*/
@TableField(exist = false)
private AuthToken token; /**
* 第三方平台返回的原始用户信息
*/
@TableField(exist = false)
private JSONObject rawUserInfo; /**
* 自定义构造函数
*
* @param authUser 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同
*/
public JustAuthUser(AuthUser authUser) {
super(authUser.getUuid(), authUser.getUsername(), authUser.getNickname(), authUser.getAvatar(),
authUser.getBlog(), authUser.getCompany(), authUser.getLocation(), authUser.getEmail(),
authUser.getRemark(), authUser.getGender(), authUser.getSource(), authUser.getToken(),
authUser.getRawUserInfo());
} }
  • 创建Mapper
import org.apache.ibatis.annotations.Mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.c3stones.entity.JustAuthUser; /**
* 授权用户Mapper
*
* @author CL
*
*/
@Mapper
public interface JustAuthUserMapper extends BaseMapper<JustAuthUser> { }
  • 创建Service
import com.baomidou.mybatisplus.extension.service.IService;
import com.c3stones.entity.JustAuthUser; /**
* 授权用户Service
*
* @author CL
*
*/
public interface JustAuthUserService extends IService<JustAuthUser> { /**
* 保存或更新授权用户
*
* @param justAuthUser 授权用户
* @return
*/
boolean saveOrUpdate(JustAuthUser justAuthUser); /**
* 根据用户uuid查询信息
*
* @param uuid 用户uuid
* @return
*/
JustAuthUser getByUuid(String uuid); /**
* 根据用户uuid移除信息
*
* @param uuid 用户uuid
* @return
*/
boolean removeByUuid(String uuid); }
  • 创建Service实现类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.c3stones.cache.JustAuthTokenCache;
import com.c3stones.entity.JustAuthUser;
import com.c3stones.mapper.JustAuthUserMapper;
import com.c3stones.service.JustAuthUserService; /**
* 授权用户Service实现
*
* @author CL
*
*/
@Service
public class JustAuthUserServiceImpl extends ServiceImpl<JustAuthUserMapper, JustAuthUser>
implements JustAuthUserService { @Autowired
private JustAuthTokenCache justAuthTokenCache; /**
* 保存或更新授权用户
*
* @param justAuthUser 授权用户
* @return
*/
@Override
public boolean saveOrUpdate(JustAuthUser justAuthUser) {
justAuthTokenCache.saveorUpdate(justAuthUser.getUuid(), justAuthUser.getToken());
return super.saveOrUpdate(justAuthUser);
} /**
* 根据用户uuid查询信息
*
* @param uuid 用户uuid
* @return
*/
@Override
public JustAuthUser getByUuid(String uuid) {
JustAuthUser justAuthUser = super.getById(uuid);
if (justAuthUser != null) {
justAuthUser.setToken(justAuthTokenCache.getByUuid(uuid));
}
return justAuthUser;
} /**
* 根据用户uuid移除信息
*
* @param uuid 用户uuid
* @return
*/
@Override
public boolean removeByUuid(String uuid) {
justAuthTokenCache.remove(uuid);
return super.removeById(uuid);
} }
  • 创建授权Controller
import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSON;
import com.c3stones.common.Response;
import com.c3stones.entity.JustAuthUser;
import com.c3stones.service.JustAuthUserService;
import com.xkcoding.justauth.AuthRequestFactory; import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils; /**
* 授权Controller
*
* @author CL
*
*/
@Slf4j
@RestController
@RequestMapping("/oauth")
public class AuthController { @Autowired
private AuthRequestFactory factory; @Autowired
private JustAuthUserService justAuthUserService; /**
* 登录
*
* @param type 第三方系统类型,例如:gitee/baidu
* @param response
* @throws IOException
*/
@GetMapping("/login/{type}")
public void login(@PathVariable String type, HttpServletResponse response) throws IOException {
AuthRequest authRequest = factory.get(type);
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
} /**
* 登录回调
*
* @param type 第三方系统类型,例如:gitee/baidu
* @param callback
* @return
*/
@SuppressWarnings("unchecked")
@RequestMapping("/{type}/callback")
public Response login(@PathVariable String type, AuthCallback callback) {
AuthRequest authRequest = factory.get(type);
AuthResponse<AuthUser> response = authRequest.login(callback);
log.info("【response】= {}", JSON.toJSONString(response)); if (response.ok()) {
justAuthUserService.saveOrUpdate(new JustAuthUser(response.getData()));
return Response.success(JSON.toJSONString(response));
}
return Response.error(response.getMsg());
} /**
* 收回
*
* @param type 第三方系统类型,例如:gitee/baidu
* @param uuid 用户uuid
* @return
*/
@SuppressWarnings("unchecked")
@RequestMapping("/revoke/{type}/{uuid}")
public Response revoke(@PathVariable String type, @PathVariable String uuid) {
AuthRequest authRequest = factory.get(type); JustAuthUser justAuthUser = justAuthUserService.getByUuid(uuid);
if (null == justAuthUser) {
return Response.error("用户不存在");
} AuthResponse<AuthToken> response = null;
try {
response = authRequest.revoke(justAuthUser.getToken());
if (response.ok()) {
justAuthUserService.removeByUuid(justAuthUser.getUuid());
return Response.success("用户 [" + justAuthUser.getUsername() + "] 的 授权状态 已收回!");
}
return Response.error("用户 [" + justAuthUser.getUsername() + "] 的 授权状态 收回失败!" + response.getMsg());
} catch (AuthException e) {
return Response.error(e.getErrorMsg());
}
} /**
* 刷新
*
* @param type 第三方系统类型,例如:gitee/baidu
* @param uuid 用户uuid
* @return
*/
@SuppressWarnings("unchecked")
@RequestMapping("/refresh/{type}/{uuid}")
@ResponseBody
public Response refresh(@PathVariable String type, @PathVariable String uuid) {
AuthRequest authRequest = factory.get(type); JustAuthUser justAuthUser = justAuthUserService.getByUuid(uuid);
if (null == justAuthUser) {
return Response.error("用户不存在");
} AuthResponse<AuthToken> response = null;
try {
response = authRequest.refresh(justAuthUser.getToken());
if (response.ok()) {
justAuthUser.setToken(response.getData());
justAuthUserService.saveOrUpdate(justAuthUser);
return Response.success("用户 [" + justAuthUser.getUsername() + "] 的 access token 已刷新!新的 accessToken: "
+ response.getData().getAccessToken());
}
return Response.error("用户 [" + justAuthUser.getUsername() + "] 的 access token 刷新失败!" + response.getMsg());
} catch (AuthException e) {
return Response.error(e.getErrorMsg());
}
} }
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class JustAuthApplication { public static void main(String[] args) {
SpringApplication.run(JustAuthApplication.class, args);
} }
  • 创建配置文件

      在resources目录下创建application.yml,其中client-idclient-secret待在对应的开放平台创建应用后回填。
server:
port: 8443 spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/justauth?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: root
password: 123456
redis:
host: 127.0.0.1
port: 6379
password: 123456
# 连接超时时间(记得添加单位,Duration)
timeout: 2000ms
# Redis默认情况下有16个分片,这里配置具体使用的分片
database: 0
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
maxActive: 8
# 连接池中的最大空闲连接 默认 8
maxIdle: 8 # Mybatis-plus配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
global-config:
db-config:
id-type: AUTO
configuration:
# 打印sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志配置
logging:
level:
com.xkcoding: debug # 第三方系统认证
justauth:
enabled: true
type:
BAIDU:
client-id: xxxxxx
client-secret: xxxxxx
redirect-uri: http://127.0.0.1:8443/oauth/baidu/callback
GITEE:
client-id: xxx
client-secret: xxx
redirect-uri: http://127.0.0.1:8443/oauth/gitee/callback
cache:
# 缓存类型(default-使用JustAuth内置的缓存、redis-使用Redis缓存、custom-自定义缓存)
type: redis
# 缓存前缀,目前只对redis缓存生效,默认 JUSTAUTH::STATE::
prefix: 'JUATAUTH::STATE::'
# 超时时长,目前只对redis缓存生效,默认3分钟
timeout: 3m

4. 创建应用

  • 百度创建应用
  1. 注册百度开发者账号。

    1.1 注册开发者账号:百度Passport
  2. 创建第三方授权应用

    2.1 配置应用:开发者服务管理

    2.2 创建工程



    2.3 安全设置



      将配置文件中justauth.type.BAIDU.redirect-uri的值http://127.0.0.1:8443/oauth/baidu/callback复制到授权回调页文本框中。

    2.4 信息回填

      将API KeySecret Key复制到配置文件中的justauth.type.BAIDU.client-idjustauth.type.BAIDU.client-secret属性。

    其他第三方平台创建请浏览:集成第三方

5. 测试

  • 登录

      浏览器访问地址:http://127.0.0.1:8443/oauth/login/baidu,即可获取到对应信息。

      请观察数据库及Redis中数据变化。
  • 刷新Token

      浏览器访问地址:http://127.0.0.1:8443/oauth/refresh/baidu/[uuid],其中uuid为数据库表中的uuid。

      目前插件中已实现的可刷新的第三方应用有限,请查看AuthRequest.refresh方法的实现类。

      请观察Redis中数据变化。
  • 移除Token

      浏览器访问地址:http://127.0.0.1:8443/oauth/revoke/baidu/[uuid],其中uuid为数据库表中的uuid。

      目前插件中已实现的可刷新的第三方应用有限,请查看AuthRequest.revoke方法的实现类。

      请观察数据库及Redis中数据变化。

6. 项目地址

  spring-boot-justauth-demo

SpringBoot基于JustAuth实现第三方授权登录的更多相关文章

  1. 【转】【Android应用开发详解】第01期:第三方授权认证(一)实现第三方授权登录、分享以及获取用户资料

    转载请注明出处:http://blog.csdn.net/yangyu20121224/article/details/9057257 由于公司项目的需要,要实现在项目中使用第三方授权登录以及分享文字 ...

  2. 【Android应用开发详解】实现第三方授权登录、分享以及获取用户资料

      由于公司项目的需要,要实现在项目中使用第三方授权登录以及分享文字和图片等这样的效果,几经波折,查阅了一番资料,做了一个Demo.实现起来的效果还是不错的,不敢独享,决定写一个总结的教程,供大家互相 ...

  3. 使用ShareSDK实现第三方授权登录、分享以及获取用户资料效果,项目中包含:源码+效果图+项目结构图

    [Android应用开发详解]第01期:第三方授权认证(一)实现第三方授权登录.分享以及获取用户资料   由于公司项目的需要,要实现在项目中使用第三方授权登录以及分享文字和图片等这样的效果,几经波折, ...

  4. QQ第三方授权登录OAuth2.0实现(Java)

    准备材料 1.已经备案好的域名 2.服务器(域名和服务器为统一主体或域名已接入服务器) 3.QQ号 4.开发流程:https://wiki.connect.qq.com/%E5%87%86%E5%A4 ...

  5. SPA+.NET Core3.1 GitHub第三方授权登录 使用AspNet.Security.OAuth.GitHub

    GitHub第三方授权登录 使用SPA+.NET Core3.1实现 GitHub第三方授权登录 类似使用AspNet.Security.OAuth.GitHub,前端使用如下:VUE+Vue-Rou ...

  6. github 授权登录教程与如何设计第三方授权登录的用户表

    需求:在网站上想评论一篇文章,而评论文章是要用户注册与登录的,那么怎么免去这麻烦的步骤呢?答案是通过第三方授权登录.本文讲解的就是 github 授权登录的教程. 效果体验地址:http://biao ...

  7. Github 第三方授权登录教程

    Github 第三方授权登录教程 ####大致流程图 ####1.首先注册一个github帐号,Applications>Developer applications>Register a ...

  8. .NET Core+QQ第三方授权登录

    安装包 dotnet add package AspNet.Security.OAuth.QQ 接上文GitHub第三方授权登录 申请过程不介绍了,申请者资料,个人也是可以申请成功的. 这时候有二个参 ...

  9. C# winform C/S WebBrowser qq第三方授权登录

    qq的授权登录,跟微信相似,不同的地方是: 1 申请appid与appkey的时候,注意填写回调地址. 2 这里可以在WebBrowser的是Navigated事件中直接得到Access Token, ...

随机推荐

  1. SNMP介绍及使用,超有用,建议收藏!

    写在前面 如果你是对SNMP完全不了解,或者只想学习如何使用现成的SNMP工具,那你找对了文章,但如果你希望学习SNMP具体协议内容,推荐阅读官方的RFC文档. 1. 简介 SNMP(Simple N ...

  2. FL Studio 插件使用教程 —— 3x Osc(上)

    在FL Studio20 中,3x Osc是继TS404插件之后资历最老的插件之一,也是FL Studio20 中最重要.使用率最高的插件之一.相比别的FL Studio20内置插件,3x Osc 相 ...

  3. 如何用EasyRecovery恢复U盘乱码的文件

    大多数用户在遇到U盘文件乱码的情况时,都显得有点手足无措,甚至会删除乱码文件,这样就导致了乱码文件数据的丢失.针对这种情况,我们应该怎么正确处理呢? 图1:U盘乱码 U盘文件乱码原因: 想要解决问题, ...

  4. bash反弹shell检测

    1.进程 file descriptor 异常检测 检测 file descriptor 是否指向一个socket 以重定向+/dev/tcp Bash反弹Shell攻击方式为例,这类反弹shell的 ...

  5. [NOIP2013][LGOJ P1967]货车运输

    Problem Link 题目描述 A国有n座城市,编号从1到n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重 ...

  6. EdgeConnect: Structure Guided Image Inpainting using Edge Prediction

    论文 pytorch 引言 语义分割获取边缘信息指导修复其二 存在的问题:之前方法能够生成具有有意义结构的缺失区域,但生成的区域往往模糊或边缘部分存在伪影. 提出问题:提出了一个两阶段的模型,将inp ...

  7. [Android systrace系列] 抓取开机过程systrace

    ------------------------------------------------------------------------- 这篇文章的小目标:了解抓取开机过程systrace的 ...

  8. 11_SurfaceView绘图

    学习SurfaceView绘图API的使用方法,为接下来使用SurfaceView开发游戏做准备. 1 package com.example.surfaceview; 2 3 import andr ...

  9. 面经手册 · 第20篇《Thread 线程,状态转换、方法使用、原理分析》

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

  10. AOV图与拓扑排序&AOE图与关键路径

    AOV网:所有的工程或者某种流程可以分为若干个小的工程或阶段,这些小的工程或阶段就称为活动.若以图中的顶点来表示活动,有向边表示活动之间的优先关系,则这样活动在顶点上的有向图称为AOV网. 拓扑排序算 ...