
作者:小傅哥 博客:https://bugstack.cn
❝沉淀、分享、成长,让自己和他人都能有所收获!😜 ❞
大家好,我是技术UP主小傅哥。
你知道互联网大厂最怕的是什么吗?但凡有点这样的风吹草动,我们就要花费大量的时间进行修复和上线。一点都不敢耽误,对于紧急类型的,基本当天发现,当天就要升级上线。那是什么问题呢?🤔
其实最怕的就是各类组件漏洞!
有这么一个东西,13scan - 安全漏洞扫描 它可以扫描出系统组件的各项存在的漏洞,给出整改建议。因为这些漏洞的存在,就可能让不法用户通过接口调用到系统数据。比如,随意输入个订单号,就知道是谁,什么时间、购买的什么、地址在哪。这是非常可怕的。
所以,在互联网大厂中,会有统一的安全授权认证服务 OAuth2。这样即使有外部对接的系统确实需要授权获得用户的数据,也可以在可靠的范围内进行授权和使用。
那么,OAuth2 是个啥呢?🤔 本节我们来分享下并做个代码案例运行验证。
OAuth 2.0 的标准 RFC 6749,解释了 OAuth 是什么。

官网:https://datatracker.ietf.org/doc/html/rfc6749
OAuth 2.0 本身是一种开放标准,不是一个具体的服务类组件,而是一种标准。旨在为用户提供授权,允许第三方应用程序访问用户在某个服务提供者(如社交网络或云服务)上的信息,而无需将用户的凭证(如用户名和密码)透露给这些应用程序。OAuth 2.0 主要用于授权,而不是身份验证。
而 Spring 中 OAuth2 就是对这套标准的具体实现,但这不是唯一实现,你甚至可以通过这套标准做一套自己的 OAuth2 授权框架。
大家在日常的生活中使用互联网类的产品,包括;购物、视频、出行等,都可能收到活动类的短信,问你是否要参与一个这样的活动,如果参与则需要点击授权允许。那么这个过程就有 OAuth2 的授权使用。如图;

在看 OAuth2 之前,可以代入的思考下,如果是你做一个认证授权框架,你会怎么做。其实你在最开始学习编程使用账号密码在数据库里匹配验证,完成后生成一个 Token 让前端保存到 Cookie 里,之后每次请求后端都携带上这个 Cookie 进行校验。
其实这个模型就是认证授权框架。认证;使用账密证明你是你,授权,则通过账密分配一个Token,让使用放通过 Token 进行数据访问。
那么,OAuth2 作为认证授权框架,提供了四种授权访问,包括;
这四种授权方式,逐渐减弱。不过,无论那种授权方式,在第三方应用申请可调用数据的令牌前,都需要先完成系统备案,验明自身身份。包括客户端 ID、客户端秘钥 Client Secret。
授权模式:指第三方应用先申请一个授权码,之后再使用该码获得令牌。授权码模式通常用于具有浏览器界面的应用程序,尤其是在需要用户交互的场景下,例如传统的Web应用。由于使用了重定向和授权码,维护了更高的安全性。

工作流程:
隐式模式主要适用于在Web浏览器中运行的单页应用(SPA)等不安全的客户端环境,因为不需要后台服务器交换授权码,简化了流程。然而,隐式模式由于直接暴露令牌,安全性较低,不建议用于敏感操作。

工作流程:
密码模式适用于用户信任客户端的情况,如用户通过原生应用(移动应用)访问服务。在此情况下,客户端直接处理用户的凭据,使用时要确保应用的安全性。

工作流程:
客户端凭证模式主要用于服务器与服务器之间的通信,如后台服务相互访问API,或者服务自身需要访问其资源。适用于没有用户上下文的场景,更多用于机器对机器(M2M)通信。

工作流程
有了上面的概念,我们再来看个实际的案例工程,验证四种授权模式。环境信息如下;

除了 OAuth2 关于 Spring Security 部分已经在前面的课程讲解过,可以补充学习。https://bugstack.cn/md/road-map/spring-security.html
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String clientId;
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof User) {
User clientUser = (User) principal;
clientId = clientUser.getUsername();
} else if (principal instanceof OauthAccountUserDetails) {
getClientIdByRequest();
return (OauthAccountUserDetails) principal;
} else {
throw new UnsupportedOperationException();
}
} else {
clientId = getClientIdByRequest();
}
// 校验用户 - 直接从数据库查询
OauthAccount account = oauthAccountDao.loadUserByUsername(clientId, username);
if (account == null || !account.getAccountNonDeleted()) {
throw new UsernameNotFoundException("err user is not found!");
}
return new OauthAccountUserDetails(account, new ArrayList<>());
}
@Bean
public TokenEnhancer additionalInformationTokenEnhancer() {
return (accessToken, authentication) -> {
Map<String, Object> information = new HashMap<>(8);
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication instanceof UsernamePasswordAuthenticationToken) {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) userAuthentication;
Object principal = token.getPrincipal();
if (principal instanceof OauthAccountUserDetails) {
OauthAccountUserDetails userDetails = (OauthAccountUserDetails) token.getPrincipal();
OauthAccount oauthAccount = userDetails.getOauthAccount();
information.put("account_info", UserAccountVO.builder()
.id(oauthAccount.getId())
.clientId(oauthAccount.getClientId())
.username(oauthAccount.getUsername())
.mobile(oauthAccount.getMobile())
.email(oauthAccount.getEmail())
.build());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(information);
}
}
return accessToken;
};
}

@Resource
private PasswordEncoder passwordEncoder;
@Test
public void test_passwordEncoder() {
log.info("测试结果:{}", passwordEncoder.encode("123456"));
}
在测试之前,你要启动服务,确保运行没问题。启动前配置数据库连接。
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.1.109:13306/xfg-dev-tech-oauth2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&useSSL=true
driver-class-name: com.mysql.cj.jdbc.Driver

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYmlnLW1hcmtldC1hcHAiXSwic2NvcGUiOlsicmVhZCIsIndyaXRlIl0sImV4cCI6MTczNjY3OTA4MCwiYXV0aG9yaXRpZXMiOlsidXNlciJdLCJqdGkiOiI4NWY0YjY2Ni1mNDliLTRiNGEtOTM1Ni0xYjRiMTVmZmI5MWEiLCJjbGllbnRfaWQiOiJ4Zmctc3R1ZGlvIn0.CqMOMbBkHMnQicpkBEeqMyJEp9HbSiGgXoYUke_PWtI",
"token_type": "bearer",
"expires_in": 7198,
"scope": "read write",
"jti": "85f4b666-f49b-4b4a-9356-1b4b15ffb91a"
}
xfg-studio/123456
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYmlnLW1hcmtldC1hcHAiXSwiZXhwIjoxNzM2Njc5MTQxLCJ1c2VyX25hbWUiOiJ4aWFvZnVnZSIsImp0aSI6ImVhZWMzZmQ0LTViOTAtNGRhNy1hODQ1LTA2MDFmMjJiNDc2ZCIsImNsaWVudF9pZCI6InhmZy1zdHVkaW8iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXX0.JgUxx6_aHqCBxuvYXvekw-ZW5pPnSw5LEKlfsd4qVyI",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYmlnLW1hcmtldC1hcHAiXSwidXNlcl9uYW1lIjoieGlhb2Z1Z2UiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXRpIjoiZWFlYzNmZDQtNWI5MC00ZGE3LWE4NDUtMDYwMWYyMmI0NzZkIiwiZXhwIjoxNzM5MjYzOTQyLCJqdGkiOiI5ZDc4ZjVjZS0xZTMwLTRiZTYtYWUyNi01NjY1NWQ4YjYzZjIiLCJjbGllbnRfaWQiOiJ4Zmctc3R1ZGlvIn0.8gMfqhBnc4wI9BsRENu_16RmZFqeCWVSyWcF4B9nA1I",
"expires_in": 7198,
"scope": "read write",
"account_info": {
"id": null,
"clientId": "xfg-studio",
"username": "xiaofuge",
"mobile": "13500002222",
"email": "523088136@qq.com"
}
}
xfg-studio/123456grant_type = password、username = xiaofuge、password = 123456
{
"status": 200,
"message": "hi login success!"
}
xfg-studio/123456username = xiaofuge、password = 123456JSESSIONID=9000E64733AA6E947054AC4326C91AF8 这个 cookie 用于获取授权码


client_id = xfg-studio、response_type = code、grant_type=authorization_code之后刷新令牌、检查令牌,就可以单独测试了。如果部署到云服务器,那么还可以走浏览器访问,单独有一个获取令牌的操作,之后再跳转地址。
- END -
加入小傅哥的星球「码农会锁」,💐斩获大厂Offer!阅读500+份简历和评审,学习6个业务项目;MVC+DDD,双架构开发小型电商、大营销(超级大课)、OpenAI 大模型应用、Lottery、IM、AI 问答助手。7个组件项目;OpenAI 代码评审、BCP 透视业务监控、动态线程池、支付SDK设计和开发、API网关、SpringBoot Starter、IDEA Plugin 插件开发。1套源码课程、1套基础教程、1到云服务器教程以及各类场景解决方案。
小傅哥有那么多课程内容,我加入后都可以学习吗?可以!
好啦,这就是小傅哥的技术列车🚌,嘎嘎实惠!🤔 几乎没有哪个大厂架构师,用这么一个普惠的价格手把手的教大家学习了。
星球「码农会锁」- 加入后从课程入口进入项目学习
星球全程手把手指导教学,遇到技术问题帮忙排查代码。已经有很多伙伴开始学起来了,还有大家交的作业笔记。有了的项目驱动学习,清晰的目标感,大家冲起来也有了更明确的方向!干干干!!!