Spring Security OAuth 2.0

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访问资源服务器

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务架构Ribbon与OpenFeign的使用 【快速入门】

一、实现负载均衡&#x1f349; 1.什么是负载均衡&#x1f95d; 通俗的讲&#xff0c; 负载均衡就是将负载&#xff08;工作任务&#xff0c;访问请求&#xff09;进行分摊到多个操作单元&#xff08;服务器,组件&#xff09;上进行执行。 根据负载均衡发生位置的不同,一般分…

(Onenet)STM32L+BC20+MQTT协议传输温湿度,ADC,电压,GPS数据到Onenet物联网平台

1、材料准备 准备以下材料 2、设备连接 2.1 插入物联网卡 首先把BC20核心板从开发板上拆下来 然后将物联卡放置在BC20核心板内 物联卡放置完成将BC20核心板重新插入到开发板内&#xff08;注意不要弄错方向&#xff09; 同时接入天线 2.2 连接ST-Link仿真器 用3条杜邦线接…

SpringBoot使用Redis作为缓存器缓存数据的操作步骤以及避坑方案

1.非注解式实现 2.1使用之前要明确使用的业务场景 例如我们在登录时&#xff0c;可以让redis缓存验证码&#xff0c;又如在分类下显示菜品数据时&#xff0c;我们可以对分类和菜品进行缓存数据等等。 2.2导入Redis相关依赖 <dependency><groupId>org.springfra…

vue3 前端编码规范

prettier 配置 1. vscode 安装prettier 的 插件 2. 新建 .prettierrc 文件 {"semi": false, // 不尾随分号"singleQuote": true, // 使用单引号"trailingComma": "none" // 多行逗号分隔的语法&#xff0c;最后一行不加逗号 }eslin…

数据库应用:MySQL备份与恢复

目录 一、理论 1.数据备份 2.完全备份与恢复 3.完全备份与恢复应用 4.增量备份与恢复 5.增量备份与恢复应用 6.使用脚本备份 7.日志管理 二、实验 1.完全备份与恢复 2.增量备份与恢复 3.使用脚本备份 三、问题 1.mysqldump报错 四、总结 一、理论 1.数据备份 …

HDFS与MapResource笔记

客户端向NN请求上传文件 NN回应可以上传 请求上传块,返回DN 所以后面就比较慢 找最近的服务器进行 64K发到1节点,1节点立刻发给2节点,同时1节点自动开始落盘,这里,3个节点是同时落盘的. 因为缓存是在内存中,而持久化是将数据存到磁盘上. 副本节点选择: 1.安全:放不同机架 2.速…

销售易的12年与七个瞬间

导读&#xff1a;企业级没有捷径 12年对一家企业意味着什么&#xff1f; 在消费互联网领域&#xff0c;12年足够长&#xff0c;短短几年内上市的故事过去屡见不鲜。在企业服务的toB领域&#xff0c;产业成熟和企业发展的时间维度被拉长&#xff0c;但故事同样精彩。 2023年7月1…

flask实现get和post请求

1、实现get请求 在项目根目录创建app.py 代码如下&#xff1a; from flask import Flask,render_template,requestapp Flask(__name__)app.route("/regist/user/", methods[GET]) def regist():return render_template("regist.html") #默认去templat…

招聘小程序制作:连接人才与企业

随着人才市场的竞争日益激烈&#xff0c;招聘小程序成为了企业寻找优秀人才和求职者找到理想工作的重要工具。通过招聘小程序&#xff0c;企业可以发布招聘信息、筛选简历&#xff0c;而求职者可以浏览职位、提交简历等。 招聘小程序的好处 精准匹配人才&#xff1a;招聘小程序…

Java二叉树

目录 一、树形结构 1.1 概念 1.2 树的性质 1.3 树的表示形式 二、二叉树 2.1 概念 2.2 两种特殊的二叉树 2.3 二叉树的性质 2.4 二叉树的存储 2.5 二叉树的基本操作 2.5.1 二叉树的遍历 2.5.2 二叉树的基本操作 一、树形结构 1.1 概念 树是一种非线性的数据结构&#xff0…

Linux搭建node环境-MobaXterm+node+pm2安装

1.登录session 2.安装X11-forwarding 我也不知道这个有什么用&#xff0c;但是有个叉叉在那里有点难受&#xff0c;就把它解决了什么是X11-forwarding&#xff1f;怎么使用&#xff1f; yum install xorg-x11-xauth xorg-x11-fonts-* xorg-x11-font-utils xorg-x11-fonts-Ty…

如何将jar 包下载到自定义maven仓库

下载命令 mvn install:install-file -Dfileartifactid-version.jar -DgroupIdgroupid -DartifactIdartifactid -Dversionversion -Dpackagingjar -DlocalRepositoryPath. -DcreateChecksumtrue参数解释 在上述命令中&#xff0c;需要替换以下参数&#xff1a; artifactid-vers…

SQL中为何时常见到 where 1=1?

你是否曾在 SELECT 查询中看到过 WHERE 11 条件。我在许多不同的查询和许多 SQL 引擎中都有看过。这条件显然意味着 WHERE TRUE&#xff0c;所以它只是返回与没有 WHERE 子句时相同的查询结果。此外&#xff0c;由于查询优化器几乎肯定会删除它&#xff0c;因此对查询执行时间没…

cocosCreator 3.6以上接入腾迅Bugly 捕捉JS错误 Android

cocosCreator3.6以上接入Bugly上报其实很简单&#xff0c;不需要网上那么多弯弯绕&#xff0c;三须三步走。 1. 按照官网方式接入android的bugly 2. android端写一个Bugly上报管理类 3. 修改你工程目录下native\engine\common\Classes\目录下的Game.h, Game.cpp两个文件&…

基于python的爬虫实现

定义 爬虫&#xff08;Web crawler&#xff09;&#xff0c;也被称为网络爬虫、网络蜘蛛或网络机器人&#xff0c;是一种自动化程序&#xff0c;用于浏览互联网并收集网页内容。 基本原理 爬虫的工作原理是通过发送HTTP请求从网页服务器获取网页的内容&#xff0c;然后解析网…

计算机专升本基础笔记二 进制转换及二进制运算规则

进制转换及二进制运算规则 什么是进制&#xff1f;     进制就是进位计数制&#xff0c;是人为定义的带进位的计数方法。我们的时间就是六十进制(满60秒进一分钟&#xff0c;满60分钟进1小时&#xff09;&#xff1b;对于任何一种进制—X进制&#xff0c;就表示每一位上的数…

GUI-Menu菜单实例(颜色+线型菜单)

运行代码&#xff1a; //GUI-Menu菜单实例&#xff08;颜色线型菜单&#xff09; #include"std_lib_facilities.h" #include"GUI/Simple_window.h" #include"GUI/GUI.h" #include"GUI/Graph.h" #include"GUI/Point.h"struc…

Java的数据结构-Map集合

文章目录 Map概述Map常用方法Map遍历元素的方法1.方法一&#xff1a;keySet()2.方法二&#xff1a;entrySet() HashMap Map概述 1、Map和collection没有继承关系2、Map集合以key和value的方式存储数据&#xff1a;键值对key和value都是引用数据类型。key和value都是存储对象的…

【LocalSend】开源跨平台的局域网文件传输工具,支持IOS、Android、Mac、Windows、Linux

工作前提条件&#xff1a;设备使用相同的局域网。 LocalSend is a cross-platform app that enables secure communication between devices using a REST API and HTTPS encryption. Unlike other messaging apps that rely on external servers, LocalSend doesn’t require …

【网络安全】渗透测试工具——Burp Suite

渗透测试工具Burp Suite主要功能详解 前言一、 Proxy模块1.1 界面布局1.1.1 菜单栏&#xff08;1&#xff09; 菜单栏 Burp&#xff08;2&#xff09; 菜单栏 project&#xff08;3&#xff09; 菜单栏 Intruder&#xff08;4&#xff09; 菜单栏 Repeater&#xff08;5&#x…