SpringSecurity+OAuth2权限管理

Spring Security

零  介绍

功能:

  • 身份认证(authentication)

  • 授权(authorization)

  • 防御常见攻击(protection against common attacks)

身份认证:

  • 身份认证是验证谁正在访问系统资源,判断用户是否为合法用户。认证用户的常见方式是要求用户输入用户名和密码。

授权:

  • 用户进行身份认证后,系统会控制谁能访问哪些资源,这个过程叫做授权。用户无法访问没有权限的资源。

防御常见攻击:

  • CSRF

  • HTTP Headers

  • HTTP Requests

一  快速入门

1  引入依赖

Spring Web、Spring Security、Thymeleaf

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId></dependency>

2  编写Controller

@Controller
public class IndexController {@GetMapping("/")public String index() {return "index";}
}

3  创建index.html

在路径resources/templates中创建index.html

<html xmlns:th="https://www.thymeleaf.org">
<head><title>Hello Security!</title>
</head>
<body>
<h1>Hello Security</h1>
<!--通过使用@{/logout},Thymeleaf将自动处理生成正确的URL,以适应当前的上下文路径。
这样,无论应用程序部署在哪个上下文路径下,生成的URL都能正确地指向注销功能。-->
<a th:href="@{/logout}">Log Out</a>
</body>
</html>

4  运行

因为没有配置tomcat端口,所以走默认8080,我们访问localhost:8080后,会自动跳转认证页面。

有随机生成的密码在后台控制台输出。默认用户名是user

点击登陆,就来到了我们所写的页面

5  分析:Spring Security默认做了什么

  • 保护应用程序URL,要求对应用程序的任何交互进行身份验证。

  • 程序启动时生成一个默认用户“user”。

  • 生成一个默认的随机密码,并将此密码记录在控制台上。

  • 生成默认的登录表单和注销页面。

  • 提供基于表单的登录和注销流程。

  • 对于Web请求,重定向到登录页面;

  • 对于服务请求,返回401未经授权。

  • 处理跨站请求伪造(CSRF)攻击。

  • 处理会话劫持攻击。

  • 写入Strict-Transport-Security以确保HTTPS。

  • 写入X-Content-Type-Options以处理嗅探攻击。

  • 写入Cache Control头来保护经过身份验证的资源。

  • 写入X-Frame-Options以处理点击劫持攻击。

二 Spring Security 的底层原理

官方文档: https://docs.spring.io/spring-security/reference/servlet/architecture.html

Spring Security之所以默认帮助我们做了那么多事情,它的底层原理是传统的Servlet过滤器

1  Filter

下图展示了处理一个Http请求时,过滤器和Servlet的工作流程:

客户端发送请求,经过一系列过滤器最终请求到servlet上,也就是后端

2  DelegatingFilterProxy

DelegatingFilterProxy 是 Spring Security 提供的一个 Filter 实现,可以在 Servlet 容器和 Spring 容器之间建立桥梁。通过使用 DelegatingFilterProxy,这样就可以将Servlet容器中的 Filter 实例放在 Spring 容器中管理。

3  FilterChainProxy

复杂的业务中不可能只有一个过滤器。因此FilterChainProxy是Spring Security提供的一个特殊的Filter,它允许通过SecurityFilterChain将过滤器的工作委托给多个Bean Filter实例。

4  SecurityFilterChain

SecurityFilterChain 被 FilterChainProxy 使用,负责查找当前的请求需要执行的Security Filter列表。

5  Multiple SecurityFilterChain

可以有多个SecurityFilterChain的配置,FilterChainProxy决定使用哪个SecurityFilterChain。如果请求的URL是/api/messages/,它首先匹配SecurityFilterChain0的模式/api/**,因此只调用SecurityFilterChain 0。假设没有其他SecurityFilterChain实例匹配,那么将调用SecurityFilterChain n。

6  DefaultSecurityFilterChain

SecurityFilterChain接口的实现,加载了默认的16个Filter

三  Spring Security自定义配置

1  基于内存的用户认证

1.1、创建自定义配置

实际开发的过程中,我们需要应用程序更加灵活,可以在SpringSecurity中创建自定义配置文件

官方文档:Java自定义配置

UserDetailsService用来管理用户信息,InMemoryUserDetailsManagerUserDetailsService的一个实现,用来管理基于内存的用户信息。

创建一个WebSecurityConfig文件:

定义一个@Bean,类型是UserDetailsService,实现是InMemoryUserDetailsManager

package com.itheima.learnspringsercuity_oauth.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;@Configuration
@EnableWebSecurity//Spring项目总需要添加此注解,SpringBoot项目中不需要
public class WebSecurityConfig {@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser( //此行设置断点可以查看创建的user对象User.withDefaultPasswordEncoder().username("shuaiqicjx") //自定义用户名.password("123") //自定义密码.roles("USER") //自定义角色.build());return manager;}
}

测试:使用用户名shuaiqicjx,密码123登录

1.2、基于内存的用户认证流程
  • 程序启动时:

    • 创建InMemoryUserDetailsManager对象

    • 创建User对象,封装用户名密码

    • 使用InMemoryUserDetailsManagerUser存入内存

  • 校验用户时:

    • SpringSecurity自动使用InMemoryUserDetailsManagerloadUserByUsername方法从内存中获取User对象

    • UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中将用户输入的用户名密码和从内存中获取到的用户信息进行比较,进行用户认证

InMemoryUserDetailsManager和UsernamePasswordAuthenticationFilter组件之间的联系如下:

  • 当用户尝试登录时,UsernamePasswordAuthenticationFilter会拦截登录请求,并提取用户名和密码。
  • 然后,UsernamePasswordAuthenticationFilter会创建一个Authentication对象(通常是UsernamePasswordAuthenticationToken),包含用户提交的用户名和密码。
  • 接下来,UsernamePasswordAuthenticationFilter会调用AuthenticationManager来执行实际的认证过程。
  • AuthenticationManager会委托给配置的UserDetailsService(在这种情况下是InMemoryUserDetailsManager)来加载用户的详细信息。
  • InMemoryUserDetailsManagerloadUserByUsername方法会被调用来根据用户名查找用户详细信息。
  • 找到用户详细信息后,UsernamePasswordAuthenticationFilter会使用这些信息来验证用户提交的密码是否正确。
  • 如果密码验证成功,用户被认为是认证通过的,Spring Security会创建一个带有用户权限信息的Authentication对象,并将其添加到安全上下文中,允许用户访问受保护的资源。

2  基于数据库的用户认证

2.1  创建数据库
-- 创建数据库
CREATE DATABASE `security-demo`;
USE `security-demo`;-- 创建用户表
CREATE TABLE `user`(`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,`username` VARCHAR(50) DEFAULT NULL ,`password` VARCHAR(500) DEFAULT NULL,`enabled` BOOLEAN NOT NULL
);
-- 唯一索引
CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`); -- 插入用户数据(密码是 "abc" )
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES
('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);

2.2  引入依赖

准备mysql链接,mp,lombok

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version>
</dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId></exclusion></exclusions>
</dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.3</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

2.3  配置数据源

根据自己的实际情况填写

#MySQL数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security-demo
spring.datasource.username=root
spring.datasource.password=123456
#SQL日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

2.4  创建实体类

创建与数据库相对应的实体类

@Data
public class User {@TableId(value = "id", type = IdType.AUTO)private Integer id;private String username;private String password;private Boolean enabled;}

2.5  Mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

2.6  Service

public interface UserService extends IService<User> {
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

2.7  Controller
@RestController
@RequestMapping("/user")
public class UserController {@Resourcepublic UserService userService;@GetMapping("/list")public List<User> getList(){return userService.list();}
}

2.8  定义 DBUserDetailsManager
  • 程序启动时:

    • 创建DBUserDetailsManager类,实现接口 UserDetailsManager, UserDetailsPasswordService

    • 在应用程序中初始化这个类的对象

  • 校验用户时:

    • SpringSecurity自动使用DBUserDetailsManagerloadUserByUsername方法从数据库中获取User对象

    • UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中将用户输入的用户名密码和从数据库中获取到的用户信息进行比较,进行用户认证

package com.itheima.learnspringsercuity_oauth.config;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.itheima.learnspringsercuity_oauth.domain.User;
import com.itheima.learnspringsercuity_oauth.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;import java.util.ArrayList;
import java.util.Collection;public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {@Resourceprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", username);User user = userMapper.selectOne(queryWrapper);if (user == null) {throw new UsernameNotFoundException(username);} else {Collection<GrantedAuthority> authorities = new ArrayList<>();return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.getEnabled(),true, //用户账号是否过期true, //用户凭证是否过期true, //用户是否未被锁定authorities); //权限列表}}@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {return null;}@Overridepublic void createUser(UserDetails user) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}
}

2.9  初始化UserDetailsService

修改WebSecurityConfig中的userDetailsService方法如下

@Configuration
@EnableWebSecurity//Spring项目总需要添加此注解,SpringBoot项目中不需要
public class WebSecurityConfig {@Beanpublic UserDetailsService userDetailsService() {DBUserDetailsManager manager = new DBUserDetailsManager();return manager;}
}

3  小总结

无论是基于内存的用户认证还是基于数据库的用户认证

基于内存:

  • 校验用户时:

    • SpringSecurity自动使用InMemoryUserDetailsManagerloadUserByUsername

基于数据库

  • 校验用户时:

    • SpringSecurity自动使用DBUserDetailsManagerloadUserByUsername方法

我们可以看到他们本质上调用了同一个loadUserByUsername的方法。

这是因为InMemoryUserDetailsManager和DBUserDetailsManager本质上继承了UserDetailsManager这个类,只不过是前者是new出来的对象,这个对象的类继承,而后者是我们自己创建出来的对象,我们自己去继承了这个类

InMemoryUserDetailsManager:

DBUserDetailsManager:

而这个UserDetailsManager类的父结构就有loadUserByUsername这个方法

测试:

我们可以访问localhost:8080这个网址,使用数据库中配置的用户名和密码进行登录,可以登陆成功

四  SpringSecurity的默认配置

在WebSecurityConfig中添加如下配置

这是默认配置,就算我们没有配置,Security也会自动执行相关逻辑

formLogin,负责整个登入登出功能

默认的链路逻辑

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {//authorizeRequests():开启授权保护//anyRequest():对所有请求开启授权保护//authenticated():已认证请求会自动被授权http.authorizeRequests(authorize -> authorize.anyRequest().authenticated()).formLogin(withDefaults())//表单授权方式.httpBasic(withDefaults());//基本授权方式return http.build();
}

五  添加用户功能

0  添加swagger测试

<!--swagger测试-->
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.1.0</version>
</dependency>

1  Controller

UserController中添加方法

@PostMapping("/add")public void add(@RequestBody User user){userService.saveUserDetails(user);log.info("添加用户成功");}

2  Service

UserService接口中添加方法

void saveUserDetails(User user);

UserServiceImpl实现中添加方法

@Resource
private DBUserDetailsManager dbUserDetailsManager;@Override
public void saveUserDetails(User user) {UserDetails userDetails = org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder().username(user.getUsername()) //自定义用户名.password(user.getPassword()) //自定义密码.build();dbUserDetailsManager.createUser(userDetails);}

3  修改配置

DBUserDetailsManager中修改createUser方法

@Overridepublic void createUser(UserDetails userDetails) {User user = new User();user.setEnabled(true);user.setUsername(userDetails.getUsername());user.setPassword(userDetails.getPassword());userMapper.insert(user);log.info("mapper插入用户成功");}

4  关闭csrf攻击防御

默认情况下SpringSecurity开启了csrf攻击防御的功能,这要求请求参数中必须有一个隐藏的_csrf字段,如下:

在filterChain方法中添加如下代码,关闭csrf攻击防御

//关闭csrf攻击防御
http.csrf((csrf) -> {csrf.disable();
});

5  测试

六  密码加密算法

1  密码加密方式

明文密码:

最初,密码以明文形式存储在数据库中。但是恶意用户可能会通过SQL注入等手段获取到明文密码,或者程序员将数据库数据泄露的情况也可能发生。

Hash算法:

Spring Security的PasswordEncoder接口用于对密码进行单向转换,从而将密码安全地存储。对密码单向转换需要用到哈希算法,例如MD5、SHA-256、SHA-512等,哈希算法是单向的,只能加密,不能解密

因此,数据库中存储的是单向转换后的密码,Spring Security在进行用户身份验证时需要将用户输入的密码进行单向转换,然后与数据库的密码进行比较。

因此,如果发生数据泄露,只有密码的单向哈希会被暴露。由于哈希是单向的,并且在给定哈希的情况下只能通过暴力破解的方式猜测密码

彩虹表:

用户恶意创建用于破解哈希密码的查找表称为彩虹表

彩虹表就是一个庞大的、针对各种可能的字母组合预先生成的哈希值集合,有了它可以快速破解各类密码。越是复杂的密码,需要的彩虹表就越大,主流的彩虹表都是100G以上,目前主要的算法有LM, NTLM, MD5, SHA1, MYSQLSHA1, HALFLMCHALL, NTLMCHALL, ORACLE-SYSTEM, MD5-HALF。

加盐密码:

为了减轻彩虹表的效果,开发人员开始使用加盐密码。不再只使用密码作为哈希函数的输入,而是为每个用户的密码生成随机字节(称为盐)。盐和用户的密码将一起经过哈希函数运算,生成一个唯一的哈希。盐将以明文形式与用户的密码一起存储。然后,当用户尝试进行身份验证时,盐和用户输入的密码一起经过哈希函数运算,再与存储的密码进行比较。唯一的盐意味着彩虹表不再有效,因为对于每个盐和密码的组合,哈希都是不同的。

自适应单向函数:

随着硬件的不断发展,加盐哈希也不再安全。原因是,计算机可以每秒执行数十亿次哈希计算。这意味着我们可以轻松地破解每个密码。

现在,开发人员开始使用自适应单向函数来存储密码。使用自适应单向函数验证密码时,故意占用资源(故意使用大量的CPU、内存或其他资源)。自适应单向函数允许配置一个“工作因子”,随着硬件的改进而增加。我们建议将“工作因子”调整到系统中验证密码需要约一秒钟的时间。这种权衡是为了让攻击者难以破解密码

自适应单向函数包括bcrypt、PBKDF2、scrypt和argon2

2  PasswordEncoder

BCryptPasswordEncoder

使用广泛支持的bcrypt算法来对密码进行哈希。为了增加对密码破解的抵抗力,bcrypt故意设计得较慢。和其他自适应单向函数一样,应该调整其参数,使其在您的系统上验证一个密码大约需要1秒的时间。BCryptPasswordEncoder的默认实现使用强度10。建议您在自己的系统上调整和测试强度参数,以便验证密码时大约需要1秒的时间。

Argon2PasswordEncoder

使用Argon2算法对密码进行哈希处理。Argon2是密码哈希比赛的获胜者。为了防止在自定义硬件上进行密码破解,Argon2是一种故意缓慢的算法,需要大量内存。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。当前的Argon2PasswordEncoder实现需要使用BouncyCastle库。

Pbkdf2PasswordEncoder

使用PBKDF2算法对密码进行哈希处理。为了防止密码破解,PBKDF2是一种故意缓慢的算法。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。当需要FIPS认证时,这种算法是一个很好的选择。

SCryptPasswordEncoder

使用scrypt算法对密码进行哈希处理。为了防止在自定义硬件上进行密码破解,scrypt是一种故意缓慢的算法,需要大量内存。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。

3  密码加密测试

在测试类中编写一个测试方法,自己给调整工作因子的大小,来看看具体验证的速度是多少

@Test
void testPassword() {// 工作因子,默认值是10,最小值是4,最大值是31,值越大运算速度越慢PasswordEncoder encoder = new BCryptPasswordEncoder(4);//明文:"password"//密文:result,即使明文密码相同,每次生成的密文也不一致String result = encoder.encode("password");System.out.println(result);//密码校验Assert.isTrue(encoder.matches("password", result), "密码不一致");
}

4 DelegatingPasswordEncoder

  • 表中存储的密码形式:{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW

  • 通过如下源码可以知道:可以通过{bcrypt}前缀动态获取和密码的形式类型一致的PasswordEncoder对象

  • 目的:方便随时做密码策略的升级,兼容数据库中的老版本密码策略生成的密码

5、RSA证书

1 生成证书

首先通过终端进入到resources目录,然后执行证书生成命令。

keytool -genkeypair -alias 01star -keyalg RSA -keypass 123456 -keystore jwt.jks -storepass 123456 -validity 3650
keytool -importkeystore -srckeystore jwt.jks -destkeystore jwt.jks -deststoretype pkcs12

参数帮助说明

keytool密钥和证书管理工具
命令:
-certreq            生成证书请求
-changealias        更改条目的别名
-delete             删除条目
-exportcert         导出证书
-genkeypair         生成密钥对
-genseckey          生成密钥
-gencert            根据证书请求生成证书
-importcert         导入证书或证书链
-importpass         导入口令
-importkeystore     从其他密钥库导入一个或所有条目
-keypasswd          更改条目的密钥口令
-list               列出密钥库中的条目
-printcert          打印证书内容
-printcertreq       打印证书请求的内容
-printcrl           打印 CRL 文件的内容
-storepasswd        更改密钥库的存储口令
使用 "keytool -command_name -help" 获取 command_name 的用法keytool -genkeypair [OPTION]...
生成密钥对选项: 
-alias <alias>                  要处理的条目的别名 
-keyalg <keyalg>                密钥算法名称 
-keysize <keysize>              密钥位大小 
-sigalg <sigalg>                签名算法名称 
-destalias <destalias>          目标别名 
-dname <dname>                  唯一判别名 
-startdate <startdate>          证书有效期开始日期/时间 
-ext <value>                    X.509 扩展 
-validity <valDays>             有效天数 
-keypass <arg>                  密钥口令 
-keystore <keystore>            密钥库名称 
-storepass <arg>                密钥库口令 
-storetype <storetype>          密钥库类型 
-providername <providername>    提供方名称 
-providerclass <providerclass>  提供方类名 
-providerarg <arg>              提供方参数 
-providerpath <pathlist>        提供方类路径 
-v                              详细输出 
-protected                      通过受保护的机制的口令使用 
"keytool -help" 获取所有可用命令

2 提取公钥

通过openssl可以提取PUBLIC KEY格式,windows下需要安装 openssl

Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions

keytool -list -rfc --keystore jwt.jks | openssl x509 -inform pem -pubkey
3 提取私钥

需要把证书先转成pfx格式

keytool -v -importkeystore -srckeystore jwt.jks -srcstoretype jks -srcstorepass 123456 -destkeystore jwt.pfx -deststoretype pkcs12 -deststorepass 123456 -destkeypass 123456

提取私钥

#显示在屏幕上
openssl pkcs12 -in jwt.pfx -nocerts -nodes#输出到文件
openssl pkcs12 -in jwt.pfx -nocerts -nodes -out jwt.key

七  自定义登陆界面

1  创建登录Controller

@Controller
public class LoginController {@GetMapping("/login")public String login() {return "login";}
}

2  创建登录页面

resources/templates/login.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head><title>登录</title>
</head>
<body>
<h1>登录</h1>
<div th:if="${param.error}">错误的用户名和密码.</div><!--method必须为"post"-->
<!--th:action="@{/login}" ,
使用动态参数,表单中会自动生成_csrf隐藏字段,用于防止csrf攻击
login: 和登录页面保持一致即可,SpringSecurity自动进行登录认证-->
<form th:action="@{/login}" method="post"><div><!--name必须为"username"--><input type="text" name="username" placeholder="用户名"/></div><div><!--name必须为"password"--><input type="password" name="password" placeholder="密码"/></div><input type="submit" value="登录" />
</form>
</body>
</html>

3  配置SecurityFilterChain

.formLogin( form -> {form.loginPage("/login").permitAll() //登录页面无需授权即可访问.usernameParameter("username") //自定义表单用户名参数,默认是username.passwordParameter("password") //自定义表单密码参数,默认是password.failureUrl("/login?error") //登录失败的返回地址;
}); //使用表单授权方式

八  前后端分离

1  用户认证流程

  • 登录成功后调用:AuthenticationSuccessHandler

  • 登录失败后调用:AuthenticationFailureHandler

2  引入fastjson

<dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.37</version>
</dependency>

3  认证成功的响应

3.1、成功结果处理

在配置包下创建一个MyAuthenticationSuccessHandler 实现 AuthenticationSuccessHandler ,重写里面的方法。

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {//获取用户身份信息Object principal = authentication.getPrincipal();//创建结果对象HashMap result = new HashMap();result.put("code", 0);result.put("message", "登录成功");result.put("data", principal);//转换成json字符串String json = JSON.toJSONString(result);//返回响应response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}

3.2、SecurityFilterChain配置
form.successHandler(new MyAuthenticationSuccessHandler()) //认证成功时的处理

3.3  测试

4  认证失败响应

4.1  失败结果处理
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {//获取错误信息String localizedMessage = exception.getLocalizedMessage();//创建结果对象HashMap result = new HashMap();result.put("code", -1);result.put("message", localizedMessage);//转换成json字符串String json = JSON.toJSONString(result);//返回响应response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}

4.2 SecurityFilterChain配置
form.failureHandler(new MyAuthenticationFailureHandler()) //认证失败时的处理

4.3  测试

输入密码错误后返回的页面

5  请求未认证的接口

5.1  实现AuthenticationEntryPoint接口

当访问一个需要认证之后才能访问的接口的时候,Spring Security会使用AuthenticationEntryPoint将用户请求跳转到登录页面,要求用户提供登录凭证。

这里我们也希望系统返回json结果,因此我们定义类实现AuthenticationEntryPoint接口

public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {//获取错误信息//String localizedMessage = authException.getLocalizedMessage();//创建结果对象HashMap result = new HashMap();result.put("code", -1);result.put("message", "需要登录");//转换成json字符串String json = JSON.toJSONString(result);//返回响应response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}

5.2  SecurityFilterChain配置
//错误处理
http.exceptionHandling(exception  -> {exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());//请求未认证的接口
});

5.3  测试

同时有两个客户端登陆

6  跨域

跨域全称是跨域资源共享(Cross-Origin Resources Sharing,CORS),它是浏览器的保护机制,只允许网页请求统一域名下的服务,同一域名指=>协议、域名、端口号都要保持一致,如果有一项不同,那么就是跨域请求。在前后端分离的项目中,需要解决跨域的问题。

在SpringSecurity中解决跨域很简单,在配置文件中添加如下配置即可

//跨域
http.cors(withDefaults());

九  身份认证

1  用户认证信息

1.1、基本概念

在Spring Security框架中,SecurityContextHolder、SecurityContext、Authentication、Principal和Credential是一些与身份验证和授权相关的重要概念。它们之间的关系如下:

  1. SecurityContextHolder:SecurityContextHolder 是 Spring Security 存储已认证用户详细信息的地方。

  2. SecurityContext:SecurityContext 是从 SecurityContextHolder 获取的内容,包含当前已认证用户的 Authentication 信息。

  3. Authentication:Authentication 表示用户的身份认证信息。它包含了用户的Principal、Credential和Authority信息。

  4. Principal:表示用户的身份标识。它通常是一个表示用户的实体对象,例如用户名。Principal可以通过Authentication对象的getPrincipal()方法获取。

  5. Credentials:表示用户的凭证信息,例如密码、证书或其他认证凭据。Credential可以通过Authentication对象的getCredentials()方法获取。

  6. GrantedAuthority:表示用户被授予的权限

总结起来,SecurityContextHolder用于管理当前线程的安全上下文,存储已认证用户的详细信息,其中包含了SecurityContext对象,该对象包含了Authentication对象,后者表示用户的身份验证信息,包括Principal(用户的身份标识)和Credential(用户的凭证信息)。

1.2、在Controller中获取用户信息
@GetMapping("/getInfo")public Map getInfo(){System.out.println("index controller");SecurityContext context = SecurityContextHolder.getContext();//存储认证对象的上下文Authentication authentication = context.getAuthentication();//认证对象String username = authentication.getName();//用户名Object principal =authentication.getPrincipal();//身份Object credentials = authentication.getCredentials();//凭证(脱敏)Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();//权限System.out.println(username);System.out.println(principal);System.out.println(credentials);System.out.println(authorities);//创建结果对象HashMap result = new HashMap();result.put("code", 0);result.put("data", username);return result;}

2  会话并发处理

后登录的账号会使先登录的账号失效

2.1  实现处理器接口

实现接口SessionInformationExpiredStrategy

public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {@Overridepublic void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {//创建结果对象HashMap result = new HashMap();result.put("code", -1);result.put("message", "该账号已从其他设备登录");//转换成json字符串String json = JSON.toJSONString(result);HttpServletResponse response = event.getResponse();//返回响应response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}

2.2  SecurityFilterChain配置
//会话管理
http.sessionManagement(session -> {session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy());
});

2.3  测试

这里我们使用不同的浏览器来模拟不同的客户端,如果同时登陆两个客户端会报错

十  授权

授权管理的实现在SpringSecurity中非常灵活,可以帮助应用程序实现以下两种常见的授权需求:

  • 用户-权限-资源:例如张三的权限是添加用户、查看用户列表,李四的权限是查看用户列表

  • 用户-角色-权限-资源:例如 张三是角色是管理员、李四的角色是普通用户,管理员能做所有操作,普通用户只能查看信息

1、基于request的授权

1.1、用户-权限-资源

需求:

  • 具有USER_LIST权限的用户可以访问/user/list接口

  • 具有USER_ADD权限的用户可以访问/user/add接口

配置权限

SecurityFilterChain

配置了访问用户列表和添加用户的权限

//开启授权保护
http.authorizeRequests(authorize -> authorize//具有USER_LIST权限的用户可以访问/user/list.requestMatchers("/user/list").hasAuthority("USER_LIST")//具有USER_ADD权限的用户可以访问/user/add.requestMatchers("/user/add").hasAuthority("USER_ADD")//对所有请求开启授权保护.anyRequest()//已认证的请求会被自动授权.authenticated());

授予权限

仅授予访问用户列表的权限

Collection<GrantedAuthority> authorities = new ArrayList<>();// 添加权限authorities.add(new GrantedAuthority() {@Overridepublic String getAuthority() {return "USER_LIST";}});

请求未授权的接口

我们为请求请求未授权的接口后返回一个自定义的返回界面,返回自定义的数据

自定义一个类去实现AccessDeniedHandler 接口

package com.itheima.learnspringsercuity_oauth.config;import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;import java.io.IOException;
import java.util.HashMap;public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException, ServletException {//创建结果对象HashMap result = new HashMap();result.put("code", -1);result.put("message", "没有权限");//转换成json字符串String json = JSON.toJSONString(result);//返回响应response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}

SecurityFilterChain

http.exceptionHandling(exception  -> {exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());//请求未认证的接口exception.accessDeniedHandler(new MyAccessDeniedHandler());//请求未授权的接口});

测试

测试的时候为了方便,一个权限都没给,然后去请求用户的列表

去添加用户,结果因为没有权限出错

1.2、用户-角色-资源

需求:角色为ADMIN的用户才可以访问/user/**路径下的资源

配置角色

SecurityFilterChain

//开启授权保护
http.authorizeRequests(authorize -> authorize//具有管理员角色的用户可以访问/user/**.requestMatchers("/user/**").hasRole("ADMIN")//对所有请求开启授权保护.anyRequest()//已认证的请求会被自动授权.authenticated()
);

授予角色
org.springframework.security.core.userdetails.User.withUsername(user.getUsername()).password(user.getPassword()).roles("ADMIN").credentialsExpired(false)//用户凭证是否过期.accountLocked(false)//用户是否未被锁定.build();

测试

在测试的时候为了显示效果,把给用户的权限给关掉了

最后测试的结果,会显示没有权限

1.3、用户-角色-权限-资源
介绍

RBAC(Role-Based Access Control,基于角色的访问控制)是一种常用的数据库设计方案,它将用户的权限分配和管理与角色相关联。以下是一个基本的RBAC数据库设计方案的示例:

用户表(User table):包含用户的基本信息,例如用户名、密码和其他身份验证信息。

列名数据类型描述
user_idint用户ID
usernamevarchar用户名
passwordvarchar密码
emailvarchar电子邮件地址
.........

角色表(Role table):存储所有可能的角色及其描述。

列名数据类型描述
role_idint角色ID
role_namevarchar角色名称
descriptionvarchar角色描述
.........

权限表(Permission table):定义系统中所有可能的权限。

列名数据类型描述
permission_idint权限ID
permission_namevarchar权限名称
descriptionvarchar权限描述
.........

用户角色关联表(User-Role table):将用户与角色关联起来。

列名数据类型描述
user_role_idint用户角色关联ID
user_idint用户ID
role_idint角色ID
.........

角色权限关联表(Role-Permission table):将角色与权限关联起来。

列名数据类型描述
role_permission_idint角色权限关联ID
role_idint角色ID
permission_idint权限ID
.........

在这个设计方案中,用户可以被分配一个或多个角色,而每个角色又可以具有一个或多个权限。通过对用户角色关联和角色权限关联表进行操作,可以实现灵活的权限管理和访问控制。

当用户尝试访问系统资源时,系统可以根据用户的角色和权限决定是否允许访问。这样的设计方案使得权限管理更加简单和可维护,因为只需调整角色和权限的分配即可,而不需要针对每个用户进行单独的设置。

实战

数据库表

DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_menu`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `menu`;CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',`username` varchar(32) DEFAULT NULL COMMENT '账户名',`password` varchar(256) DEFAULT NULL COMMENT '密码',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';CREATE TABLE `role` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '唯一ID',`name` varchar(32) DEFAULT NULL COMMENT '角色名',`keyword` varchar(64) DEFAULT NULL COMMENT '关键词',`description` varchar(128) DEFAULT NULL COMMENT '角色描述',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';CREATE TABLE `user_role` (`user_id` int(11) NOT NULL COMMENT '用户ID',`role_id` int(11) NOT NULL COMMENT '角色ID',PRIMARY KEY (`user_id`,`role_id`),CONSTRAINT `FK_Reference_u_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),CONSTRAINT `FK_Reference_u_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色';CREATE TABLE `menu` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单编号',`name` varchar(128) DEFAULT NULL COMMENT '菜单名',`link_url` varchar(128) DEFAULT NULL COMMENT '链接地址',`path` varchar(128) DEFAULT NULL COMMENT '路由地址',`priority` int(11) DEFAULT NULL COMMENT '显示优先级别',`icon` varchar(64) DEFAULT NULL COMMENT '图标',`description` varchar(128) DEFAULT NULL COMMENT '描述',`parent_menu_id` int(11) DEFAULT NULL COMMENT '父级菜单编号',`level` int(11) DEFAULT NULL COMMENT '层次级别',`is_enable` tinyint(4) DEFAULT '1' COMMENT '是否启用 0 禁用 1 启用',PRIMARY KEY (`id`),CONSTRAINT `FK_Reference_menu` FOREIGN KEY (`parent_menu_id`) REFERENCES `menu` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='菜单';CREATE TABLE `role_menu` (`role_id` int(11) NOT NULL COMMENT '角色ID',`menu_id` int(11) NOT NULL COMMENT '菜单ID',PRIMARY KEY (`role_id`,`menu_id`),CONSTRAINT `FK_Reference_r_menu` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`),CONSTRAINT `FK_Reference_r_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色菜单关系表';INSERT INTO `user` VALUES ('1', 'admin', '$2a$10$K7U.Xolbbz3fGsAzpIawmeQuTWt/W0TXA8DpugqRwWsE0PeRSi1Vu');
INSERT INTO `user` VALUES ('2', 'test', '$2a$10$K7U.Xolbbz3fGsAzpIawmeQuTWt/W0TXA8DpugqRwWsE0PeRSi1Vu');
INSERT INTO `role` VALUES ('1', '超级管理员', 'SUPER_ADMIN', null);
INSERT INTO `role` VALUES ('2', '普通管理员', 'ADMIN', null);
INSERT INTO `user_role` VALUES ('1', '1');
INSERT INTO `user_role` VALUES ('2', '2');INSERT INTO `menu` VALUES ('1', '示例查询', '/cpp/query', '2', '2', 'fa-user-md', null, null, '1', '1');
INSERT INTO `menu` VALUES ('2', '示例添加', '/cpp/add', '/2-1', '1', null, null, '1', '2', '1');
INSERT INTO `menu` VALUES ('3', '示例修改', '/cpp/modify', '/2-2', '2', null, null, '1', '2', '1');
INSERT INTO `menu` VALUES ('4', '示例删除', '/cpp/delete', '/2-3', '3', null, null, '1', '2', '1');
INSERT INTO `menu` VALUES ('5', '示例JSON', '/cpp/json', '/2-3', '3', null, null, '1', '2', '1');
INSERT INTO `role_menu` VALUES ('1', '1');
INSERT INTO `role_menu` VALUES ('1', '2');
INSERT INTO `role_menu` VALUES ('1', '3');
INSERT INTO `role_menu` VALUES ('1', '4');
INSERT INTO `role_menu` VALUES ('1', '5');
INSERT INTO `role_menu` VALUES ('2', '1');
INSERT INTO `role_menu` VALUES ('2', '5');

2、基于方法的授权

2.1、开启方法授权

在配置文件中添加如下注解

2.2、给用户授予角色和权限
return org.springframework.security.core.userdetails.User.withUsername(user.getUsername()).password(user.getPassword()).roles("ADMIN")//添加用户的角色.authorities("USER_ADD", "USER_UPDATE")//添加用户的权限.build();

2.3  常用授权注解
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Resourcepublic UserService userService;//用户必须有 ADMIN 角色 并且 用户名是 admin 才能访问此方法@PreAuthorize("hasRole('ADMIN') and authentication.name == 'admim'")@GetMapping("/getListByRoleAndName")public List<User> getListByRoleAndName(){return userService.list();}//用户必须有 ADMIN 角色 才能访问此方法@PreAuthorize("hasRole('ADMIN')")@GetMapping("/getListByRole")public List<User> getListByRole(){return userService.list();}//用户必须有 USER_ADD 权限 才能访问此方法@PreAuthorize("hasAuthority('USER_ADD')")@PostMapping("/add")public void add(@RequestBody User user){userService.saveUserDetails(user);}
}

2.4  测试

我们给用户授权只授权admin的角色

访问add方法

登陆之后返回用户的权限

getListByRole()访问成功

add方法没有权限

OAuth2

1  OAuth2简介

1.1  OAuth2是什么

“Auth” 表示 “授权” Authorization

“O” 是 Open 的简称,表示 “开放”

连在一起就表示 “开放授权”,OAuth2是一种开放授权协议。

OAuth2最简向导:The Simplest Guide To OAuth 2.0

1.2  OAuth2的角色

OAuth 2协议包含以下角色:

  1. 资源所有者(Resource Owner):即用户,资源的拥有人,想要通过客户应用访问资源服务器上的资源。

  2. 客户应用(Client):通常是一个Web或者无线应用,它需要访问用户的受保护资源。

  3. 资源服务器(Resource Server):存储受保护资源的服务器或定义了可以访问到资源的API,接收并验证客户端的访问令牌,以决定是否授权访问资源。

  4. 授权服务器(Authorization Server):负责验证资源所有者的身份并向客户端颁发访问令牌。

用一个例子来说,就是张三(资源所有者)要用微信(客户应用)来查看微信钱包有多少米(资源服务器)。客户应用需要在这个查看的请求上带上自己的令牌给资源服务器,令牌由授权服务器发送

1.3  OAuth2的四种授权模式

RFC6749:

RFC 6749 - The OAuth 2.0 Authorization Framework (ietf.org)

阮一峰:

OAuth 2.0 的四种方式 - 阮一峰的网络日志 (ruanyifeng.com)

四种模式:

  • 授权码(authorization-code)

  • 隐藏式(implicit)

  • 密码式(password)

  • 客户端凭证(client credentials)

第一种方式:授权码

授权码(authorization code),指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用,最复杂,也是最安全的,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

第二种方式:隐藏式

隐藏式(implicit),也叫简化模式,有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。

前端对后端

RFC 6749 规定了这种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为隐藏式。这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。

https://a.com/callback#token=ACCESS_TOKEN
将访问令牌包含在URL锚点中的好处:锚点在HTTP请求中不会发送到服务器,减少了泄漏令牌的风险。

第三种方式:密码式

密码式(Resource Owner Password Credentials):如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌。

这种方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。

第四种方式:凭证式

凭证式(client credentials):也叫客户端模式,适用于没有前端的命令行应用,即在命令行下请求令牌。

后端对后端

这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。

注意

第二种和第四种差不多,只不过是请求的对象的不同,第二种是前端对后端发送请求,第四种是后端对后端发送请求

1.4  授权类型的选择

授权码(authorization code)

隐藏式(implicit)

密码式(Resource Owner Password Credentials)

凭证式(client credentials)

2  Spring中的OAuth2

2.1  相关角色

回顾:OAuth 2中的角色

  1. 资源所有者(Resource Owner)

  2. 客户应用(Client)

  3. 资源服务器(Resource Server)

  4. 授权服务器(Authorization Server)

2.2  Spring中的实现

OAuth2 :: Spring Security

Spring Security

  • 客户应用(OAuth2 Client):OAuth2客户端功能中包含OAuth2 Login

  • 资源服务器(OAuth2 Resource Server)

Spring

  • 授权服务器(Spring Authorization Server):它是在Spring Security之上的一个单独的项目。

3  GiuHub社交登录案例

基于github的第三方认证

3.1  创建应用

注册客户应用:

登录GitHub,在开发者设置中找到OAuth Apps,创建一个application,为客户应用创建访问GitHub的凭据:

登陆github账户

点击右上角图标,点击setting

划到最下面,点击开发者设置

找到OAuth

创建一个应用

配置应用的信息

填写应用信息:默认的重定向URI模板为{baseUrl}/login/oauth2/code/{registrationId}。registrationId是ClientRegistration的唯一标识符。

创建之后,生成了密钥,记得保存好。

3.2  创建测试项目

创建一个springboot项目oauth2-login-demo,创建时引入如下依赖

示例代码参考:spring-security-samples/servlet/spring-boot/java/oauth2/login at 6.2.x · spring-projects/spring-security-samples (github.com)

3.3  配置OAuth客户端属性

application.yml:

id和密钥写自己的

spring:security:oauth2:client:registration:github:client-id: 7807cc3bb1534abce9f2client-secret: 008dc141879134433f4db7f62b693c4a5361771b
#            redirectUri: http://localhost:8200/login/oauth2/code/github

3.4  创建Controller

官方示例代码

@Controller
public class IndexController {@GetMapping("/")public String index(Model model,@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,@AuthenticationPrincipal OAuth2User oauth2User) {model.addAttribute("userName", oauth2User.getName());model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());model.addAttribute("userAttributes", oauth2User.getAttributes());return "index";}
}

3.5  创建html页面

resources/templates/index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head><title>Spring Security - OAuth 2.0 Login</title><meta charset="utf-8" />
</head>
<body>
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()"><div style="float:left"><span style="font-weight:bold">User: </span><span sec:authentication="name"></span></div><div style="float:none">&nbsp;</div><div style="float:right"><form action="#" th:action="@{/logout}" method="post"><input type="submit" value="Logout" /></form></div>
</div>
<h1>OAuth 2.0 Login with Spring Security</h1>
<div>You are successfully logged in <span style="font-weight:bold" th:text="${userName}"></span>via the OAuth 2.0 Client <span style="font-weight:bold" th:text="${clientName}"></span>
</div>
<div>&nbsp;</div>
<div><span style="font-weight:bold">User Attributes:</span><ul><li th:each="userAttribute : ${userAttributes}"><span style="font-weight:bold" th:text="${userAttribute.key}"></span>: <span th:text="${userAttribute.value}"></span></li></ul>
</div>
</body>
</html>

3.6  测试

启动服务,访问localhost:8080

会跳转至github的登陆页面

登陆之后,会询问是否要认证

登陆成功,返回到是自定义的那个页面

  • 启动程序并访问localhost:8080。浏览器将被重定向到默认的自动生成的登录页面,该页面显示了一个用于GitHub登录的链接。

  • 点击GitHub链接,浏览器将被重定向到GitHub进行身份验证。

  • 使用GitHub账户凭据进行身份验证后,用户会看到授权页面,询问用户是否允许或拒绝客户应用访问GitHub上的用户数据。点击允许以授权OAuth客户端访问用户的基本个人资料信息。

  • 此时,OAuth客户端访问GitHub的获取用户信息的接口获取基本个人资料信息,并建立一个已认证的会话。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/886740.shtml

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

相关文章

为什么芯麦的 GC4931P 可以替代A4931/Allegro 的深度对比介绍

在电机驱动芯片领域&#xff0c;芯麦 GC4931P 和 A4931 都是备受关注的产品。它们在多种应用场景中发挥着关键作用&#xff0c;今天我们就来详细对比一下这两款芯片。 一、性能参数对比 &#xff08;一&#xff09;电流输出能力 A4931 具有一定的电流输出能力&#xff0c;但芯…

ThreadLocal原理及其内存泄漏

ThreadLocal通过为每个线程创建一个共享变量的副本来保证各个线程之间变量的访问和修改互不影响。 ThreadLocal存放的值是线程内共享的&#xff0c;线程间互斥的&#xff0c;主要用于线程内共享数据&#xff0c;避免通过参数传递。 ThreadLocal有四个方法&#xff1a; initialV…

工业大数据分析与应用:开启智能制造新时代

在全球工业4.0浪潮的推动下&#xff0c;工业大数据分析已经成为推动智能制造、提升生产效率和优化资源配置的重要工具。通过收集、存储、处理和分析海量工业数据&#xff0c;企业能够获得深刻的业务洞察&#xff0c;做出更明智的决策&#xff0c;并实现生产流程的全面优化。本文…

web安全测试渗透案例知识点总结(上)——小白入狱

目录 一、Web安全渗透测试概念详解1. Web安全与渗透测试2. Web安全的主要攻击面与漏洞类型3. 渗透测试的基本流程 二、知识点详细总结1. 常见Web漏洞分析2. 渗透测试常用工具及其功能 三、具体案例教程案例1&#xff1a;SQL注入漏洞利用教程案例2&#xff1a;跨站脚本&#xff…

每天五分钟机器学习:支持向量机算法数学基础之核函数

本文重点 从现在开始,我们将开启支持向量机算法的学习,不过在学习支持向量机算法之前,我们先来学习一些支持向量机所依赖的数学知识,这会帮助我们更加深刻的理解支持向量机算法,本文我们先来学习核函数。 定义 核函数(Kernel Function)是一种在支持向量机(SVM)、高…

【小程序】dialog组件

这个比较简单 我就直接上代码了 只需要传入title即可&#xff0c; 内容部分设置slot 代码 dialog.ttml <view class"dialog-wrapper" hidden"{{!visible}}"><view class"mask" /><view class"dialog"><view …

【MySQL】ubantu 系统 MySQL的安装与免密码登录的配置

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;MySQL初阶探索&#xff1a;构建数据库基础 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f4da;mysql的安装&#x1f4d5;MySQL的登录&#x1f30f;MySQL配置免密码登录 &#x1f4da;mysql的…

Dubbo源码解析-服务注册(五)

一、服务注册 当确定好了最终的服务配置后&#xff0c;Dubbo就会根据这些配置信息生成对应的服务URL&#xff0c;比如&#xff1a; dubbo://192.168.65.221:20880/org.apache.dubbo.springboot.demo.DemoService? applicationdubbo-springboot-demo-provider&timeout300…

计算机网络-理论部分(二):应用层

网络应用体系结构 Client-Server客户-服务器体系结构&#xff1a;如Web&#xff0c;FTP&#xff0c;Telnet等Peer-Peer&#xff1a;点对点P2P结构&#xff0c;如BitTorrent 应用层协议定义了&#xff1a; 交换的报文类型&#xff0c;请求or响应报文类型的语法字段的含义如何…

麒麟时间同步搭建chrony服务器

搭建chrony服务器 在本例中&#xff0c;kyserver01&#xff08;172.16.200.10&#xff09;作为客户端&#xff0c;同步服务端时间&#xff1b;kyserver02&#xff08;172.16.200.11&#xff09;作为服务端&#xff0c;提供时间同步服务。 配置服务端&#xff0c;修改以下内容…

学习threejs,通过SkinnedMesh来创建骨骼和蒙皮动画

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.SkinnedMesh 蒙皮网格…

HarmonyOS NEXT应用元服务开发Intents Kit(意图框架服务)习惯推荐方案概述

一、习惯推荐是HarmonyOS学习用户的行为习惯后做出的主动预测推荐。 1.开发者将用户在应用/元服务内的使用行为向HarmonyOS共享&#xff0c;使得HarmonyOS可以基于共享的数据学习用户的行为习惯。 2.在HarmonyOS学习到用户的行为习惯后&#xff0c;会给用户推荐相应功能&#x…

华为防火墙技术基本概念学习笔记

1.防火墙概述 1.1防火墙与交换机、路由器对比 路由器与交换机的本质是转发&#xff0c;防火墙的本质是控制。 防火墙与路由器、交换机是有区别的。路由器用来连接不同的网络&#xff0c;通过路由协议保证互联互通&#xff0c;确保将报文转发到目的地;交换机则通常用来组建局域…

shell(5)字符串运算符和逻辑运算符

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

帆软report参数栏宽度设置

最右边的竖杠被放大后在拉回来

Mysql-DQL语句

文章目录 DQL 语句简单查询查询表所有数据查询指定列 别名查询清除重复值查询结果参与运算 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Mysql专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月16日11点39分 DQL 语句 DQL 语句数据…

MySQL数据库3——函数与约束

一.函数 1.字符串函数 MySQL中内置了很多字符串函数&#xff0c;常用的几个如下&#xff1a; 使用方法&#xff1a; SELECT 函数名(参数);注意&#xff1a;MySQL中的索引值即下标都是从1开始的。 2.数值函数 常见的数值函数如下&#xff1a; 使用方法&#xff1a; SELECT…

Vue.js 插槽 Slots 实际应用 最近重构项目的时候遇到的...

前端开发中 插槽 Slots 是一个重要的概念 我们可以查看一下vue.js的官方文档 https://cn.vuejs.org/guide/components/slots 类似于连接通道一样 可以把核心代码逻辑搬到另外的地方 做一个引用 而原先的地方可能并不能这样书写 对于这个概念我在vue的官方文档里面找到了…

5G 现网信令参数学习(3) - RrcSetup(2)

前一篇&#xff1a;5G 现网信令参数学习(3) - RrcSetup(1) 目录 1. rlf-TimersAndConstants 2. spCellConfigDedicated 2.1 initialDownlinkBWP 2.1.1 pdcch-Config 2.1.1.1 controlResourceSetToAddModList 2.1.1.2 searchSpacesToAddModList 2.1.2 pdsch-Config 2.1…

MySQL(5)【数据类型 —— 字符串类型】

阅读导航 引言一、char&#x1f3af;基本语法&#x1f3af;使用示例 二、varchar&#x1f3af;基本语法&#x1f3af;使用示例 三、char 和 varchar 比较四、日期和时间类型1. 基本概念2. 使用示例 五、enum 和 set&#x1f3af;基本语法 引言 之前我们聊过MySQL中的数值类型&…