04-获取认证的用户身份信息

存储用户信息的方式

获取用户信息的流程

用户提交账号和密码后,DaoAuthenticationProvider调用UserDetailsService接口实现类的loadUserByUsername()方法,该方法可以接收请求参数username的值,然后根据该值查询用户信息,最后将账号,密码,权限封装到UserDetails对象中并返回给Spring security框架

public interface UserDetailsService {// 根据用户名获取用户信息的方法,不同的实现类对应不同的实现方式UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
// UserDetails是用户信息接口
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}

在这里插入图片描述

基于内存的方式存储用户信息

InMemoryUserDetailsManagerUserDetailsService接口的实现类,可以将用户信息以硬编码的方式存储在内存中并封装到UserDetails对象中

package com.xuecheng.auth.config;
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic UserDetailsService userDetailsService() {// 1. 配置用户信息服务InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();// 2. 创建用户信息这里暂时写死,后面需要从数据库中动态查询, Kyle的权限是p1,Lucy的权限是p2// User是Spring Security提供的工具类manager.createUser(User.withUsername("Kyle").password("123").authorities("p1").build());manager.createUser(User.withUsername("Lucy").password("456").authorities("p2").build());return manager;}// 设置密码比对的规则@Beanpublic PasswordEncoder passwordEncoder() {// 采用明文的方式直接比对return NoOpPasswordEncoder.getInstance();}protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/r/**")// 表示"/r/**"开头的请求需要认证.authenticated().anyRequest().permitAll().and().formLogin().successForwardUrl("/login-success");// 配置退出登录页面,认证成功后访问/logout可退出登录http.logout().logoutUrl("/logout");}
}

基于数据库存储用户信息和密码编码格式

实际开发中认证服务所需要的用户信息都是存储在数据库中,所以我们要从数据库中查询用户信息

第一步:屏蔽掉原先的InMemoryUserDetailsManager, 自定义一个UserDetailsService接口的实现类UserDetailsImpl并重写loadUserByUsername()方法,该方法可以接收URL中携带的请求参数username的值

  • 我们重写方法的业务逻辑是根据用户的账号名从数据库中查询对应的用户信息,然后将账号,密码,用户权限信息封装到UserDetails类型的对象中并返回给Spring Security框架,不设置权限会报Cannot pass a null GrantedAuthority collection
@Service
public class UserDetailsImpl implements UserDetailsService {@AutowiredXcUserMapper xcUserMapper;/**** @param name     用户输入的登录账号* @return         UserDetails* @throws         UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {// 根据username去XcUser表中查询对应的用户信息XcUser user = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, name));// 返回NULL表示用户不存在,SpringSecurity会帮我们处理即抛出异常表示用户不存在if (user == null) {return null;}// 设置用户权限// String[] authorities=  {"test"};// 取出数据库中用户的密码String password = user.getPassword();// 将用户名和密码以及用户权限(权限必须要设置)封装成一个UserDetails对象,然后返回即可return User.withUsername(user.getUsername()).password(password).authorities("test").build();}
}

第二部:Spring Security框架将URL中的请求参数password的值即用户的登陆密码和UserDetails对象中从数据库中查询到的密码进行比对

  • 数据库中的密码是加密过的,但用户登陆时输入的密码是明文形式,所以我们要修改WebSecurityConfig配置类中的密码格式器PasswordEncoder指定的密码校验方式
密码格式描述
NoOpPasswordEncoder表示通过明文的方式比较密码
BCryptPasswordEncoder表示先将用户输入的密码编码为BCrypt格式后才与数据库中的密码进行比对
@Bean
public PasswordEncoder passwordEncoder() {     // return NoOpPasswordEncoder.getInstance();return new BCryptPasswordEncoder();
}

BCryptPasswordEncoder原理: 虽然同样的原始密码可以对应不同的Hash值,但是比对密码时如果调用它提供的matches方法即使Hash值不同但校验是正确的

  • 先将数据库表中用户密码的存储方式改为BCrype格式, 申请令牌时输入的密码需要的还是明文形式
public static void main(String[] args) {String password = "123456";BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();for (int i = 0; i < 5; i++) {// 每个计算出的Hash值都不一样String encodePsw = encoder.encode(password);// 虽然Hash值不一样,但是校验是可以通过的System.out.println("转换后密码:" + encodePsw + "比对情况:" + encoder.matches(password, encodePsw));}
}// 转换后密码:$2a$10$6hbvtCtgcISvbBHJ.UnhPO1io7StF.ySPkmAvzO/efvNmHVVJZOeK比对情况:true
// 转换后密码:$2a$10$ufYW9qXSAk0N201B/wCR7uGrzygawnwXtyL2vKpDLAOCOkF33sGnK比对情况:true
// 转换后密码:$2a$10$DEaVxYHakIE/kDvAU4eC7OZ7c9kqKBJedClVxDPnYH.zwuZvCRnzm比对情况:true
// 转换后密码:$2a$10$s2qgaKGgULYQ7tce2u6TIeHopap4HqfyghJYu1vdDZ2WcNk70ykFe比对情况:true
// 转换后密码:$2a$10$XQaQJIfXyd/UvMHC..uBNuDXNVrZHnEGn.tW0oSB6WVjdsZLFpkGq比对情况:true

由于我们修改了框架密码的校验方式,所以还需要将认证服务批准接入的客户端的密钥也更改为BCrypt的编码格式

// 配置客户端的详情信息
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()// 使用in-memory存储.withClient("XcWebApp")// 客户端的Id.secret(new BCryptPasswordEncoder().encode("XcWebApp"))//客户端密钥转为BCrypt格式.resourceIds("xuecheng-plus")//资源列表.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// 允许的授权类型.scopes("all")// 允许的授权范围.autoApprove(false)//false跳转到授权页面                  .redirectUris("http://localhost/");//客户端接收授权码的重定向地址
}

第三步:重启认证服务使用HttpClient通过密码模式申请令牌,要保证登陆的明文密码在数据库中有对应的BCrypt格式的加密密码

###### 密码模式
POST localhost:53070/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=Kyle&password=111111

第四步:查看服务端响应的结果

// 输入正确的账号密码即可成功申请令牌
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsieHVlY2hlbmctcGx1cyJdLCJ1c2VyX25hbWUiOiJLeWxlIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTY3ODQ1MTUxNiwiYXV0aG9yaXRpZXMiOlsidGVzdCJdLCJqdGkiOiJkOWUwYjU0ZS03Zjg4LTQ2NjAtYjFlZS04ZWQzYjYzZmQwNjMiLCJjbGllbnRfaWQiOiJYY1dlYkFwcCJ9.NaS3hmpDtX3zkXvnZWoo9ROEWgYeA6GoxBzy_lOxzvA",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsieHVlY2hlbmctcGx1cyJdLCJ1c2VyX25hbWUiOiJLeWxlIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImQ5ZTBiNTRlLTdmODgtNDY2MC1iMWVlLThlZDNiNjNmZDA2MyIsImV4cCI6MTY3ODcwMzUxNiwiYXV0aG9yaXRpZXMiOlsidGVzdCJdLCJqdGkiOiI5YjE4OTUwOS1iYmEzLTRkMTctYTNkNC05OWQwZGI5NjU0MDAiLCJjbGllbnRfaWQiOiJYY1dlYkFwcCJ9.548w5CdQiIU5_k1qRBjzM-PMBqy-XX3zr17tQS6g6CM",
"expires_in": 7199,
"scope": "all",
"jti": "d9e0b54e-7f88-4660-b1ee-8ed3b63fd063"
}// 输入错误的密码,申请令牌失败
{
"error": "invalid_grant",
"error_description": "用户名或密码错误"
}

扩展用户身份信息的方式(UserDetails)

用户表中除了存储用户的账号密码还有其他信息,但是UserDetails接口中只有username、password字段,这样JWT令牌中也只会写入用户的账号和密码信息

  • 在认证阶段DaoAuthenticationProvider会调用我们自定义的UserDetailsService接口实现类的loadUserByUsername()方法查询数据库表中用户全部的信息,但是UserDetails中只能存储账号和密码
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}

由于JWT令牌中用户身份信息来源于UserDetails, 所以我们只能把UserDetails中包含的用户信息写入JWT令牌中,想要扩展用户的身份信息有两种方式

  • 修改源码: 对框架提供的UserDetails的属性进行扩展,使之包括更多的自定义属性
  • 存储Json(推荐): 我们还可以将用户的全部信息转为Json数据存储到UserDetails的username属性(需要脱敏),并不一定要只存储用户账号
@Service
public class UserDetailsImpl implements UserDetailsService {@AutowiredXcUserMapper xcUserMapper;/**** @param name     用户输入的登录账号* @return         UserDetails* @throws         UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {// 根据username去XcUser表中查询对应的用户信息XcUser user = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, name));// 返回NULL表示用户不存在,SpringSecurity会帮我们处理即抛出异常表示用户不存在if (user == null) {return null;}// 设置用户权限// String[] authorities=  {"test"};// 取出数据库中用户的密码String password = user.getPassword();// 将查询到的用户信息映射的XcUser对象转为Json,注意对用户的敏感信息要进行脱敏user.setPassword(null);String userString = JSON.toJSONString(user);// 将用户名和密码以及用户权限(权限必须要设置)封装成一个UserDetails对象,然后返回即可return User.withUsername(userString).password(password).authorities("test").build();}
}

重启认证服务,使用密码模式重新获取令牌

// 密码模式
POST localhost:53070/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=Kyle&password=111111

访问校验接口查看JWT令牌中的内容,此时令牌的user_name属性中包含了查询到的用户全部信息(密码为null)

// 访问校验接口
POST localhost:53070/auth/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsieHVlY2hlbmctcGx1cyJdLCJ1c2VyX25hbWUiOiJ7XCJjb21wYW55SWRcIjpcIjEyMzIxNDE0MjVcIixcImNyZWF0ZVRpbWVcIjpcIjIwMjItMDktMjhUMDg6MzI6MDNcIixcImlkXCI6XCI1MlwiLFwibmFtZVwiOlwiS2lraVwiLFwicGFzc3dvcmRcIjpcIiQyYSQxMCQwcHQ3V2xmVGJuUERUY1d0cC8uMk11NUNUWHZvaG5OUWhSNjI4cXE0Um9LU2MwZEdBZEVnbVwiLFwic2V4XCI6XCIxXCIsXCJzdGF0dXNcIjpcIlwiLFwidXNlcm5hbWVcIjpcIkt5bGVcIixcInV0eXBlXCI6XCIxMDEwMDJcIn0iLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNjc4NDUyMzU0LCJhdXRob3JpdGllcyI6WyJ0ZXN0Il0sImp0aSI6Ijc2MDc0MDI4LTBiM2MtNDQ4Mi1hN2Y0LTc1NDI3ZTA2OTFjMSIsImNsaWVudF9pZCI6IlhjV2ViQXBwIn0._GKfGE2s5k0n6VC4_RKQrzdzydWY-WtX3Q_Hc4DxQ1g{"aud": ["xuecheng-plus"],"user_name": "{\"companyId\":\"1232141425\",\"createTime\":\"2022-09-28T08:32:03\",\"id\":\"52\",\"name\":\"Kiki\",\"password\":\"$2a$10$0pt7WlfTbnPDTcWtp/.2Mu5CTXvohnNQhR628qq4RoKSc0dGAdEgm\",\"sex\":\"1\",\"status\":\"\",\"username\":\"Kyle\",\"utype\":\"101002\"}","scope": ["all"],"active": true,"exp": 1678452354,"authorities": ["test"],"jti": "76074028-0b3c-4482-a7f4-75427e0691c1","client_id": "XcWebApp"
}

资源服务获取用户身份信息

现在认证服务生成JWT令牌的user_name属性已经存储用户JSON格式的信息,在资源服务中就可以取出该JSON格式的内容转换为用户对象去使用

第一步: 在content-api工程中写一个工具类SecurityUtil用来在各个微服务中获取到当前登录用户信息的对象

@Slf4j
public class SecurityUtil {public static XcUser getUser() {try {// 通过SecurityContextHolder获取user_name属性的值即包含用户信息的Json字符串Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();if (principal instanceof String) {// 将包含用户信息的Json字符串转换为XcUser对象String userJson = principal.toString();XcUser xcUser = JSON.parseObject(userJson, XcUser.class);return xcUser;}} catch (Exception e) {log.error("获取当前登录用户身份信息出错:{}", e.getMessage());e.printStackTrace();}return null;}// 这里使用内部类是为了不让content工程去依赖auth工程@Datapublic static class XcUser implements Serializable {private static final long serialVersionUID = 1L;private String id;private String username;private String password;private String salt;private String name;private String nickname;private String wxUnionid;private String companyId;/*** 头像*/private String userpic;private String utype;private LocalDateTime birthday;private String sex;private String email;private String cellphone;private String qq;/*** 用户状态*/private String status;private LocalDateTime createTime;private LocalDateTime updateTime;}
}

第二步: 在内容管理服务中的查询课程信息接口中使用工具类获取当前登陆的用户信息

@ApiOperation("根据课程id查询课程基础信息")
@GetMapping("/course/{courseId}")
public CourseBaseInfoDto getCourseBaseById(@PathVariable Long courseId) {SecurityUtil.XcUser user = SecurityUtil.getUser();System.out.println("当前用户身份为:" + user);return courseBaseInfoService.getCourseBaseInfo(courseId);
}

第三步: 启动认证服务、网关、内容管理服务, 首先访问认证服务生成令牌,然后携带生成的令牌访问内容管理服务的查询课程接口,最后在控制台中查看登陆的用户信息

当前用户身份为:SecurityUtil.XcUser(id=52, username=Kyle, password=null, salt=null, name=Kiki, nickname=null, wxUnionid=null, companyId=1232141425, userpic=null, utype=101002, birthday=null, sex=1, email=null, cellphone=null, qq=null, status=, createTime=2022-09-28T08:32:03, updateTime=null)

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

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

相关文章

本地映射测试环境域名,解决登录测试环境后,也可以使用本地域名访问,可以正常跑本地项目

问题&#xff1a;单点登录进入系统不使用token&#xff0c;是将token携带在cookie中&#xff0c;登录成功后每次调用接口&#xff0c;都会在cookie中自动携带&#xff0c;这样导致即使在本地使用proxy代理解决了跨域&#xff0c;但由于本地域名不一致&#xff0c;也无法进行本地…

HW01—SY系列单点测厚仪 应用于轧钢橡胶、金属、塑料…

关键字&#xff1a;单点测厚仪,HW01—SY测厚仪,常温测厚仪,热轧板测厚仪,非接触测厚仪,橡胶测厚仪,塑料板测厚仪,木板测厚仪, 产品简介&#xff1a; 该系列测厚仪整体采用C型架结构&#xff0c;C型架上安装上下两个对射的激光位移传感器&#xff0c;可测量一个点的厚度尺寸。C型…

杰发科技AC7840——EEPROM初探

0.序 7840和7801的模拟EEPROM使用不太一样 1.现象 按照官方Demo&#xff0c;在这样的配置下&#xff0c;我们看到存储是这样的&#xff08;连续三个数字1 2 3&#xff09;。 使用串口工具的多帧发送功能 看不出多少规律 修改代码后 发现如下规律&#xff1a; 前四个字节是…

信号与线性系统翻转课堂笔记16——离散LTI系统的各类响应

信号与线性系统翻转课堂笔记16——离散LTI系统的各类响应 The Flipped Classroom16 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#xff0c;重点…

Java连接Mysql报错:javax.net.ssl.SSLException: Received fatal alert: internal_error

大致报错日志如下&#xff1a; The last packet successfully received from the server was 11 milliseconds ago. The last packet sent successfully to the server was 10 milliseconds ago.at sun.reflect.GeneratedConstructorAccessor275.newInstance(Unknown Source)…

了解 NSA 关于管理 OSS 和 SBOM 的最新指南

开源软件很容易受到恶意行为者的攻击&#xff0c;但软件材料清单可以帮助减轻威胁。美国国家安全局的指导为管理生态系统奠定了坚实的基础。 软件供应链安全仍然是网络安全和软件行业的一个关键话题&#xff0c;并且有充分的理由&#xff0c;从针对大型软件供应商的持续攻击到…

简单工厂、工厂方法、抽象工厂和策略模式

摘要 本文简单介绍软件开发过程中面临的痛点和几个总体原则。详细介绍了简单工厂、工厂方法、抽象工厂和策略模式的实现&#xff0c;以及各种模式之间的相似、区别。 背景 开发面临哪些问题&#xff08;痛点&#xff09;&#xff1f; 相信做过大型软件开发的tx都遇到过以下类似…

2023年年终总结 —— 致满载荣誉或满脸惆怅的你

开篇请允许我引用歌曲《西楼儿女》的一句歌词&#xff1a; 陌生的朋友你请听我讲&#xff0c;许多年前我也曾有梦想&#xff0c;想过满载荣誉回到家乡&#xff0c;这肆意的风压弯了海棠。 2023年即将过去&#xff0c;不管你这一年经历了多少&#xff0c;或是职场的得心应手、情…

微服务事务处理:CAP 定理和最终一致性的关系

CAP 定理和最终一致性 CAP 定理和最终一致性是两个密切相关的概念&#xff0c;但它们在范围和细节上有所不同。以下是比较&#xff1a; CAP 定理 **正式陈述&#xff1a;**在分布式系统中&#xff0c;最多只能同时满足以下三个保证中的两个&#xff1a;一致性、可用性和分区…

STL——遍历算法

1.for_each 函数原型&#xff1a; for_each(iterator beg, iterator end, _func);——// 遍历算法 遍历容器元素&#xff1b; beg 开始迭代器&#xff1b;end 结束迭代器&#xff1b; _func 函数或者函数对象 #include<iostream> using namespace std; #include<ve…

C++ 判断 float 或 double 数据小数点后位数的方法

文章目录 Method 1Method 2Method 3 Method 1 #include <cmath> int countDecimalPlaces(double num) { if (num 0) return 0; // 避免除以零错误 return std::floor(std::log10(std::abs(num - std::floor(num)))) 1; }Method 2 #include <string> #…

GBASE南大通用-小内存单机安装GBase 8c分布式数据库实践

* 这种小内存部署方式仅用于分布式数据库个人学习使用&#xff0c;不建议用于其他用途。 随着数据高并发复杂场景业务需求不断增多&#xff0c;信息数据呈现出爆炸式增长、多源多维、数据类型繁复等特征。在这一趋势下&#xff0c;目前分布式数据库因其架构的天然优势&#xf…

Creo各版本安装指南

下载链接 https://pan.baidu.com/s/1VyP0_185mJeBiorlpUJqrQ?pwd0531 1.鼠标右击【Creo10.0(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 Creo10.0(64bit)】。 ​2.打开解压后的文件夹&#xff0c;鼠标右击【Crack】选择【解压到…

mysql 数据查重与查重分页

起因是公司的crm录入不规范,有重复数据。 之后考虑到需要手动处理,首先需要自动找出重复的数据 查重要求: 存在多个不允许重复的字段,任一字段重复,则判断为同一个客户。划分到同一重复组中。 查重sql如下 SELECT CONCAT(组, dense_rank() OVER (ORDER BY group_key)) …

Winform RDLC报表(数据库连接、报表函数使用、动态表头)

文章目录 NuGet安装库数据库连接报表设计报表引用添加报表 数据集设计方法一手动添加方法二——连接数据库添加 关联报表与数据集表格数据与数据集数据设计表格格式、字体设计报表数据字段绑定 Winform 使用报表控件数据库填充数据集从数据库获取与数据源相同字段的数据 动态表…

数据库一般会采取什么样的优化方法?

数据库一般会采取什么样的优化方法&#xff1f; 1、选取适合的字段属性 为了获取更好的性能&#xff0c;可以将表中的字段宽度设得尽可能小。 尽量把字段设置成not null 执行查询的时候&#xff0c;数据库不用去比较null值。 对某些省份或者性别字段&#xff0c;将他们定义为e…

【并发】AtomicInteger很安全

AtomicInteger 简介与常规用法 AtomicInteger 是 Java 中 java.util.concurrent.atomic 包下的一个类。用于实现原子操作的整数。它是一个基于CAS&#xff08;Compare-And-Swap&#xff09;实现的原子整数类。它提供了一系列的原子操作&#xff0c;确保对整数的操作是原子性的&…

JavaScript(ES6)数据结构与算法之树

6. 树 文章目录 6. 树6.1 概念6.2 二叉树6.3 二叉搜索树概念代码实现插入遍历获取最值搜索删除节点 6.4 红黑树红黑树概念红黑树规则平衡原理 6.1 概念 非线性结构 n(n>0)个节点构成的有限集合&#xff0c;n0时称为空树 对于任一非空树 有一个根节点其余节点可以构成子树 …

python 中断点调试 pdb 包的介绍及使用

pdb 的简介 pdb&#xff08;python debugger&#xff09; 是 python 中的一个命令行调试包&#xff0c;为 python 程序提供了一种交互的源代码调试功能&#xff0c;其官方使用文档链接为 pdb — Python 的调试器。 pdb 的主要功能包括设置断点、单步调试、进入函数调试、查看当…

Django、Python版本升级问题大汇总

Django3.0升级到4.1,Python3.8升级到3.11.6问题大汇总 报错1:ERROR: Could not build wheels for cffi, uWSGI, which is required to install pyproject.toml-based projects ERROR: Could not build wheels for cffi, uWSGI, which is required to install pyproject.tom…