Spring Security OAuth 2.0

news2025/1/14 18:38:56

1 概念

OAuth 2.0 到底是什么呢?我们先从字面上来分析下。OAuth 2.0 一词中的字母 “O” 是 Open 的简称,表示 “开放” , “Auth” 表示 “授权”,连在一起就表示 “开放授权”。

OAuth 2.0是一种授权框架,提供了一套规范和协议,用于实现授权流程和访问令牌的管理,而非单个的授权协议。在不暴露用户凭据的情况下,允许第三方应用程序访问用户在另一个应用程序中存储的资源。OAuth 2.0的主要目的是为了简化授权流程,提高安全性,并支持多种应用场景。它已经成为许多互联网服务和应用程序的标准授权协议。

这也是为什么我们使用 OAuth 的场景,通常发生在开放平台的环境下。 OAuth2.0是OAuth协议的延续版本,但不向 后兼容OAuth 1.0即完全废止了OAuth1.0。很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服 务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。

Oauth2和Oauth1

OAuth 2.0和OAuth 1.0是两种不同的授权协议,它们的主要区别在于授权流程和安全性。

OAuth 1.0是一种基于数字签名的授权协议,它使用了一种称为“OAuth签名”的方法来验证请求的合法性。在OAuth 1.0中,客户端需要使用应用程序密钥和访问令牌密钥来生成签名,然后将签名附加到请求中。服务提供商使用相同的密钥来验证签名的有效性。OAuth 1.0的优点是安全性高,因为它使用了数字签名来验证请求的合法性。但是,OAuth 1.0的缺点是授权流程比较复杂,需要多次交互,不太适合移动设备等资源受限的环境。

OAuth 2.0是一种基于令牌的授权协议,它使用了访问令牌和刷新令牌来授权访问。在OAuth 2.0中,客户端需要向授权服务器发送请求,以获取访问令牌。然后,客户端可以使用访问令牌来访问受保护的资源。如果访问令牌过期,客户端可以使用刷新令牌来获取新的访问令牌。OAuth 2.0的优点是授权流程简单,易于使用,并且适用于各种不同的应用程序和设备。但是,OAuth 2.0的缺点是安全性相对较低,因为它依赖于令牌来授权访问,而令牌可能会被窃取或滥用。 使用场景方面,OAuth 1.0适用于需要高安全性的场景,例如金融应用程序等。OAuth 2.0适用于需要简单易用的场景,例如社交媒体应用程序等。

OAuth2和Spring Security是两个不同但相关的概念。

  1. OAuth2(开放授权):OAuth2是一种授权框架,用于在应用程序之间进行安全的资源访问授权。它允许用户授权第三方应用程序代表他们访问受保护的资源,而无需共享其凭据(例如用户名和密码)。OAuth2定义了一组授权流程和协议,包括授权码授权、密码授权、客户端凭证授权和简化授权等。OAuth2通常用于构建安全的API和Web应用程序,使用户能够授权其他应用程序访问其数据,同时提供了更好的安全性和用户控制。

  2. Spring Security:Spring Security是一个用于身份验证和授权的框架,它提供了一种在应用程序中实现安全性的方式。Spring Security使开发人员能够轻松地集成各种身份验证和授权机制,包括基于表单的身份验证、基于HTTP Basic/Digest的身份验证、基于LDAP的身份验证、基于OAuth2的身份验证等。它提供了一套功能强大的API和配置选项,以保护应用程序的资源并确保只有经过授权的用户可以访问。

联系和区别:

  • 联系:OAuth2和Spring Security可以结合使用,以实现安全的身份验证和授权机制。Spring Security提供了对OAuth2的支持,可以使用Spring Security OAuth2模块轻松地构建OAuth2服务器和客户端应用程序。

  • 区别:OAuth2是一种授权框架,用于授权第三方应用程序访问受保护的资源。Spring Security是一个用于身份验证和授权的框架,提供了一种在应用程序中实现安全性的方式。OAuth2是Spring Security中的一个功能模块,用于实现OAuth2授权机制。除了OAuth2之外,Spring Security还提供了其他身份验证和授权机制的支持,例如基于表单的身份验证和基于角色的授权。

总之,OAuth2和Spring Security是两个不同的概念,但可以结合使用以提供安全的身份验证和授权机制,使应用程序能够安全地保护和共享资源。

2 认证流程

1)客户端请求第三方授权

用户进入浏览器登录页面,点击微信的图标以微信账号登录系统,用户是自己在微信里信息的资源拥有者.

 

 2)点击“微信”出现一个二维码,此时用户扫描二维码,开始给码云授权。

 

 

3)客户端获取到授权码,请求认证服务器申请令牌

此过程用户看不到,客户端应用程序请求认证服务器,请求携带授权码。

4)认证服务器向客户端响应令牌

微信认证服务器验证了客户端请求的授权码,如果合法则给客户端颁发令牌,令牌是客户端访问资源的通行证。

此交互过程用户看不到,当客户端拿到令牌后,用户在码云看到已经登录成功。

5)客户端请求资源服务器的资源

客户端携带令牌访问资源服务器的资源。

码云网站携带令牌请求访问微信服务器获取用户的基本信息。

6)资源服务器返回受保护资源

资源服务器校验令牌的合法性,如果合法则向用户响应资源信息内容。

以上认证授权详细的执行流程如下:

 

 

3 OAuth2.0角色

 

  • 客户端 浏览器

本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:Android客户端、Web客户端(浏览器端)、微信客户端等。

  • 资源拥有者 客户本人

通常为用户,也可以是应用程序,即该资源的拥有者。

  • 授权服务器(也称认证服务器) 微信

用于服务提供商对资源拥有的身份进行认证、对访问资源进行授权,认证成功后会给客户端发放令牌 (access_token),作为客户端访问资源服务器的凭据。本例为微信的认证服务器。

  • 资源服务器 微信存储的用户信息

存储资源的服务器,本例子为微信存储的用户信息。

服务提供商不能允许随便一个客户端就接入到它的授权服务器,所以服务提供商会给准入的接入方一个身份,用于接入时的凭据:

client_id: 客户端标识

client_secret:客户端密钥

因此,准确来说,授权服务器对两种OAuth2.0中的两个角色进行认证授权,分别是资源拥有者客户端

4 认证方式

1 授权码模式

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

 

  1. 按照上述时序图举个简单的例子,小明使用微信授权方式登录app。

    小明点开手机里面的app,他不想手动输入账号密码登录,而是采用了微信登录。 点击微信登录按钮,app拉起授权页面。 微信授权服务器则生成授权页面,用户看见授权页面点击确定按钮进行授权。 微信授权服务器校验用户身份合法性后生成请求code,点击确认授权后,页面跳转至app页面并携带请求code(授权码)。 app拿到授权码后,携带授权码向授权服务器获取访问令牌access_token。 拿到access_token后,则携带access_token向受保护资源发起访问。 校验access_token无误后,受保护资源返回资源数据(个人的身份数据,昵称,地区等信息)。 成功登录app,小明继续使用app内的功能。

2 简化模式

简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

这种方式一般用于没有服务器的单页面应用,因为他们没有服务器端去拿授权码换取 Token。

 

 

  1. 客户端将用户重定向到授权服务器的授权页面,并请求授权。

  2. 用户在授权页面上输入用户名和密码,授权服务器验证用户身份。

  3. 授权服务器向用户显示授权请求的范围和权限,并要求用户授权。

  4. 用户同意授权请求。

  5. 授权服务器将访问令牌直接返回给浏览器,作为URL的一部分。

  6. 浏览器将访问令牌传递给客户端应用程序,客户端应用程序使用访问令牌来访问受保护的资源。

    简化模式的优点是简单易用,适用于客户端是浏览器的应用程序,但缺点是安全性较低,因为访问令牌直接传递给浏览器,容易被恶意攻击者截获。因此,简化模式适用于访问受保护的资源较少的情况。

3 密码模式

密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。

 

4 客户端认证

客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

 

2 Spring Cloud Security OAuth2

Spring Cloud Security OAuth2是一个基于Spring Cloud的OAuth2认证和授权框架,它提供了一系列的组件和工具,帮助开发者快速构建安全的分布式系统。

Spring Cloud Security OAuth2的核心组件包括:

  1. OAuth2认证服务器:用于颁发和验证访问令牌,支持多种授权方式,如授权码模式、密码模式、客户端模式等。

  2. 资源服务器:用于保护受保护的资源,验证访问令牌的有效性,支持多种访问令牌类型,如JWT、Opaque等。

  3. 客户端:用于向OAuth2认证服务器请求访问令牌,支持多种客户端类型,如Web应用、移动应用、第三方应用等。

  4. Spring Security集成:Spring Cloud Security OAuth2与Spring Security深度集成,提供了一系列的安全过滤器和认证提供者,支持多种认证方式,如用户名密码认证、LDAP认证、OAuth2认证等。

Spring Cloud Security OAuth2的优点包括:

  1. 简单易用:提供了一系列的注解和配置,帮助开发者快速构建安全的分布式系统。

  2. 可扩展性强:提供了一系列的扩展点,如自定义认证提供者、自定义授权方式等,方便开发者根据实际需求进行扩展。

  3. 安全性高:基于OAuth2协议,提供了多种授权方式和访问令牌类型,保证了系统的安全性。

  4. 社区活跃:是Spring Cloud社区的一部分,拥有庞大的社区支持和活跃的开发者,保证了框架的稳定性和可靠性。

 

 

 

认证流程如下:

1、客户端请求UAA授权服务进行认证。

2、认证通过后由UAA颁发令牌。

3、客户端携带令牌Token请求资源服务。

4、资源服务校验令牌的合法性,合法即返回资源信息。

3 项目和模式

1 项目搭建

Auth-server

  • 依赖

 


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- for OAuth 2.0 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.6.RELEASE</version>
</dependency>


<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
</dependency>

<!-- 连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.16</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  • 配置文件

 

# 应用服务 WEB 访问端口
server.port=8001
        
#-----------------------------Mysql通用
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
#mybatis.config-location=classpath:mybatis-config.xml
mybatis.type-aliases-package=com.xinzhi.model
mybatis.mapper-locations=classpath:mapper/*.xml


#-----------------------------Mysql
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

#----------------------------redis
spring.redis.database=13
spring.redis.host=localhost
spring.redis.port=6379
  • security配置类

package com.xinzhi.config;

import com.xinzhi.service.SpringUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;

@Configuration
public class SecurityConfig  extends WebSecurityConfigurerAdapter {

    @Resource
    SpringUserDetailsService userDetailsService;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
       //配置了身份验证的用户详细信息加载器和密码加密方式。
        //使用了前面注入的userDetailsService对象作为用户详细信息加载器,
        //并使用passwordEncoder方法返回的PasswordEncoder对象进行密码加密。
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }
	//创建了一个BCryptPasswordEncoder对象,用于对密码进行加密。
    //BCryptPasswordEncoder是Spring Security提供的密码加密工具。
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

 

  • UserDetailsService

 

import com.xinzhi.dao.UserMapper;
import com.xinzhi.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class SpringUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {

        User user = userMapper.selectByUsername(username);
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }

        //获得用户角色列表id
        List<String> roleCodes = userMapper.selectRolesByUserId(user.getId());
        //通过角色列表获取权限列表
        List<String> menus = userMapper.selectMenuByRoles(roleCodes);
		// 对角色列表进行处理,添加前缀"ROLE_",表示这是一个角色。
        roleCodes = roleCodes.stream().distinct().map(r->"ROLE_"+r).collect(Collectors.toList());
		//将角色列表添加到权限列表中
        menus.addAll(roleCodes);
       //将权限列表设置为用户的权限,并将其转换为Spring Security所需的格式。
        user.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",",menus)));
        return user;
    }
}
  • dao

package com.xinzhi.dao;

import com.xinzhi.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface UserMapper {

    User selectByUsername(String usename);

    List<String> selectRolesByUserId(Integer id);

    Integer updatePassword(@Param("username") String username, @Param("password") String password);

    List<String> selectMenuByRoles(@Param("roleCodes")List<String> roleCodes);

}

 

  • mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xinzhi.dao.UserMapper" >

  <sql id="Base_Column_List" >
    id, username, password, enabled, accountNonExpired, accountNonLocked, credentialsNonExpired
  </sql>
    <update id="updatePassword">
        update user set password=#{password} where username=#{username}
    </update>

    <select id="selectByUsername" resultType="com.xinzhi.model.User">
    select
    <include refid="Base_Column_List" />
    from user
    where username = #{username}
  </select>
  <select id="selectRolesByUserId" resultType="string">
    select
        r.name
    from
        role r
     left join
        user_role ur
     on r.id = ur.rid
     where ur.uid=#{uid}
  </select>
    <select id="selectMenuByRoles" resultType="java.lang.String">
        select pattern from menu m
        left join menu_role rm on m.id=rm.mid
        left join role r on r.id=rm.rid
        where
        <foreach collection="roleCodes" open="r.name in(" item="name" close=")" separator=",">
            #{name}
        </foreach>
    </select>

</mapper>

 

  • 认证服务器(授权码模式)

import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import javax.annotation.Resource;

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

    @Resource
    PasswordEncoder passwordEncoder;

    //这个位置我们将Client客户端注册信息写死,后面章节我们会讲解动态实现
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //指定客户端详细信息将存储在内存中
        clients.inMemory()
                .withClient("102051867").secret(passwordEncoder.encode("PjrmUzGKAmAKGySi")) // Client 账号、密码。
                .redirectUris("http://localhost:8888/callback") // 配置回调地址,选填。
                .authorizedGrantTypes("authorization_code") // 授权类型
                .scopes("all"); // 客户端的授权范围 Scope
    }

    //配置授权服务器的安全性
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        
        oauthServer
                .tokenKeyAccess("permitAll()")//获取令牌(token)的请求的访问权限
                .checkTokenAccess("permitAll()")//校验令牌(token)的请求的访问权限
                .allowFormAuthenticationForClients();//配置允许客户端进行表单身份验证
    }
}

# 说明
- @EnableAuthorizationServer注解表示开启认证服务器功能。
- 这里的配置实际上和我们在QQ互联上的注册信息,client就是APP ID,secret就是APP Key,回调地址就是我们在QQ互联配置的应用回调地址。
- .authorizedGrantTypes("authorization_code") 指定使用授权码模式,进行认证
- scopes是一组权限的集合,表示可以申请的权限范围,该权限可以被验证,我们后续会讲

 数据库sql

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pattern` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES (1, '/admin/**');
INSERT INTO `menu` VALUES (2, '/user/**');
INSERT INTO `menu` VALUES (3, '/guest/**');

-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
DROP TABLE IF EXISTS `menu_role`;
CREATE TABLE `menu_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rid` int(11) NULL DEFAULT NULL,
  `mid` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of menu_role
-- ----------------------------
INSERT INTO `menu_role` VALUES (1, 1, 1);
INSERT INTO `menu_role` VALUES (2, 1, 2);
INSERT INTO `menu_role` VALUES (3, 2, 2);
INSERT INTO `menu_role` VALUES (4, 3, 3);

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'product', '商品管理员');
INSERT INTO `role` VALUES (2, 'admin', '系统管理员');
INSERT INTO `role` VALUES (3, 'user', '用户管理员');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `enabled` tinyint(1) NULL DEFAULT NULL,
  `accountNonExpired` tinyint(1) NULL DEFAULT NULL,
  `accountNonLocked` tinyint(1) NULL DEFAULT NULL,
  `credentialsNonExpired` tinyint(1) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '$2a$10$YeilFczw8sQ1RBthIFLRW.pI/KkdbDofOmZ0w5Wfq9mMMyL3ylC/.', 1, 1, 1, 1);
INSERT INTO `user` VALUES (2, 'admin', '$2a$10$YeilFczw8sQ1RBthIFLRW.pI/KkdbDofOmZ0w5Wfq9mMMyL3ylC/.', 1, 1, 1, 1);
INSERT INTO `user` VALUES (3, 'han', '$2a$10$YeilFczw8sQ1RBthIFLRW.pI/KkdbDofOmZ0w5Wfq9mMMyL3ylC/.', 1, 1, 1, 1);

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NULL DEFAULT NULL,
  `rid` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 2, 2);
INSERT INTO `user_role` VALUES (4, 3, 3);

SET FOREIGN_KEY_CHECKS = 1;

 

  • 访问获取授权码,访问浏览器会进去登录页面,输入正确的密码以后,能够获取到授权码。

http://localhost:8001/oauth/authorize?client_id=102051867&redirect_uri=http://localhost:8888/callback&response_type=code&scope=all

 

# 说明
- /oauth/authorize为获取授权码的地址,由Spring Security OAuth项目提供
- client_id即我们认证服务器中配置的client
- redirect_uri即回调地址,授权码的发送地址该地址为第三方客户端应用的地址。要和我们之前配置的回调地址对上。
- response_type=code表示希望获取的响应内容为授权码
- scope表示申请的权限范围

2 授权码获取token

  • 通过授权码获取token, 在postman中模拟请求

 

localhost:8001/oauth/token?grant_type=authorization_code&code=&redirect_uri=http://localhost:8888/callback&scope=all&client_secret=PjrmUzGKAmAKGySi&client_id=102051867

 

3 密码模式

1 配置OAuth2AuthorizationServer

  • 依赖注入AuthenticationManager ,不注入会报错

  • 配置支持password密码模式

 

  • 因为要使用AuthenticationManager ,所以在Spring Security全局配置SecurityConfig.java中加入如下代码,将其初始化为Spring bean

 

 

SecurityConfig类添加

//创建和配置一个身份验证管理器的Bean,用于处理应用程序中的身份验证请求
@Bean(name= BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }



OAuth2AuthorizationServer类添加  
 
 @Resource
    private AuthenticationManager authenticationManager;

    public void configure(AuthorizationServerEndpointsConfigurer endpoints){
      //配置授权服务器的端点使用指定的AuthenticationManager对象进行身份验证
      endpoints.authenticationManager(authenticationManager);
    }   

2 postman测试

 

 localhost:8001/oauth/token?grant_type=password&redirect_uri=http://localhost:8888/callback&scope=all&client_secret=PjrmUzGKAmAKGySi&client_id=102051867&username=admin&password=123

4 简化模式

简化模式是授权码模式的“简化”,所以只需要在以上配置的基础上,为authorizedGrantTypes加上implicit配置即可。

  使用如下链接获取授权码,浏览器打开 ,输入用户名和密码直接获取到授权码。implicit

http://localhost:8001/oauth/authorize?client_id=102051867&redirect_uri=http://localhost:8888/callback&response_type=token

 

5 客户端模式

  • 客户端模式实际上是密码模式的简化,无需配置或使用资源拥有者账号。因为它没有用户的概念,直接与授权服务器交互,通过 Client 的编号(client_id)和密码(client_secret)来保证安全性。

  • 配置方式为authorizedGrantTypes加上client_credentials配置即可

 

localhost:8001/oauth/token?grant_type=client_credentials&redirect_uri=http://localhost:8888/callback&scope=all&client_secret=PjrmUzGKAmAKGySi&client_id=102051867

3 令牌刷新

Spring Security OAuth实现认证服务器的四种授权模式:授权码模式、简化模式、密码模式、客户端模式。每一个模式的最终认证结果都是我们获取到了一个AccessToken,后续我们可以使用这个Token访问资源服务器。需要注意的一点是AccessToken是有有效期的,如请求结果中的expires_in字段。

1 配置令牌刷新

  • 配置方式为authorizedGrantTypes加上refresh_token配置

  • 为OAuth2AuthorizationServer配置类加入UserDetailsService,刷新令牌的时候需要用户信息

 

 

OAuth2AuthorizationServer类添加

@Resource
private SpringUserDetailsService userDetailsService;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
    		     										 endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService);
}
//userDetailsService用于配置用户详情服务,它提供根据用户名加载用户详细信息的功能。

 

  • 这样当我们通过授权码模式和密码模式请求AccessToken的时候,返回结果中将多出一个字段refresh_token。(客户端模式和简化模式是不支持refresh_token)

 

 localhost:8001/oauth/token?grant_type=password&redirect_uri=http://localhost:8888/callback&scope=all&client_secret=PjrmUzGKAmAKGySi&client_id=102051867&username=admin&password=123

 

3 refresh_token、access_token的有效期。

 @Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
        .withClient("102051867").secret(passwordEncoder.encode("PjrmUzGKAmAKGySi")) // Client 账号、密码。
        .redirectUris("http://localhost:8888/callback") // 配置回调地址,选填。
        .authorizedGrantTypes("authorization_code", "password", "implicit", "client_credentials", "refresh_token") // 授权码模式
        .scopes("all") // 可授权的 Scope
        .accessTokenValiditySeconds(27*3600)  //token保留时间   将访问令牌的有效期设置为27小时 97200秒
        .refreshTokenValiditySeconds(3600);  // 将刷新令牌的有效期设置为1小时,即3600秒。
}

4 AccessToken访问资源

1 资源服务和认证服务在一起的方式

  • 在刚才的项目中添加controller

 

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "This is the resource!";
    }
}

网页访问:localhost:8001/api/hello

  • 在config文件下添加资源授权配置

package com.xinzhi.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
//通过在Spring Boot应用程序的配置类上添加@EnableResourceServer注解,可以将应用程序标记为资源服务器,并启用相关的功能。这个注解会自动配置一些基本的资源服务器功能
//1验证和解析传入的访问令牌(Access Token):资源服务器会验证传入请求的访问令牌,以确保其有效性和合法性。
//2授权访问控制:资源服务器会根据访问令牌中的权限信息来控制对资源的访问。只有持有有效且包含适当权限的访问令牌的用户才能成功访问受保护的资源。
//3处理OAuth 2.0错误:资源服务器会处理与OAuth 2.0相关的错误,例如访问令牌无效或已过期等。
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()//配置对请求的授权规则
             .anyRequest().authenticated()//所有请求都需要进行身份验证提,提供有效的访问令牌才能访问资源
             .and()//连接多个安全配置
             .requestMatchers()
             .antMatchers("/api/**");//配置了请求匹配规则,限定了只有以"/api/"开头的请求才会被拦截和授权。
    }

}

 

  • 用上面获取到的token尝试访问: http://localhost:8001/api/hello

 

 

2 资源服务和认证服务不在一起的方式

1 资源服务器改造

在上个认证服务的基础上,删除OAuth2ResourceServer认证的代码,controller的代码也用不上

2 资源服务器远程认证

创建项目 Resource-server

  • 资源服务器除了没有OAuth2AuthorizationServer认证,其他的配置和认证服务一样

  • 添加资源服务配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;

@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {
	
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
             .anyRequest().authenticated()
             .and()
             .requestMatchers()
             .antMatchers("/api/**");
    }

    /**
     * 资源服务器去认证服务器远程认证
     * @return
     */
    @Primary
    @Bean
    public RemoteTokenServices tokenService() {
        final RemoteTokenServices tokenService = new RemoteTokenServices();
        tokenService.setCheckTokenEndpointUrl("http://localhost:8001/oauth/check_token");
        tokenService.setClientId("102051867");
        tokenService.setClientSecret("PjrmUzGKAmAKGySi");
        return tokenService;
    }

    /**
     * 配置token认证方式
     * @param resources
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.tokenServices(tokenService());
    }
}

 

  • 启动认证服务和资源服务,获取到认证服务的token,携带token访问资源服务

  • 测试:localhost:8002/api/hello

  • 这种方式每次访问资源服务器都要去认证服务器去认证, 增加了网络资源的消耗,增加了接口资源的访问时长。

  • 创建数据库

  • 3 JdbcTokenStore认证

DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(256) DEFAULT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

 

  • 资源服务器添加数据库认证方式,注释远程认证方式

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
             .anyRequest().authenticated()
             .and()
             .requestMatchers()
             .antMatchers("/api/**");
    }

    @Resource
    private DataSource dataSource;

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

    @Bean
    @Primary
    public DefaultTokenServices tokenService() {
        //创建了一个DefaultTokenServices的实例。
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());//设置了TokenStore,用于存储令牌
        return defaultTokenServices;
    }

    /**
     * 配置token认证方式
     * @param resources
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.tokenServices(tokenService());
    }

    /**
     * 资源服务器去认证服务器远程认证
     * @return
     */
//    @Primary
//    @Bean
//    public RemoteTokenServices tokenService() {
//        final RemoteTokenServices tokenService = new RemoteTokenServices();
//        tokenService.setCheckTokenEndpointUrl("http://localhost:8001/oauth/check_token");
//        tokenService.setClientId("client1");
//        tokenService.setClientSecret("123456");
//        return tokenService;
//    }
}

 

  • 认证服务器同样需要添加数据库认证方式

 @Resource
private DataSource dataSource;

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

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints
        .tokenStore(tokenStore())
        .authenticationManager(authenticationManager)
        .userDetailsService(userDetailsService);
}

 

 

  • 重新启动两个服务,访问认证服务获取token的时候,去查看数据库,数据库里就有了token信息

 

 

  • 资源服务携带token访问

 

 

4 RedisTokenStore

  • 认证和资源服务器导入redis依赖

 

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

  • 认证和资源服务器,将TokenStore配置信息修改如下 ,并注释jdbcTokenStore

@Resource
private RedisConnectionFactory connectionFactory;

@Bean
public TokenStore tokenStore() {
    RedisTokenStore redis = new RedisTokenStore(connectionFactory);
    return redis;
}

 

  • 启动认证和资源服务器,获取token可以查看到redis中已经保存好数据了。

 

  • 携带token访问资源服务器

 

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

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

相关文章

Linux下做性能分析5:Amdahl模型

[前言] 前一个Blog我们使用了一个叫cs的程序作为例子&#xff0c;那个程序是我为了举例子临时写的&#xff0c;这个代码我共享在这里&#xff1a;GitHub - nekin2012/btest。后面我要再举例子的话&#xff0c;就都加到这个地方来。由于这些代码没有经过最基本的软件质量保证工…

Visual Studio 自定义的颜色字体不生效

问题描述&#xff1a; 1、dll1中引用第三方库的类不识别&#xff0c;颜色黑白&#xff0c;自定义颜色不生效&#xff1b;定义的是结构体 2、在dll2引用另一个dll1中的结构体。结构体不识别&#xff0c;今天成员函数cpp中自定义颜色不生效。 问题解决方式&#xff1a; 全部清…

基于linux下的高并发服务器开发(第一章)- GDB调试(4)1.16

05 / GDB命令-调试命令 在第8行打上断点和bubbleSort打上断点 在第16行也打上断点&#xff0c;然后i b查看断点 执行run&#xff0c;停在第一个断点&#xff0c;这一行还没有执行 接下来按c继续执行&#xff0c;停在了bubbleSort list bubble.cpp:bubbleSort

【C】动态内存管理详解

动态内存管理 C/C内存开辟区域为什么存在动态内存分配&#xff1f; 动态内存函数的介绍&#xff08;在头文件stdlib.h中&#xff09;mallocfreecallocrealloc 常见的动态内存的错误对NULL解引用操作对动态开辟的空间进行越界访问对非动态开辟的空间进行free释放使用free释放动态…

探索Python异常:让程序不再崩溃!

文章目录 前言什么是异常捕获异常基本语法捕获指定类型异常捕获多个指定类型的异常捕获异常描述信息捕获所有异常elsefinally 异常的嵌套自定义异常结语 前言 在编程的世界中&#xff0c;我们常常会面对各种各样的错误和异常情况。尤其当我们使用Python这样的高级编程语言时&a…

4. 设计测试用例 (一) 等价类 边界值 判定表

目录 1. 设计测试用例的基本要素 1.1 测试用例概念 1.2 测试用例要素 1.3 测试用例的重要性 2. 测试用例设计方法 2.1 基于需求设计测试用例 步骤 2.2 练习设计测试用例 2.3 具体设计测试用例方法 2.3.1 等价类 设计测试用例步骤 举例 2.3.2 边界值 设计测试用例…

PowerDesigner 数据库建模使用详解

目录 一、前言 二、PowerDesigner概述 2.1 PowerDesigner核心能力 2.1.1 集成多种建模能力 2.1.2 自动生产代码能力 2.1.3 强大的逆向工程能力 2.1.4 可扩展的企业库解决方案 2.2 PowerDesigner常用的几种模型 2.2.1 概念模型 2.2.2 逻辑数据模型 2.2.3 物理模型 2.2…

综合能源系统(2)——综合能源系统典型应用场景

综合能源系统关键技术与典型案例  何泽家&#xff0c;李德智主编 根据空间分布范围特征&#xff0c;综合能源系统可大致划分为楼宇级、园区级以及区域级。楼宇级综合能源系统适用于办公楼、家庭、商场等单一主体区域&#xff0c;投资、建设和运营模式较为简单&#xff0c;技术…

arm学习stm32芯片学习方波启动蜂鸣器,马达,风扇,裸机开发,soc

main.c #include "pwm.h" extern void printf(const char *fmt, ...); void delay_ms(int ms) {int i,j;for(i 0; i < ms;i)for (j 0; j < 1800; j); } int main() {//蜂鸣器初始化hal_pwm_beep_init1();//马达hal_pwm_motor_init1();//风扇hal_pwm_blower_…

基于Vue3+LeaderLine实现画线测距及线条自由调整

先看下效果&#xff1a;我们画线后可以根据比例关系自动计算距离&#xff0c;并且线条不对可以自由调整 <template><div id"image-detail"><el-image :src"myImageUrl" style"height: auto; width: 800px;" fit"scale-dow…

Django实现接口自动化平台(十)自定义action names【持续更新中】

相关文章&#xff1a; Django实现接口自动化平台&#xff08;九&#xff09;环境envs序列化器及视图【持续更新中】_做测试的喵酱的博客-CSDN博客 深入理解DRF中的Mixin类_做测试的喵酱的博客-CSDN博客 python中Mixin类的使用_做测试的喵酱的博客-CSDN博客 本章是项目的一…

高数笔记5(第一章函数 极限 连续-第三节-函数的连续性)

目录 第三节 函数的连续性&#xff08;1&#xff09;函数的连续性例1&#xff08;补充定义&#xff0c;函数连续&#xff09;例4&#xff08;无穷小*有界量&#xff09;例6 &#xff08;补充定义&#xff0c;三角函数的代换的妙用&#xff09; &#xff08;2&#xff09;连续函…

7.带你入门matlab偏斜度和峰度(matlab程序)

峰度&#xff08;Kurtosis&#xff09;与偏态&#xff08;Skewness&#xff09;就是量测数据正态分布特性的两个指标。 峰度&#xff08;Kurtosis&#xff09; 峰度衡量数据分布的平坦度&#xff08;flatness&#xff09;&#xff0c;即数据取值分布形态陡缓程度的统计量。它…

C#,数值计算——柯西分布(Cauchy distribution)的计算方法与源程序

柯西分布&#xff08;Cauchy distribution&#xff09;简介 The Cauchy distribution, also called the Lorentzian distribution or Lorentz distribution, is a continuous distribution describing resonance behavior. It also describes the distribution of horizontal …

ASEMI快恢复二极管MUR2080CTR资料,MUR2080CTR参数

编辑-Z MUR2080CTR是一种高压、超快恢复整流二极管。它通常用于各种电子应用&#xff0c;如电源、逆变器和电机控制电路。该二极管设计用于处理高压和高频开关&#xff0c;适用于需要快速高效整流的应用。 MUR2080CTR二极管的一个关键特性是其超快的恢复时间。这意味着它可以非…

从零开始 Spring Cloud 2:Eureka 注册中心

从零开始 Spring Cloud 2&#xff1a;Eureka 注册中心 图源&#xff1a;laiketui.com Eureka 常被用作 Spring Cloud 的注册中心&#xff0c;用于注册微服务的接口提供方。 在上一篇文章中&#xff0c;我们实现了两个子模块互相调用接口&#xff0c;但存在一个缺陷&#xff0…

旅游管理系统的设计与实现(论文+源码)_kaic

摘 要 旅游业走过了改革开放&#xff0c;到现在依旧蓬勃发展。但是放眼国际社会&#xff0c;我们在旅游业发展的深度和广度上所做的努力还远远不够。在中国&#xff0c;旅游业也将成为经济崛起中的重要一环。目前&#xff0c;我们生活在一个信息时代里。无论是工作&#xff0c;…

哈希表的原理

哈希概念 线性表、树结构的查找方式都是以关键字的比较为基础&#xff0c;查找效率比较低&#xff0c;顺序表的时间复杂度是O&#xff08;n&#xff09;&#xff0c;平衡树中为树的高度&#xff0c;即O&#xff08;logn&#xff09;&#xff0c;搜素的效率取决于搜索过程的元素…

归并排序的递归和非递归

基本思想 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide andConquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列有序&a…

OpenCV项目开发实战--详细介绍如何进行边缘轮廓检测 (Python/C++)-附源码

使用轮廓检测​​,我们可以检测对象的边界,并轻松在图像中定位它们。它通常是许多有趣应用的第一步,例如图像前景提取、简单图像分割、检测和识别。 因此,让我们使用 OpenCV 来了解轮廓和轮廓检测,并亲眼看看如何使用它们来构建各种应用程序。 轮廓在计算机视觉中的应用