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

news2024/9/21 10:40:16

文章目录

  • 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.Driver
    url: jdbc:mysql://localhost:3306/test-db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
    username: root
    password: root123
  main:
    allow-bean-definition-overriding: true
logging:
  level:
    com.itheima: debug
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  type-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;

    @Override
    public 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;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles;
    }

    @Override
    public String getPassword() {
        return this.sysUserPo.getPassword();
    }

    @Override
    public String getUsername() {
        return this.sysUserPo.getUsername();
    }

    /**
     * 以下字段,我的用户表设计简单,没有过期、禁用等字段
     * 这里都返回true
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public 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 {

    @Resource
    private UserMapper userMapper;

    @Resource
    private RoleMapper roleMapper;

    @Override
    public 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 {

    @Resource
    private 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;  //客户端角色详情

    @Resource
    private AuthenticationManager authenticationManager;  //注入安全配置类中定义的认证管理器Bean

    @Resource(name = "jdbcAuthorizationCodeServices")
    private AuthorizationCodeServices authorizationCodeServices;  //注入自定义的授权码模式服务配置Bean

    @Resource(name = "jdbcApprovalStore")
    private ApprovalStore approvalStore;   //授权信息保存策略





    //token令牌管理
    @Bean
    public 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令牌端点访问的安全策略
     * (不是所有人都可以来访问框架提供的这些令牌端点的)
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
        authorizationServerSecurityConfigurer.tokenKeyAccess("permitAll()")   //oauth/token_key这个端点(url)是公开的,不用登录可调
                .checkTokenAccess("permitAll()")   // oauth/check_token这个端点是公开的
                .allowFormAuthenticationForClients();  //允许客户端表单认证,申请令牌
    }



    /**
     * Oauth2.0客户端角色的信息来源:内存、数据库
     * 这里用数据库
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
        clientDetailsServiceConfigurer.withClientDetails(clientDetailsService);

    }

    /**
     * 令牌端点访问和令牌服务(令牌怎么生成、怎么存储等)
     */
    @Override
    public 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 {

    @Resource
    private UserService userService;

    //设置权限
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf()
                .disable();

    }


    //AuthenticationManager对象在Oauth2认证服务中要使用,提取放到IOC容器中
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    //指定认证对象的来源
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }


    @Bean
    public 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 {

    @Bean
    public 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";

    @Resource
    private DataSource dataSource;

    @Resource
    ResourceServerTokenServices resourceServerTokenServices;

    @Bean
    public TokenStore jdbcTokenStore() {
        return new JdbcTokenStore(dataSource);
    }


    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(RESOURCE_ID)   //资源id
                .tokenStore(jdbcTokenStore())   //告诉资源服务token在库里
                .tokenServices(resourceServerTokenServices)
                .stateless(true);
    }

    @Override
    public 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.coloradmin.cn/o/1535373.html

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

相关文章

[C#]winformYOLO区域检测任意形状区域绘制射线算法实现

【简单介绍】 Winform OpenCVSharp YOLO区域检测与任意形状区域射线绘制算法实现 在现代安全监控系统中&#xff0c;区域检测是一项至关重要的功能。通过使用Winform结合OpenCVSharp库&#xff0c;并结合YOLO&#xff08;You Only Look Once&#xff09;算法&#xff0c;我们…

Docker之docker compose!!!!

一、概述 是 Docker 官方提供的一款开源工具&#xff0c;主要用于简化在单个主机上定义和运行多容器 Docker 应用的过程。它的核心作用是容器编排&#xff0c;使得开发者能够在一个统一的环境中以声明式的方式管理多容器应用的服务及其依赖关系。 也就是说Docker Compose是一个…

利用Jmeter工具对服务器,数据库进行性能监控,压测,导出性能测试报告

Jmeter是Apache基金会旗下的一款免费,开源,轻量级的性能测试工具,主要针对web应用程序客户端/服务器进行性能测试.它可以分别测试静态、动态资源(Java Servlet,CGI Scripts,Java Object,数据库和FTP服务器等),它可以通过线程组来模拟数个用户,在一段时间内同时登录服务器,数个用…

MySQL的基础操作与管理

一.MySQL数据库基本操作知识&#xff1a; 1.SQL语句&#xff1a; 关系型数据库&#xff0c;都是使用SQL语句来管理数据库中的数据。 SQL&#xff0c;即结构化查询语言(Structured Query Language) 。 SQL语句用于维护管理数据库&#xff0c;包括数据查询、数据更新、访问控…

【机器学习】基于蝴蝶算法优化的BP神经网络分类预测(BOA-BP)

目录 1.原理与思路2.设计与实现3.结果预测4.代码获取 1.原理与思路 【智能算法应用】智能算法优化BP神经网络思路【智能算法】蝴蝶优化算法&#xff08;BOA)原理及实现 2.设计与实现 数据集&#xff1a; 数据集样本总数2000 多输入多输出&#xff1a;样本特征24&#xff0c…

WAF是什么意思?waf防火墙部署方式

防火墙中的WAF是什么意思&#xff1f;WAF是Web Application Firewall的简称&#xff0c;即Web应用防火墙&#xff08;也称网站应用级入侵防御系统&#xff09;一种专门为Web应用提供保护的网络安全设备&#xff0c;工作在应用层&#xff0c;通过执行一系列针对HTTP/HTTPS的安全…

GraalVM详细安装及打包springboot、java、javafx使用教程(打包普通JAVA项目篇)

前言 在当前多元化开发环境下&#xff0c;Java作为一种广泛应用的编程语言&#xff0c;其应用部署效率与灵活性的重要性日益凸显。Spring Boot框架以其简洁的配置和强大的功能深受开发者喜爱&#xff0c;而JavaFX则为开发者提供了构建丰富桌面客户端应用的能力。然而&#xff…

硬盘、内存、缓存(CPU)和寄存器 空间大小与存取速度的区别及设计原理

一、寄存器和存储器是不同的 很多人会将 寄存器 与 存储器 二者混淆&#xff0c;认为它们是同一个东西。但并不是&#xff01;&#xff01; 寄存器是CPU上的一个模块 存储器是 内存硬盘的统称 二、存取速度的比较 CPU(包含寄存器&#xff0c;缓存) > 内存 > 硬盘 内…

Monoxer Programming Contest 2024(AtCoder Beginner Contest 345)(A,B,C,D,E,F)

比赛链接 这场。。。好像已经是一周之前的比赛来着&#xff0c;终于补完了。 C是个披着字符串外衣的数学容斥题。D是个超级超级暴力的爆搜&#xff0c;写起来超级麻烦&#xff0c;感觉。。。真是一次酣畅淋漓的赤石。E是个DP&#xff0c;朴素想法其实比较直观&#xff0c;不过…

C# 右键快捷菜单(上下文菜单)的两种实现方式

在C#中&#xff0c;ContextMenuStrip是一种用于创建右键菜单的控件。它提供了一种方便的方式来为特定的控件或窗体添加自定义的上下文菜单选项。有两种实现方式&#xff0c;如下&#xff1a; 一.通过ContextMenuStrip控件实现 1.从工具箱中拖一个ContextMenuStrip控件到窗体上…

银行量子金融系统应用架构设计

量子金融&#xff08;即Financial-Quantum&#xff0c;简称Fin-Q&#xff09;&#xff0c;特指量子科技在金融行业中的应用。 目前&#xff0c;量子科技中以量子保密通信、量子随机数和量子计算发展进度较快&#xff0c;取得了诸多阶段性重大技术突破和商用成果&#xff0c;这…

vue学习日记13:记账清单,渲染添加删除

一.需求说明 二、实践 1.基本渲染 &#xff08;1&#xff09;代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" …

JAVA安全(偏基础)

SQL注入 SQLI(SQL Injection)&#xff0c; SQL注入是因为程序未能正确对用户的输入进行检查&#xff0c;将用户的输入以拼接的方式带入SQL语句&#xff0c;导致了SQL注入的产生。攻击者可通过SQL注入直接获取数据库信息&#xff0c;造成信息泄漏。 JDBC JDBC有两个方法获取s…

实时数仓之实时数仓架构(Doris)

目前比较流行的实时数仓架构有两类,其中一类是以Flink+Doris为核心的实时数仓架构方案;另一类是以湖仓一体架构为核心的实时数仓架构方案。本文针对Flink+Doris架构进行介绍,这套架构的特点是组件涉及相对较少,架构简单,实时性更高,且易于Lambda架构实现,Doris本身可以支…

Vue3中基本数据类型为什么需要.value,,,引用类型不需要.value

1、在v3中使用基本数据类型&#xff08;如数字、字符串、布尔值&#xff09;时&#xff0c;如果你希望响应式地更新数据并触发视图更新,需要使用ref包裹基本数据类型,然后将基本数据类型转化为响应式对象;- - - 因此当你使用ref包裹基本数据类型时,实际上得到的是一个包含.valu…

解读 Xend Finance:向 RWA 叙事拓展,构建更具包容性的 DeFi 体系

在二十世纪后&#xff0c;非洲地区陆续爆发了主权运动&#xff0c;这也让非洲大陆逐渐摆脱“殖民地”的标签。目前&#xff0c;非洲大陆公有 54 个主权国家&#xff0c;接近 15 亿且仍在飙升的人口规模&#xff0c;其 GDP 已经与印度相当&#xff0c;且仍旧处于飞速的发展进程中…

计算机服务器中了mkp勒索病毒怎么办,mkp勒索病毒解密工具流程

在网络飞速发展的时代&#xff0c;越来越多的企业离不开网络&#xff0c;利用网络可以为企业更好地开展各项工作业务&#xff0c;帮助企业有效调整发展方向与规划&#xff0c;但网络是一把双刃剑&#xff0c;在为人们提供便利的同时&#xff0c;也为企业的数据安全带来严重威胁…

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

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

Qt登录页面

#include "mywidget.h" #include "ui_mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget) {ui->setupUi(this);//接收动图QMovie *mv new QMovie(":/pictrue/luori.gif");ui->loglab->setMovie(…

ssm项目(tomcat项目),定时任务(每天运行一次)相同时间多次重复运行job 的bug

目录标题 一、原因 一、原因 debug本地调试没有出现定时任务多次运行的bug&#xff0c;上传到服务器就出现多次运行的bug。&#xff08;war的方式部署到tomcat&#xff09; 一开始我以为是代码原因&#xff0c;或者是linux和win环境不同运行定时任务的方式不一样。 但是自己…