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是两个不同但相关的概念。
-
OAuth2(开放授权):OAuth2是一种授权框架,用于在应用程序之间进行安全的资源访问授权。它允许用户授权第三方应用程序代表他们访问受保护的资源,而无需共享其凭据(例如用户名和密码)。OAuth2定义了一组授权流程和协议,包括授权码授权、密码授权、客户端凭证授权和简化授权等。OAuth2通常用于构建安全的API和Web应用程序,使用户能够授权其他应用程序访问其数据,同时提供了更好的安全性和用户控制。
-
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)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。
-
按照上述时序图举个简单的例子,小明使用微信授权方式登录app。
小明点开手机里面的app,他不想手动输入账号密码登录,而是采用了微信登录。 点击微信登录按钮,app拉起授权页面。 微信授权服务器则生成授权页面,用户看见授权页面点击确定按钮进行授权。 微信授权服务器校验用户身份合法性后生成请求code,点击确认授权后,页面跳转至app页面并携带请求code(授权码)。 app拿到授权码后,携带授权码向授权服务器获取访问令牌access_token。 拿到access_token后,则携带access_token向受保护资源发起访问。 校验access_token无误后,受保护资源返回资源数据(个人的身份数据,昵称,地区等信息)。 成功登录app,小明继续使用app内的功能。
2 简化模式
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
这种方式一般用于没有服务器的单页面应用,因为他们没有服务器端去拿授权码换取 Token。
-
客户端将用户重定向到授权服务器的授权页面,并请求授权。
-
用户在授权页面上输入用户名和密码,授权服务器验证用户身份。
-
授权服务器向用户显示授权请求的范围和权限,并要求用户授权。
-
用户同意授权请求。
-
授权服务器将访问令牌直接返回给浏览器,作为URL的一部分。
-
浏览器将访问令牌传递给客户端应用程序,客户端应用程序使用访问令牌来访问受保护的资源。
简化模式的优点是简单易用,适用于客户端是浏览器的应用程序,但缺点是安全性较低,因为访问令牌直接传递给浏览器,容易被恶意攻击者截获。因此,简化模式适用于访问受保护的资源较少的情况。
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的核心组件包括:
-
OAuth2认证服务器:用于颁发和验证访问令牌,支持多种授权方式,如授权码模式、密码模式、客户端模式等。
-
资源服务器:用于保护受保护的资源,验证访问令牌的有效性,支持多种访问令牌类型,如JWT、Opaque等。
-
客户端:用于向OAuth2认证服务器请求访问令牌,支持多种客户端类型,如Web应用、移动应用、第三方应用等。
-
Spring Security集成:Spring Cloud Security OAuth2与Spring Security深度集成,提供了一系列的安全过滤器和认证提供者,支持多种认证方式,如用户名密码认证、LDAP认证、OAuth2认证等。
Spring Cloud Security OAuth2的优点包括:
-
简单易用:提供了一系列的注解和配置,帮助开发者快速构建安全的分布式系统。
-
可扩展性强:提供了一系列的扩展点,如自定义认证提供者、自定义授权方式等,方便开发者根据实际需求进行扩展。
-
安全性高:基于OAuth2协议,提供了多种授权方式和访问令牌类型,保证了系统的安全性。
-
社区活跃:是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访问资源服务器