【SpringSecurity】十七、OAuth2授权服务器 + 资源服务器Demo

文章目录

  • 0、库表准备
  • 1、项目结构
  • 2、基于数据库的认证
  • 3、授权服务器配置
  • 4、授权服务器效果测试
  • 5、资源服务器配置
  • 6、其他授权模式测试
    • 6.1 密码模式
    • 6.2 简化模式
    • 6.3 客户端模式
    • 6.4 refresh_token模式

相关📕:【Spring Security Oauth2 配置理论部分】

0、库表准备

库表结构:

在这里插入图片描述

oauth2的相关表SQL:

https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

基于RBAC,简化下,只要角色,不要权限表,表结构为:

1)用户表sys_user

在这里插入图片描述

2)角色表sys_role

在这里插入图片描述

3)用户角色关系表sys_user_role

在这里插入图片描述

1、项目结构

创建两个服务,一个充当授权服务器,结构为:

在这里插入图片描述

另一个充当资源服务器,结构为:

在这里插入图片描述

数据库层采用mysql + mybatis-plus实现,相关依赖:

<dependencies><!--spring security starter--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--spring security oauth核心依赖--><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.3.4.RELEASE</version></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><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId><version>3.4.0</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>

application.yml内容:

# 资源服务器同配置,端口为9010
server:port: 9009   
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/test-db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=falseusername: rootpassword: root123main:allow-bean-definition-overriding: true
logging:level:com.itheima: debug
mybatis-plus:configuration:map-underscore-to-camel-case: truetype-aliases-package: com.plat.domain

2、基于数据库的认证

创建Po:

@TableName("sys_user")
@Data
public class SysUserPo implements Serializable {private Integer id;private String username;private String password;public Integer getId() {return id;}public String getUsername() {return username;}public String getPassword() {return password;}
}
@TableName("sys_role")
@Data
public class SysRolePo implements GrantedAuthority, Serializable {private Integer id;private String roleName;private String roleDesc;@Overridepublic String getAuthority() {return this.roleName;    //注意这里权限的处理,通过实现GrantedAuthority, 和框架接轨}
}

创建一个中转类,实现UserDetails,以后返回给框架(也可以用框架自己的User类,我觉得自己写个中转类更顺手)。注意其聚合SysUserPo以及权限属性。因SysUser我设计的简略,因此UserDetails的是否被禁用、是否过期等字段直接返回true,不再去自定义的SysUser中去查

@Data
@Builder
public class SecurityUser implements UserDetails {private SysUserPo sysUserPo;private List<SysRolePo> roles;public SecurityUser(SysUserPo sysUserPo, List<SysRolePo> roles) {this.sysUserPo = sysUserPo;this.roles = roles;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return roles;}@Overridepublic String getPassword() {return this.sysUserPo.getPassword();}@Overridepublic String getUsername() {return this.sysUserPo.getUsername();}/*** 以下字段,我的用户表设计简单,没有过期、禁用等字段* 这里都返回true*/@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

Mapper:

@Repository
@Mapper
public interface UserMapper extends BaseMapper<SysUserPo> {@Select("select * from sys_user where username = #{username}")SysUserPo selectUserByName(String username);}
@Repository
@Mapper
public interface RoleMapper extends BaseMapper<SysRolePo> {@Select("SELECT r.id, r.role_name roleName, r.role_desc roleDesc "+"FROM sys_role r ,sys_user_role ur "+"WHERE r.id=ur.role_id AND ur.user_id=#{uid}")public List<SysRolePo> selectAuthByUserId(Integer uid);}

写UserDetialsService接口的实现类,好自定义用户查询逻辑:

public interface UserService extends UserDetailsService {
@Service
public class UserServiceImpl implements UserService {@Resourceprivate UserMapper userMapper;@Resourceprivate RoleMapper roleMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//自定义用户类SysUserPo sysUserPo = userMapper.selectUserByName(username);//权限List<SysRolePo> authList = roleMapper.selectAuthByUserId(sysUserPo.getId());return new SecurityUser(sysUserPo, authList);}
}

3、授权服务器配置

注入DataSource对象,定义授权服务器需要的相关Bean:

@Configuration
public class OAuth2Bean {@Resourceprivate DataSource dataSource;  //数据库连接池对象/*** 客户端服务详情* 从数据库查询客户端信息*/@Bean(name = "jdbcClientDetailsService")public JdbcClientDetailsService clientDetailsService(){return new JdbcClientDetailsService(dataSource);}/*** 授权信息保存策略*/@Bean(name = "jdbcApprovalStore")public ApprovalStore approvalStore(){return new JdbcApprovalStore(dataSource);}/*** 令牌存储策略*/@Bean(name = "jdbcTokenStore")public TokenStore tokenStore(){//使用数据库存储令牌return new JdbcTokenStore(dataSource);}//设置授权码模式下,授权码如何存储@Bean(name = "jdbcAuthorizationCodeServices")public AuthorizationCodeServices authorizationCodeServices(){return new JdbcAuthorizationCodeServices(dataSource);}}

配置OAuth2的授权服务器:

@Configuration
@EnableAuthorizationServer    //OAuth2的授权服务器
public class OAuth2ServiceConfig implements AuthorizationServerConfigurer {@Resource(name = "jdbcTokenStore")private TokenStore tokenStore;    //注入自定义的token存储配置Bean@Resource(name = "jdbcClientDetailsService")private ClientDetailsService clientDetailsService;  //客户端角色详情@Resourceprivate AuthenticationManager authenticationManager;  //注入安全配置类中定义的认证管理器Bean@Resource(name = "jdbcAuthorizationCodeServices")private AuthorizationCodeServices authorizationCodeServices;  //注入自定义的授权码模式服务配置Bean@Resource(name = "jdbcApprovalStore")private ApprovalStore approvalStore;   //授权信息保存策略//token令牌管理@Beanpublic AuthorizationServerTokenServices tokenServices() {DefaultTokenServices tokenServices = new DefaultTokenServices();tokenServices.setClientDetailsService(clientDetailsService);   //客户端信息服务,即向哪个客户端颁发令牌tokenServices.setSupportRefreshToken(true);  //支持产生刷新令牌tokenServices.setTokenStore(tokenStore);   //令牌的存储策略tokenServices.setAccessTokenValiditySeconds(7200);    //令牌默认有效期2小时tokenServices.setRefreshTokenValiditySeconds(259200);  //refresh_token默认有效期三天return tokenServices;}/*** token令牌端点访问的安全策略* (不是所有人都可以来访问框架提供的这些令牌端点的)*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {authorizationServerSecurityConfigurer.tokenKeyAccess("permitAll()")   //oauth/token_key这个端点(url)是公开的,不用登录可调.checkTokenAccess("permitAll()")   // oauth/check_token这个端点是公开的.allowFormAuthenticationForClients();  //允许客户端表单认证,申请令牌}/*** Oauth2.0客户端角色的信息来源:内存、数据库* 这里用数据库*/@Overridepublic void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {clientDetailsServiceConfigurer.withClientDetails(clientDetailsService);}/*** 令牌端点访问和令牌服务(令牌怎么生成、怎么存储等)*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManager)  //设置认证管理器,密码模式需要.authorizationCodeServices(authorizationCodeServices)  //授权码模式需要.approvalStore(approvalStore).tokenServices(tokenServices())  //token管理服务.allowedTokenEndpointRequestMethods(HttpMethod.POST);  //允许Post方式访问}
}

web安全配置类:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate UserService userService;//设置权限@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginProcessingUrl("/login").permitAll().and().csrf().disable();}//AuthenticationManager对象在Oauth2认证服务中要使用,提取放到IOC容器中@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//指定认证对象的来源@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(passwordEncoder());}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}

授权服务器配置完成,启动服务。

4、授权服务器效果测试

浏览器模拟客户端系统请求资源,客户端系统自已重定向到以下路径:

http://localhost:9009/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=https://www.baidu.com

向服务方获取授权码。到达服务方系统的登录页面,输入用户在服务方系统的账户密码:

在这里插入图片描述

服务方系统校验通过,询问用户是否向c1客户端系统开放权限all去获取它的资源:

在这里插入图片描述
点击同意,重定向到客户端注册的redirect_url,并返回授权码:

在这里插入图片描述

客户端系统用授权码去/oauth/token换取令牌:

在这里插入图片描述

成功获得令牌。携带此令牌向资源服务器发起请求。

ps:复习认证授权的对接流程

在这里插入图片描述

  • 客户端系统向本地服务发起授权申请
  • 客户端系统授权地址重定向到服务端系统的/oauth/authorize接口
  • 客户端系统向服务端系统的认证中心发起授权申请
  • 服务端系统校验是否已登录
  • 未登录则需要在服务端系统页面完成用户登录
  • 服务端系统认证中心发放授权码
  • 客户端系统申请token
  • 客户端系统使用code向服务端换取token
  • 服务端系统返回token及有效期
  • 服务端系统同步缓存token
  • 返回token给客户端系统

5、资源服务器配置

配置一个远程校验token的Bean,设置校验token的端点url,以及资源服务自己的客户端id和密钥:

@Configuration
public class BeanConfig {@Beanpublic ResourceServerTokenServices tokenServices() {RemoteTokenServices services = new RemoteTokenServices();services.setCheckTokenEndpointUrl("http://localhost:9009/oauth/check_token");services.setClientId("resourceServiceId");services.setClientSecret("123");return services;}
}

配置授权服务器:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true)
public class OAuthSourceConfig extends ResourceServerConfigurerAdapter {public static final String RESOURCE_ID = "res1";@Resourceprivate DataSource dataSource;@ResourceResourceServerTokenServices resourceServerTokenServices;@Beanpublic TokenStore jdbcTokenStore() {return new JdbcTokenStore(dataSource);}@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.resourceId(RESOURCE_ID)   //资源id.tokenStore(jdbcTokenStore())   //告诉资源服务token在库里.tokenServices(resourceServerTokenServices).stateless(true);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//这就是给客户端发token时的scope,这里会校验scope标识.antMatchers("/**").access("#oauth2.hasAnyScope('all')").and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}
}

写个测试接口:

@RestController
public class ResourceController {@GetMapping("/r/r1")public String r1(){return "access resource 1";}@GetMapping("/r/r2")public String r2(){return "access resource 2";}
}

携带上面申请的令牌访问测试接口。token正确时:

在这里插入图片描述

token错误时:

在这里插入图片描述

6、其他授权模式测试

上面测完了授权码模式,该模式最安全,因为access_token只在服务端在交换,而不经过浏览器,令牌不容易泄露。

6.1 密码模式

测试密码模式,刚开始报错:unauthorized grant type:password。

在这里插入图片描述

想起客户端注册信息是我手动插入到oauth表里的,新改个字段:

在这里插入图片描述

一切正常:

在这里插入图片描述
很明显,这种模式会把用户在服务端系统的账户和密码泄漏给客户端系统。因此该模式一般用于客户端系统也是自己公司开发的情况。

6.2 简化模式

相比授权码模式,少了一步授权码换token的步骤。

在这里插入图片描述
response_type=token,说明是简化模式。

/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com

在这里插入图片描述

简化模式用于客户端只是个前端页面的情况。即没有服务器端的第三方单页面应用,因为没有服务器端就无法接收授权码+换取token

6.3 客户端模式

使用客户端模式:

在这里插入图片描述

/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials参数:- client_id:客户端准入标识。
- client_secret:客户端秘钥。
- grant_type:授权类型,填写client_credentials表示客户端模式

简单但不安全,需要对客户端系统很信任,可用于合作方系统间对接:

在这里插入图片描述

6.4 refresh_token模式

在这里插入图片描述

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

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

相关文章

《InfMAE: A Foundation Model in Infrared Modality》CVPR2024

基础模型vs大模型&#xff1a;大模型&#xff0c;也称基础模型&#xff0c;是指具有大规模参数和复杂计算结构的机器学习模型 以后的研究中必须把大模型和基础模型耦合进来 总结&#xff1a;占坑 1. AB 多光谱的基础模型 红外的基础模型 可见光的基础模型 整体架构差不多…

微信小程序小案例实战

.wxml: <view class "title">狂飙经典语录 </view> <view class"out"><block wx:if"{{listArr.length}}"> <!-- bloock不会影响排版--><view class"list"><view class"row" wx:…

mysql性能调优

mysql性能调优 sysbench压测调优到百万级别qps sysbench压测调优到百万级别qps 这篇文章https://www.percona.com/blog/millions-queries-per-second-postgresql-and-mysql-peaceful-battle-at-modern-demanding-workloads/#:~:textWe%20contacted%20SysBench%20author%20Alex…

seata安装

seara-Server下载 数据库建库seata 建表SQL地址:https://github.com/apache/incubator-seata/blob/develop/script/server/db/mysql.sql 表结构: 更改配置: # Copyright 1999-2019 Seata.io Group. # # Licensed under the Apache License, Version 2.0 (the "Licens…

Java学习笔记NO.24

T1.完成理工超市系统的商品类及其子类的定义&#xff0c;实现“浏览商品”及“查看商品详情”功能 &#xff08;1&#xff09;商品类 public class Goods {public String name;public double price;public int count;public String desc;public Goods(String name, double p…

ORA-02270-此列列表的唯关键字或主键不匹配

学习场景 数据库约束学习 -- 主表 班级表 create table classinfo(cid number(4), -- 班级编号cname varchar2(100), -- 班级名称cyear number(4), -- 入学年份clen number(1) -- 学制 );-- 从表 学生表 create table stuInfo(stuNo number(10) primary key, cid number(4)…

C语言经典算法-6

文章目录 其他经典例题跳转链接31.数字拆解32.得分排行33.选择、插入、气泡排序34.Shell 排序法 - 改良的插入排序35.Shaker 排序法 - 改良的气泡排序 其他经典例题跳转链接 C语言经典算法-1 1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. 三色棋 5. 老鼠走迷官&#xff08;一&…

《C语言深度剖析》---------关键字(1)

1.双击实质--->加载内存 windows系统里面&#xff0c;双击的本质就是运行程序&#xff0c;把程序加载到内存里面&#xff1b; 任何程序运行的时候都必须加载到内存里面&#xff1b; 程序没有运行之前在硬盘里面&#xff0c;为什么程序运行之前必须加载到内存里面呢&#…

【Apache ShenYu源码】如何实现负载均衡模块设计

ShenYu是一个异步的&#xff0c;高性能的&#xff0c;跨语言的&#xff0c;响应式的 API 网关。有关ShenYu的介绍可以戳这。 一、前瞻 今天我们尝试不同的代码阅读方式&#xff0c;按模块来去阅读源码&#xff0c;看看效果如何。 本次阅读锁定在shenyu-loadbalancer&#xf…

比较基因组——还是看我的教程吧!

一、运行orthofinder 首先 orthofinder使用的版本为2.5.* 不要使用2.2的&#xff0c;2.2默认比对是blast&#xff0c;速度非常慢&#xff0c;结果文件呈现形式也不让人满意。2.5默认用的diamond 速度非常快 第一步代码&#xff1a; nohup orthofinder -t 40 -f data/ & # …

【网页实战项目设计】基于SSM的医院预约挂号系统

基于SSM的医院预约挂号系统 项目截图 开发环境与技术框架 开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&a…

实战whisper第二天:直播语音转字幕(全部代码和详细部署步骤)

直播语音实时转字幕&#xff1a; 基于Whisper的实时直播语音转录或翻译是一项使用OpenAI的Whisper模型实现的技术&#xff0c;它能够实时将直播中的语音内容转录成文本&#xff0c;甚至翻译成另一种语言。这一过程大致分为三个步骤&#xff1a;捕获直播音频流、语音识别&#x…

在线教育话术(1W字精选)

产品结构图 Nginx实现代理 问&#xff1a;我们在本机的host文件中配置了域名映射&#xff0c;都是同一个服务器。我们只需要输入对应的域名就可以到对应的界面&#xff0c;这是怎么实现的&#xff1f; 答&#xff1a;主要就是通过Nginx反向代理来实现的&#xff0c;Nginx会先…

2024-03-20 作业

作业要求&#xff1a; 1> 创建一个工人信息库&#xff0c;包含工号&#xff08;主键&#xff09;、姓名、年龄、薪资。 2> 添加三条工人信息&#xff08;可以完整信息&#xff0c;也可以非完整信息&#xff09; 3> 修改某一个工人的薪资&#xff08;确定的一个&#x…

电影aac是什么意思?如何播放、转换、编辑aac?

"电影AAC"这个术语可能是指电影中的音频编码格式。AAC&#xff08;Advanced Audio Coding&#xff09;是一种常见的音频编码格式&#xff0c;通常用于压缩音频文件&#xff0c;以在保持高质量的同时减小文件大小。在电影中&#xff0c;AAC格式的音频通常用于提供高质…

Java学习笔记NO.25

T2.编写程序实现乐手弹奏乐器。乐手可以弹奏不同的乐器从而发出不同的声音。可以弹奏的乐器包括二胡、钢琴和琵琶。要求&#xff1a; (1)定义乐器类Instrument&#xff0c;包括方法makeSound() (2)定义乐器类的子类&#xff1a;二胡Erhu、钢琴Piano和小提琴Violin (3)定义乐手类…

H12-811题库(带解析,亲测高分可以通过)

大家可以直接点赞关注后&#xff0c;加作者微信&#xff08;备注“CSDN”&#xff09;就可以获取&#xff0c;微信在文章最后&#xff01; 808、[单选题]某公司网管要进行网络规划的时候&#xff0c;能够要让PC1访问PC2的数据包从G0/0/0口走(图上G0/0/2)。PC2访问PC1的数据包从…

浅谈RPC的理解

浅谈RPC的理解 前言RPC体系Dubbo架构最后 前言 本文中部分知识涉及Dubbo&#xff0c;需要对Dubbo有一定的理解&#xff0c;且对源码有一定了解 如果不了解&#xff0c;可以参考学习我之前的文章&#xff1a; 浅谈Spring整合Dubbo源码&#xff08;Service和Reference注解部分&am…

网络世界的城关——网卡

网络世界的城关——网卡 网卡到底是什么&#xff1f;网卡的功能网卡的真面目网卡的组成网卡的种类1.基于网络连接方式分类2.基于总线接口类型分类3.基于接口类型的分类4.基于传输速度的分类5.基于应用领域的分类 网卡到底是什么&#xff1f; 网卡我们可以这样通俗地理解&#x…

游戏平台出海运营有难度吗?

随着全球互联网的飞速发展&#xff0c;游戏产业已经成为了文化娱乐领域的重要支柱。在这个背景下&#xff0c;越来越多的游戏平台开始寻求出海运营&#xff0c;以拓展海外市场&#xff0c;实现更大的商业价值。然而&#xff0c;游戏平台出海运营并非易事&#xff0c;其中涉及到…