1 Star 0 Fork 0

哈哈 / tensquare_parent

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

OAuth2-----授权码模式使用

一、定义

1、定义: OAuth 2.0是一种授权框架,用于授权第三方应用访问用户的资源,如照片、个人信息等,OAuth 2.0具有高度的安全性和可扩展性,被广泛应用于各种开放平台的接口鉴权,是目前应用最广泛的开放平台鉴权方式之一
2、使用场景:
例如,一个终端用户(资源所有者)可以授权一个打印服务(客户端)访问她存储在照片共享服务(资源服务器)上的受保护照片,而无需与打印服务共享她的用户名和密码。相反,她直接与受照片共享服务信任的服务器(授权服务器)进行身份验证,该服务器颁发打印服务委托特定的凭据(访问令牌)。 第三方应用授权登录:在APP或者网页接入一些第三方应用时,时常会需要用户登录另一个合作平台,比如QQ,微博,微信的授权登录,第三方应用通过oauth2方式获取用户信息

二、流程图

流程图

参考官网定义:https://datatracker.ietf.org/doc/html/rfc6749
名词解释:
resource owner:资源所有者
resource server:资源服务器
Client:客户
authorization server:授权服务器

流程说明:

(A)客户端向资源所有者请求授权。
(B) 客户端收到授权授予,该授权授予是表示资源所有者的授权的凭证。
(C)客户端携带授权凭证,向授权服务器申请令牌。
(D)授权服务器对客户端进行身份验证并验证授权授予,如果有效,则颁发访问令牌。
(E)客户端使用令牌,向资源服务器申请资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。

相应官网:

https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html#oauth2-resource-server-access-token-jwt --- spring-security官网

https://docs.spring.io/spring-security-oauth2-boot/docs/ --- spring-security-oauth2-boot各个版本
https://docs.spring.io/spring-security-oauth2-boot/docs/2.7.x/reference/html5/ --- spring-security-oauth2-boot对应版本官网

https://spring.io/projects/spring-cloud-security#learn -- Spring Cloud Security官网

https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_security --- 第十一部分。Spring Cloud Security

其他网页参考:

http://websystique.com/spring-security/secure-spring-rest-api-using-oauth2/ --- Secure Spring REST API using OAuth2

https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html --- 理解OAuth 2.0

https://www.ruanyifeng.com/blog/2019/04/oauth_design.html --- OAuth 2.0 的一个简单解释

https://codeleading.com/article/73121122186/ ----Spring Security 源码分析(四):OAuth2 实现

三、分类

OAuth 2.0定义了四种授权方式。

授权码模式(authorization code)
简化模式(implicit)
密码模式(resource owner password credentials)
客户端模式(client credentials)

1、授权码模式:

授权码模式流程图

(A)客户端通过引导用户代理到授权端点。
(B)授权服务器对用户认证,并让用户确认是否选择授权
(C)如果用户确认授权访问权限,授权服务器将根据重定向URI返回一个授权码,包括其他参数。
(D)客户端收到授权码,附上刚才的重定向URI,向授权服务器申请令牌。由客户端的后台向授权服务器申请完成。
(E)授权服务器确认了 授权码和重定向URI的信息没错,然后向客户端发送访问令牌(access token)和更新令牌(refresh token)。

1.1、请求授权码:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1  
Host: server.example.com  

1.1.1.请求参数:

response_type:表示授权类型,必填,此处的值固定为"code"
client_id:表示客户端的ID,且唯一,必填
redirect_uri:表示重定向URI,可填,最好带上,并且是外网可访问
scope:表示申请的权限范围,可填 "all"
state:任意值,原样返回。

1.1.2.响应结果:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA &state=xyz
其中code值即为返回的授权码,需要保留,接下来,请求令牌

1.2、请求令牌:

POST /token HTTP/1.1   
Host: server.example.com  
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW   
Content-Type: application/x-www-form-urlencoded  
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA  
     &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb   

1.2.1.请求参数:

grant_type:表示授权模式,必填项,此处的值固定为"authorization_code"。
code:表示第一步所获得的授权码,必填项。
redirect_uri:重定向URI,必填项,且必须与第一步中的URI一样。
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
重点为"Authorization",其中的值"czZCaGRSa3F0MzpnWDFmQmF0M2JW"为client_id:client_secret,客户端ID与客户端密码组合,使用英文的冒号:连接的base64格式编码格式,Basic 为固定值
Content-Type为请求类型,一定是”application/x-www-form-urlencoded”

1.2.2.响应结果:
HTTP/1.1 200 OK     
Content-Type: application/json;charset=UTF-8  
Cache-Control: no-store   
Pragma: no-cache   
{  
       "access_token":"2YotnFZFEjr1zCsicMWpAA",  
       "token_type":"example",  
       "expires_in":3600,  
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",  
       "example_parameter":"example_value"  
}   

access_token:表示访问令牌。
token_type:表示令牌类型,该值大小写不敏感,可以是bearer类型或mac类型。
expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
refresh_token:表示更新令牌,用来获取下一次的访问令牌,设置authorizedGrantTypes时带有"refresh_token"才会返回该值
scope:表示权限范围,一般省略。

2、简化模式:

简化模式流程图

(A)客户端通过引导用户代理到授权端点。
(B)授权服务器对用户认证,并让用户确认是否选择授权。
(C)如果用户同意授权,授权服务器将根据URI重定向回来,并带着令牌。
(D)用户向资源服务器请求,其中不包括上一步返回的信息。
(E)资源服务器返回一个网页,其中包含有C步请求中返回的信息。
(F)浏览器执行资源服务器返回的脚本,提取访问令牌。
(G)浏览器将访问令牌传递给客户端。

2.1、请求认证
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz  
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1  
Host: server.example.com   

请求类型还是application/x-www-form-urlencoded

2.1.1.请求参数

response_type:表示授权类型,此处的值固定为"token",必填项。
client_id:表示客户端的ID,必填项。
redirect_uri:表示重定向的URI,可填项。
scope:表示权限范围,可填项。
state:任意值,原样返回。

2.2.请求资源:
HTTP/1.1 302 Found  
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA   
               &state=xyz&token_type=example&expires_in=3600  

请求类型还是application/x-www-form-urlencoded

2.2.1.请求参数:

access_token:表示访问令牌,必填项。
token_type:表示令牌类型,该值大小写不敏感,必填项。
expires_in:表示过期时间,单位为秒。
scope:表示权限范围,可省略。
state:任意值,原样返回。

3、密码模式

密码模式流程图

(A) 资源所有者向客户端提供其用户名和密码
(B) 客户端将用户信息向授权服务器请求,授权服务器进行身份验证。
(C) 授权服务器对客户端进行身份验证并验证
资源所有者凭据,如果有效,则提供访问令牌

3.1、请求令牌

POST /token HTTP/1.1  
Host: server.example.com  
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW   
Content-Type: application/x-www-form-urlencoded  
grant_type=password&username=johndoe&password=A3ddj3w  
3.1.1.请求参数:

grant_type:表示授权类型,此处的值固定为"password",必填项。
username:表示用户名,必填项。
password:表示用户的密码,必填项。
scope:表示权限范围,可填项。
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
重点为"Authorization",其中的值"czZCaGRSa3F0MzpnWDFmQmF0M2JW"为client_id:client_secret,客户端ID与客户端密码组合,使用英文的冒号:连接的base64格式编码格式,Basic 为固定值
Content-Type为请求类型,一定是”application/x-www-form-urlencoded”

3.1.2.响应结果:
 HTTP/1.1 200 OK  
     Content-Type: application/json;charset=UTF-8  
     Cache-Control: no-store  
     Pragma: no-cache  

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }

4、客户端模式

客户端模式流程图

(A) 客户端通过授权服务器进行身份验证,并且请求访问令牌。
(B) 授权服务器对客户端进行认证,并提供访问令牌。

4.1请求令牌

POST /token HTTP/1.1  
Host: server.example.com   
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW  
Content-Type: application/x-www-form-urlencoded  
grant_type=client_credentials  
4.1.1请求参数:

granttype:表示授权类型,此处的值固定为"client_credentials",必填项。
scope:表示权限范围,可填项。
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
重点为"Authorization",其中的值"czZCaGRSa3F0MzpnWDFmQmF0M2JW"为client_id:client_secret,客户端ID与客户端密码组合,使用英文的冒号:连接的base64格式编码格式,Basic 为固定值
Content-Type为请求类型,一定是”application/x-www-form-urlencoded”

4.1.2响应结果:
HTTP/1.1 200 OK  
     Content-Type: application/json;charset=UTF-8  
     Cache-Control: no-store  
     Pragma: no-cache  

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "example_parameter":"example_value"
     }

5、刷新令牌

POST /token HTTP/1.1  
Host: server.example.com   
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW  
Content-Type: application/x-www-form-urlencoded   
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA  

5.1请求参数:

grant_type:表示使用的授权模式,此处的值固定为"refresh_token",必填项。
refresh_token:表示早前收到的更新令牌,必填项。
scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
重点为"Authorization",其中的值"czZCaGRSa3F0MzpnWDFmQmF0M2JW"为client_id:client_secret,客户端ID与客户端密码组合,使用英文的冒号:连接的base64格式编码格式,Basic 为固定值
Content-Type为请求类型,一定是”application/x-www-form-urlencoded”

5.2响应结果

HTTP/1.1 200 OK   
Content-Type: application/json;charset=UTF-8  
Cache-Control: no-store  
Pragma: no-cache  
{  
       "access_token":"2YotnFZFEjr1zCsicMWpAA",  
       "token_type":"example",  
       "expires_in":3600,  
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",  
       "example_parameter":"example_value"  
}  

四、专业术语

(1)Third-party application:第三方应用程序
(2)HTTP service:HTTP服务提供商。
(3)Resource Owner:资源所有者。
(4)User Agent:用户代理,就是指浏览器。
(5)Authorization server:授权服务器,即服务提供商专门用来处理认证的服务器。
(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与授权服务器,可以是同一台服务器,也可以是不同的服务器。
(7)client:受保护资源请求的应用程序
(8)client_id:客户端标识
(9)client_secret:客户端秘钥

五、开始引入jar

本例使用

SpringBoot 2.1.14.RELEASE版本,  
Spring cloud   Greenwich.SR5版本,  
Spring cloud alibaba  2.1.2.RELEASE版本,  
JDK8,  
Mysql 5.7,  
Maven3.8,  
Spring-security-config 5.1.10  
Spring-security-oauth2 2.3.4.RELEASE  

主要对授权码模式进行简单配置,在pom.xml中引入

<dependency>  
    <groupId>org.springframework.cloud</groupId>  
    <artifactId>spring-cloud-starter-oauth2</artifactId>  
</dependency>  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-redis</artifactId>  
</dependency>

对于微服务项目,引入这个spring-cloud-starter-oauth2即可,里面已经包含的oauth2和security的jar;如果是spring boot 单体项目,可引入spring-security-oauth2-autoconfigure或者其他,其他功能所需依赖jar自行按需引入。

注意:
Spring Security OAuth 已经停止更新
https://spring.io/projects/spring-security-oauth
过期图

SQL脚本:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;  
CREATE TABLE `oauth_access_token`  (  
  `token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `token` longblob NULL,  
  `authentication_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `authentication` longblob NULL,  
  `refresh_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL  
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;  

-- ----------------------------  
-- Table structure for oauth_approvals  
-- ----------------------------  
DROP TABLE IF EXISTS `oauth_approvals`;  
CREATE TABLE `oauth_approvals`  (  
  `userId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `clientId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `expiresAt` datetime(0) NULL DEFAULT NULL,  
  `lastModifiedAt` datetime(0) NULL DEFAULT NULL  
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;  

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;

CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('clients', 'resource-api', '$2a$10$bCaomfHma4JaQmK1HOVk2.AkXr71jLhOQCORZQnqIgClYv.HncMry', 'all', 'authorization_code,password,refresh_token', 'http://www.baidu.com', NULL, NULL, NULL, NULL, 'false');

-- ----------------------------
-- Table structure for oauth_client_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_token`;

CREATE TABLE `oauth_client_token`  (
  `token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` longblob NULL,
  `authentication_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;

CREATE TABLE `oauth_code`  (
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` longblob NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;

CREATE TABLE `oauth_refresh_token`  (
  `token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` longblob NULL,
  `authentication` longblob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;

CREATE TABLE `sys_permission`  (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `permission_NAME` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
  `permission_url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单地址',
  `parent_id` int(11) NOT NULL DEFAULT 0 COMMENT '父菜单id',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;

CREATE TABLE `sys_role`  (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `ROLE_NAME` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
  `ROLE_DESC` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'ROLE_ADMIN', '管理员角色');

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;

CREATE TABLE `sys_role_permission`  (
  `RID` int(11) NOT NULL COMMENT '角色编号',
  `PID` int(11) NOT NULL COMMENT '权限编号',
  PRIMARY KEY (`RID`, `PID`) USING BTREE,
  INDEX `FK_Reference_12`(`PID`) USING BTREE,
  CONSTRAINT `FK_Reference_11` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_Reference_12` FOREIGN KEY (`PID`) REFERENCES `sys_permission` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;

CREATE TABLE `sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称',
  `password` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  `status` int(1) NULL DEFAULT 1 COMMENT '1开启0关闭',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '123456', 1);

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;

CREATE TABLE `sys_user_role`  (
  `UID` int(11) NOT NULL COMMENT '用户编号',
  `RID` int(11) NOT NULL COMMENT '角色编号',
  PRIMARY KEY (`UID`, `RID`) USING BTREE,
  INDEX `FK_Reference_10`(`RID`) USING BTREE,
  CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);

SET FOREIGN_KEY_CHECKS = 1;

六、授权服务器

认证(Authentication):用来验证用户是否具有访问系统的权限
授权(Authorization):用来验证用户是否具有访问某个资源的权限,如果授权通过,该用户就能对资源做增删改查等操作

@Configuration

@EnableAuthorizationServer

注解并继承AuthorizationServerConfifigurerAdapter来配置OAuth2.0 授权服务器。

AuthorizationServerConfifigurerAdapter要求配置

ClientDetailsServiceConfifigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化

AuthorizationServerEndpointsConfifigurer:用来配置令牌(token)的访问端点和令牌服务(tokenservices)。

AuthorizationServerSecurityConfifigurer:用来配置令牌访问端点的安全约束

代码如下:

@Configuration
@EnableAuthorizationServer
public class OAuthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public AuthUserService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private  AuthorizationServerTokenServices tokenServices;

    //配置令牌端点的安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()")
                // 第三方客户端校验token需要带入 clientId 和clientSecret来校验
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }


    //配置令牌的访问端点:需要配置认证管理器,授权码服务,令牌管理服务,令牌的请求方式
    //这几个都需要以bean的形式进行配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)//认证管理器,密码模式时使用
                    .authorizationCodeServices(authorizationCodeServices) // 授权码服务,授权码模式时使用,针对授权码模式有效,会将授权码放到 auth_code 表,授权后就会删除它
                    .userDetailsService(userDetailsService) // 刷新令牌授权包含对用户信息的检查
                    .approvalStore(approvalStore())  // 授权记录
                    .tokenServices(tokenServices) // 令牌管理
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许以POST方式获取令牌
    }
    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                // 授权服务的名字
                .withClient("clients-auth")
                // 授权密码
                .secret(passwordEncoder.encode("auth3366"))
                // 重定向位置
                .redirectUris("http://www.baidu.com")
                // 授权范围
                .scopes("all")
                // 是否自动批准
                .autoApprove(false)
                // 授权类型
                .authorizedGrantTypes("authorization_code","refresh_token");
    }


    // 配置授权码储存,暂时存在内存中,可存于内存与数据库中,使用jwt 注释掉
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
         return new InMemoryAuthorizationCodeServices();
    }
   // 资源服务器到授权服务器验证AccessToken ,两种方式,资源服务器每接收一次请求,都到授权服务器认证AccessToken  
// 第二种是:到数据请求数据认证,前提是授权服务器已经把AccessToken存入数据库中,  
// 其中 第一种每次都去授权服务器认证的,又分两种 RemoteTokenServices 和 DefaultTokenServices(默认) 。而DefaultTokenServices 不适合认证资源服务分离部署  
    @Bean  
    public AuthorizationServerTokenServices tokenServices(){  
        DefaultTokenServices tokenServices = new DefaultTokenServices();  
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情,从容器中获取  
        tokenServices.setTokenStore(tokenStore);//token的存储方式  
        // 是否支持 refreshToken  
        tokenServices.setSupportRefreshToken(true);  
        // 是否复用 refreshToken  
        tokenServices.setReuseRefreshToken(false);  
        // token有效期自定义设置,默认12小时  
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24);  
        //默认30天,这里修改  
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);  
        return tokenServices;  
    }  

    /**
     * 内存存储采用的是TokenStore接口默认实现类InMemoryTokenStore,开发时方便调试,适用单机版
* InMemoryTokenStore:token存储内存之中(默认,不适合认证资源服务分离部署)  
* JdbcTokenStore:token存储在关系型数据库之中  
* JwtTokenStore:token不会存储到任何介质中,使用JWT令牌作为AccessToken,在请求发起者和服务提供者之间网络传输
* RedisTokenStore:token存储在Redis数据库之中  
     * @return 
     */    
     
    @Bean
    public TokenStore tokenStore(){  
        return new InMemoryTokenStore();  
    }  
    /**  
     * 使用使用内存方式来保存用户的授权批准记录,也可以使用jdbc保存  
     * @return  
     */  
    @Bean  
    public ApprovalStore approvalStore(){  
        return new InMemoryApprovalStore();  
}  
}

userDetailsService:可从数据库获取账号信息,在这里简单封装一下

@Slf4j  
@Component   
public class AuthUserService implements UserDetailsService {  
   @Autowired  
   private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String encode = passwordEncoder.encode("123456");
        /**
         * 获取用户信息得逻辑,自定义权限规则
         **/
        return new User(username,encode, AuthorityUtils.createAuthorityList("admin"));
    }
}

七、资源服务器

application.xml配置oauth2的参数:

security:  
  oauth2:  
    client:  
      clientId: clients-auth  
      clientSecret: auth3366  
      userAuthorizationUri: http://localhost:8002/oauth/authorize  
      accessTokenUri: http://localhost:8002/oauth/token  
      registeredRedirectUri: http://localhost:8002/oauth/authorize  
    resource:  
      id: clients  
      userInfoUri: http://127.0.0.1:8003/api/user/getCurrentUser  
      tokenInfoUri: http://localhost:8002/oauth/check_token  
    authorization:  
      checkTokenAccess: http://localhost:8002/oauth/check_token  

代码如下:

@Configuration  
@EnableResourceServer  
@EnableGlobalMethodSecurity(prePostEnabled = true)  
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {  

    @Value("${security.oauth2.client.clientId}")
    private String clientId;

    @Value("${security.oauth2.client.clientSecret}")
    private String secret;

    @Value("${security.oauth2.authorization.checkTokenAccess}")
    private String checkTokenEndpointUrl;

   @Autowired  
   private AuthExceptionEntryPoint authExceptionEntryPoint; // 自定义错误处理,implements AuthenticationEntryPoint ,不添加也可以  

   @Override  
   public void configure(ResourceServerSecurityConfigurer resources) {    
       resources.authenticationEntryPoint(authExceptionEntryPoint).tokenServices(tokenService()).stateless(true);  
   }    
    @Override  
    public void configure(HttpSecurity http) throws Exception {  
        http.authorizeRequests()// 对相关请求进行授权  
                .anyRequest()// 任意请求  
                .authenticated()// 都要经过验证  
                /*.and()  
                .requestMatchers()// 设置要保护的资源  
                .antMatchers("/**")*/;// 保护的资源  
    }  
      //默认是DefaultTokenServices,但是使用 RemoteTokenServices 远程检验token,因为时使用内存演示,授权服务与资源服务是分开的,启动服务的时候开辟的内存不一致,所以不能使用DefaultTokenServices的token认证模式  
       public RemoteTokenServices tokenService() {  
       // 每次请求都要去授权服务 认证,增加了网络开销  
        RemoteTokenServices tokenService = new RemoteTokenServices();  
        tokenService.setClientId(clientId);  
        tokenService.setClientSecret(secret);  
        tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);  
        return tokenService;  
    }
}  

八、Security

在授权项目中添加security配置,注意本版。在spring security 5.2版本以后该写法已经摒弃,具体请查看官网示例

@Configuration  
@EnableWebSecurity  
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {  

    @Lazy
    @Autowired
    private AuthUserService userService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .antMatchers("/oauth/**","/login/**","/logout/**")
                .permitAll()
                .anyRequest()
                .authenticated()
               // .and()
               // .formLogin()  // FormLogin模式              
                .and()
                .httpBasic()  // 开启HttpBasic模式
                .csrf()
                .disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

    @Bean("passwordEncoder")
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

}  

如果不配置登入页面。Spring security 会构造一个登入页,而这个任务就交给了DefaultLoginPageGeneratingFilter这个过滤器 默认退出的过滤器为DefaultLogoutPageGeneratingFilter

登入认证大概经过的过滤器
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]

Spring Security 有: HttpBasic模式登录认证、 formLogin模式登录认证 两种模式,Basic模式比较简单也不那么安全,不建议在生产环境使用,但是可用来演示。在security的5.x版本之前都是默认Basic的模式,在5.x版本之后默认使用form模式,form模式可自定义登入页面属性路径,具体操作请自行上网查看相关信息

九、内存方式请求

//配置客户端  
@Override  
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {  
    clients.inMemory()  
            // 授权服务的名字  
            .withClient("clients-auth")  
            // 授权密码  
            .secret(passwordEncoder.encode("auth3366"))  
            // 重定向位置  
            .redirectUris("http://www.baidu.com")  
            // 授权范围  
            .scopes("all")  
            // 是否自动批准  
            .autoApprove(false)  
            // 授权类型  
            .authorizedGrantTypes("authorization_code","refresh_token");  
}  
/**  
 * 内存存储采用的是TokenStore接口默认实现类InMemoryTokenStore,开发时方便调试,适用单机版  
 * @return  
 */  
@Bean  
public TokenStore tokenStore(){  
    return new InMemoryTokenStore();  
}  
/**  
 * 使用使用内存方式来保存用户的授权批准记录,也可以使用jdbc保存  
 * @return  
 */  
@Bean  
public ApprovalStore approvalStore(){  
    return new InMemoryApprovalStore();  
}  

十、授权码

请求授权码:
http://127.0.0.1:8002/oauth/authorize?client_id=clients-auth&response_type=code&scope=all&redirect_uri=http://www.baidu.com

1、登录页 账号密码是数据库的中系统用户的账号密码:admin 123456,授权选择按钮 Approve

字段 描述
response_type 必填。将其值设置为code表示如果成功的话将收到一个授权码。
client_id 必填。客户端标识。
redirect_uri 可选。重定向URI虽然不是必须的,但是你的服务应该会需要它。而且,这个URL必须和授权服务端注册的redirect_id一致。
scope 可选。请求可能会有一个或多个scope值。授权服务器会把客户端请求的范围(scope)展示给用户看。
state 推荐。state参数用于应用存储特定的请求数据的可以防止CSRF攻击。授权服务器必须原封不动地将这个值返回给应用。

登入图 授权图 选择“Approve ”授权
重定向到百度页面,并带着授权码

十一、获取token

获取code=AIljsQ,再postman工具中请求access_token,请求方式请参考官网说明: https://datatracker.ietf.org/doc/html/rfc6749#autoid-36

请求"/oauth/token" , post请求

字段 描述
response_type 必填。授权码模式固定值authorization_code。
client_id 非必填。主要与请求方式有关,使用头部Authorization: Basic xxxx方式,就不用填写,直接把client_id:client_secret 使用base64加密方式加到Basic后面,官方提示必须填,未经身份验证的客户端必须发送其“client_id”,如果不是使用Basic方式,则必填
client_secret 非必填。主要与请求方式有关,使用头部Authorization: Basic xxxx方式,就不用填写,直接把client_id:client_secret 使用base64加密方式加到Basic后面,如果不是使用Basic方式,则必填
redirect_uri 必填
code 必填,即上一步请求返回的授权码

加密client-id:client-secret形式的客户端id与私钥,用英文分号隔开,base64加密方式,加在header中,

参考 https://datatracker.ietf.org/doc/html/rfc2617#autoid-4 如: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

public static void main(String[] args) {
    System.out.println(new org.apache.tomcat.util.codec.binary.Base64().encodeAsString("clients-1:12345678".getBytes()));
    System.out.println(java.util.Base64.getEncoder().encodeToString("clients-1:12345678".getBytes()));
}

TokenEndpoint.class的postAccessToken为生成token的方法,但在进入该方法之前, 先判断认证是走client认证还是Authorization认证。首先 在AbstractAuthenticationProcessingFilter.doFilter

在ClientCredentialsTokenEndpointFilter.ClientCredentialsRequestMatcher.matches中判断请求参数中存在client_id,则走client客户端认证方式

如果不存在,则进入到BasicAuthenticationFilter.doFilterInternal解释出Authorization的参数, 不管哪种方式最后都会在BasicAuthenticationFilter.doFilterInternal中进入到TokenEndpoint的postAccessToken生成token

十二、拿token获取数据

localhost:8003/api/user/getCurrentUser 加上一步返回的token

请求资源验证入口为OAuth2AuthenticationProcessingFilter.class,
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {}

进入提取token,Authentication authentication = tokenExtractor.extract(request);在BearerTokenExtractor.extractv.extractToken.extractHeaderToken中提取token

也可以通过access_token参数带着token请求,然后封装成authentication一个对象

在进入OAuth2AuthenticationManager认证管理,封装RemoteTokenServices的token验证对象,这一步很关键

封装请求头

远程请求验证token

随即进入验证身份的入口:AbstractAuthenticationProcessingFilter,重复验证的步骤,还是走的Authorization认证方式

重新把请求对封装到上下文中

进入到授权服务的token管理服务

进入到token的校验方法中CheckTokenEndpoint

最后验证成功后返回到远程管理token方法中来

回到认证管理OAuth2AuthenticationManager

回到验证入口,把对象放入到上写文中OAuth2AuthenticationProcessingFilter

最后到调用方法

返回结果

十三、JDBC方式

1、数据库添加一条数据

INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('clients-auth', 'admin_resourceids', '$2a$10$9jBvNKTxv3A1Tv5fgfpHd.9kgm9f2Diei.9voOYazRo/tntrCcHWO', 'all', 'authorization_code,refresh_token,password', 'http://www.baidu.com', NULL, 3600, 36000, NULL, '1');

2、修改授权服务器:


@Configuration
@EnableAuthorizationServer
public class OAuthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public AuthUserService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private  AuthorizationServerTokenServices tokenServices;

    @Resource
    private DataSource dataSource;

    //配置令牌端点的安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()")
                // 第三方客户端校验token需要带入 clientId 和clientSecret来校验
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }


    //配置令牌的访问端点:需要配置认证管理器,授权码服务,令牌管理服务,令牌的请求方式
    //这几个都需要以bean的形式进行配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)//认证管理器,密码模式时使用
                    .authorizationCodeServices(authorizationCodeServices) // 授权码服务,授权码模式时使用,针对授权码模式有效,会将授权码放到 auth_code 表,授权后就会删除它
                    .userDetailsService(userDetailsService) // 刷新令牌授权包含对用户信息的检查
                    .approvalStore(approvalStore())  // 授权记录
                    .tokenServices(tokenServices) // 令牌管理
                    .tokenStore(tokenStore) // token存储数据库
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许以POST方式获取令牌
    }
    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 从数据库中获取客户端信息
        clients.withClientDetails(clientDetails());
    }

    /**
     * 从数据库中获取客户端详情
     * @return
     */
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }


    // 配置授权码储存,暂时存在内存中,可存于内存与数据库中
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        // 授权码存于数据库
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    // 配置管理令牌服务 ,DefaultTokenServices 和 RemoteTokenServices
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情,从容器中获取
        tokenServices.setTokenStore(tokenStore);//基于数据库存储令牌,管理token的方式
        // 是否支持 refreshToken
        tokenServices.setSupportRefreshToken(true);
        // 是否复用 refreshToken
        tokenServices.setReuseRefreshToken(false);
        // token有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24);
        //默认30天,这里修改
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }

    /**
     * 内存存储采用的是TokenStore接口默认实现类InMemoryTokenStore,开发时方便调试,适用单机版
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
       // token存储于数据库
        return new JdbcTokenStore(dataSource);
    }
    /**
     * 使用使用内存方式来保存用户的授权批准记录,也可以使用jdbc保存
     * @return
     */
    @Bean
    public ApprovalStore approvalStore(){
        // 授权码存储数据库
        return new JdbcApprovalStore(dataSource);
    }
}

3、修改资源服务器:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    @Value("${security.oauth2.resource.id}")
    private String resourceId;

   @Autowired
   private AuthExceptionEntryPoint authExceptionEntryPoint;

    @Resource
   private DataSource dataSource;

   @Autowired
   private TokenStore tokenStore;

   @Override
   public void configure(ResourceServerSecurityConfigurer resources) {
       resources.authenticationEntryPoint(authExceptionEntryPoint)
               .tokenServices(tokenService())
               .resourceId(resourceId) // 如果要配置这个resourceId,那这里的resourceId必须要跟授权服务中数据库字段resourceId的值一致
               .tokenStore(tokenStore)
               .stateless(true);
   }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()// 对相关请求进行授权
                .anyRequest()// 任意请求
                .authenticated()// 都要经过验证
                /*.and()
                .requestMatchers()// 设置要保护的资源
                .antMatchers("/**")*/;// 保护的资源
    }

    /**
     * 改用存缓存校验token,token的认证方式
     * @return
     */
    public DefaultTokenServices tokenService(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setTokenStore(tokenStore);
        return tokenServices;
    }

    @Bean
    public TokenStore tokenStore() {
        //使用数据库
        return new JdbcTokenStore(dataSource);
    }

}

4、请求:

授权码: 获取token: 先到数据库查询是否存在token 不存在时,则插入token到数据库

请求资源,到数据库查询token

十四、Redis方式

1、配置redis对象

@Configuration
public class RedisTokenStoreConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore tokenStore (){
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        // redis:oauth2使用的是java二进制序列化的方式,存在一些问题,最好能重写RedisTokenStore序列化方式
        //1. 如果UserDetails定义的字段发生增删,已存在的token,访问校验的时候,就会发生序列化错误;
        //2. 如果去redis中查看某个token的内容的时候,会发现全是乱码,完全看不懂;
        redisTokenStore.setSerializationStrategy(new JdkSerializationStrategy());
        return redisTokenStore;
    }
}

2、修改授权服务

只修改token的管理方式,其他信息,如客户端,授权码等仍存于数据库

@Configuration
@EnableAuthorizationServer
public class OAuthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public AuthUserService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private  AuthorizationServerTokenServices tokenServices;

    @Resource
    private DataSource dataSource;

    //配置令牌端点的安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()")
                // 第三方客户端校验token需要带入 clientId 和clientSecret来校验
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }


    //配置令牌的访问端点:需要配置认证管理器,授权码服务,令牌管理服务,令牌的请求方式
    //这几个都需要以bean的形式进行配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)//认证管理器,密码模式时使用
                    .authorizationCodeServices(authorizationCodeServices) // 授权码服务,授权码模式时使用,针对授权码模式有效,会将授权码放到 auth_code 表,授权后就会删除它
                    .userDetailsService(userDetailsService) // 刷新令牌授权包含对用户信息的检查
                    .approvalStore(approvalStore())  // 授权记录
                    .tokenServices(tokenServices) // 令牌管理
                    .tokenStore(tokenStore) // token存储redis
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许以POST方式获取令牌
    }
    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 从数据库中获取客户端信息
        clients.withClientDetails(clientDetails());
    }

    /**
     * 从数据库中获取客户端详情
     * @return
     */
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }


    // 配置授权码储存,可存于内存与数据库中
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        // 授权码存于数据库
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    // 配置管理令牌服务 ,DefaultTokenServices 和 RemoteTokenServices
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情,从容器中获取
        tokenServices.setTokenStore(tokenStore);//基于redis存储令牌,管理token的方式
        // 是否支持 refreshToken
        tokenServices.setSupportRefreshToken(true);
        // 是否复用 refreshToken
        tokenServices.setReuseRefreshToken(false);
        // token有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24);
        //默认30天,这里修改
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }

    /**
     * 使用使用内存方式来保存用户的授权批准记录,也可以使用jdbc保存
     * @return
     */
    @Bean
    public ApprovalStore approvalStore(){
        // 授权码存储数据库
        return new JdbcApprovalStore(dataSource);
    }
}

3、修改资源服务器

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Value("${security.oauth2.resource.id}")
    private String resourceId;

   @Autowired
   private AuthExceptionEntryPoint authExceptionEntryPoint;

    @Resource
   private DataSource dataSource;

   @Autowired
   private TokenStore tokenStore;

   @Override
   public void configure(ResourceServerSecurityConfigurer resources) {
       resources.authenticationEntryPoint(authExceptionEntryPoint)
               .tokenServices(tokenService())
               .resourceId(resourceId) // 如果要配置这个resourceId,那这里的resourceId必须要跟授权服务中数据库字段resourceId的值一致
               .tokenStore(tokenStore)
               .stateless(true);
   }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()// 对相关请求进行授权
                .anyRequest()// 任意请求
                .authenticated()// 都要经过验证              
    }

    /**
     * 改用存缓存校验token,token的认证方式
     * @return
     */
    public DefaultTokenServices tokenService(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setTokenStore(tokenStore);
        return tokenServices;
    }
}

4、请求

十五、Jwt是什么

JSON Web令牌(JWT)(全称:Json Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于作为JSON对象在各方之间安全地传输信息。此信息是经过数字签名的,因此可以验证和信任 官方:https://jwt.io/introduction/
https://jwt.io/#debugger-io ---加解密

十六、Jwt包含哪些

1、JSON Web Token

由三部分组成,它们之间用圆点(.)连接。这三部分分别是:

Header(头部)
Payload(负载)
Signature(签名)

一个典型的JWT看起来是这个样子的

xxxxx.yyyyy.zzzzz
Header.Payload.Signature

2、Header

由两部分组成,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

{
    'alg': "HS256",
    'typ': "JWT"
}

3、Payload

JWT的第二部分是payload,它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, public 和 private。

Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
Public claims : 可以随意定义。
Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明

用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

4、Signature

是对前两部分的签名,防止数据篡改,需要一个密钥,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),对header部分、payload部分进行签名 算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户

5、JWT 的使用方式

服务器返回给客户端JWT后,可以放在cookie或者缓存localStorage中,但最好是放在 HTTP 请求的头信息Authorization字段里面,这样更方便跨域请求

十七、JWT方式

为什么使用JWT:令牌采用JWT格式,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,只需要发送请求时带上JWT(一般放在请求头)访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权

1、普通方式(对称加密):

1.1、修改授权服务

1.1.1、添加JwtTokenStore

@Configuration
public class JwtTokenConfig {

    private static final String JWT_SIGN="admin-sign";

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey(JWT_SIGN);
        return accessTokenConverter;
    }
}

1.1.2、修改授权服务

@Configuration
@EnableAuthorizationServer
public class OAuthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public AuthUserService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private TokenStore jwtTokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private TokenEnhancer jwtTokenEnhancer;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private  AuthorizationServerTokenServices tokenServices;

    @Resource
    private DataSource dataSource;

    //配置令牌端点的安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()")
                // 第三方客户端校验token需要带入 clientId 和clientSecret来校验
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }


    //配置令牌的访问端点:需要配置认证管理器,授权码服务,令牌管理服务,令牌的请求方式
    //这几个都需要以bean的形式进行配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)//认证管理器,密码模式时使用
                    .authorizationCodeServices(authorizationCodeServices) // 授权码服务,授权码模式时使用,针对授权码模式有效,会将授权码放到 auth_code 表,授权后就会删除它
                    .userDetailsService(userDetailsService) // 刷新令牌授权包含对用户信息的检查
                    .approvalStore(approvalStore())  // 授权记录
                    .tokenServices(tokenServices) // 令牌管理
                    .tokenStore(jwtTokenStore) // 基于jwt管理token
                    .accessTokenConverter(jwtAccessTokenConverter)
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许以POST方式获取令牌
    }
    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 从数据库中获取客户端信息
        clients.withClientDetails(clientDetails());
    }

    /**
     * 从数据库中获取客户端详情
     * @return
     */
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }


    // 配置授权码储存,可存于内存与数据库中
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        // 授权码存于数据库
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    // 配置管理令牌服务 ,DefaultTokenServices 和 RemoteTokenServices
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情,从容器中获取
        tokenServices.setTokenStore(jwtTokenStore);//基于redis存储令牌,管理token的方式

        // 是否支持 refreshToken
        tokenServices.setSupportRefreshToken(true);
        // 是否复用 refreshToken
        tokenServices.setReuseRefreshToken(false);
        // token有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24);
        //默认30天,这里修改
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }

    /**
     * 使用使用内存方式来保存用户的授权批准记录,也可以使用jdbc保存
     * @return
     */
    @Bean
    public ApprovalStore approvalStore(){
        // 授权码存储数据库
        return new JdbcApprovalStore(dataSource);
    }
}

1.2、修改资源服务

1.2.1、配置:

security:
  oauth2:
    resource:
      id: admin_resourceids  #// 必须与授权服务的resourceIds一致,不然就干脆都不配置,ResourceServerSecurityConfigurer默认一个

1.2.2、添加jwt:

@Configuration
public class JwtTokenConfig {

    private static final String JWT_SIGN="admin-sign";


    @Bean
    public TokenStore jwtTokenStore() throws Exception {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() throws Exception {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();      
        accessTokenConverter.setSigningKey(JWT_SIGN);
        return accessTokenConverter;
    }
}

1.2.3、修改ResourceServerConfig

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

  
    @Value("${security.oauth2.resource.id}")
    private String resourceId;
	
   @Autowired
   private AuthExceptionEntryPoint authExceptionEntryPoint;

   @Autowired
   private TokenStore jwtTokenStore;


   @Override
   public void configure(ResourceServerSecurityConfigurer resources) {
       resources.authenticationEntryPoint(authExceptionEntryPoint) // 认证失败时候调用            
               .resourceId(resourceId) // 必须与授权服务的resourceIds一致,不然就干脆都不配置,ResourceServerSecurityConfigurer默认一个
               .tokenStore(jwtTokenStore)
               .stateless(true);
   }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()// 对相关请求进行授权
                .anyRequest()// 任意请求
                .authenticated()// 都要经过验证
                /*.and()
                .requestMatchers()// 设置要保护的资源
                .antMatchers("/**")*/;// 保护的资源
    }

}

1.3、增强jwt

添加增强类:

@Component
public class JwtTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> map = new HashMap<>();
        // 1. 获取认证信息
        // 客户端
        String clientId = authentication.getOAuth2Request().getClientId();// 客户端ID
        // 用户
        Authentication userAuthentication = authentication.getUserAuthentication();
        Object principal = userAuthentication.getPrincipal();
        if (principal instanceof User){
            User user= (User) principal;
            map.put("userName", user.getUsername());
        }
        // 2.设置到accessToken中
        map.put("clientId", clientId);
        map.put("three", "可添加任何信息");
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
        return accessToken;
    }
}

修改授权服务:

@Configuration
@EnableAuthorizationServer
public class OAuthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public AuthUserService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private TokenStore jwtTokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private TokenEnhancer jwtTokenEnhancer;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private  AuthorizationServerTokenServices tokenServices;

    @Resource
    private DataSource dataSource;

    //配置令牌端点的安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()")
                // 第三方客户端校验token需要带入 clientId 和clientSecret来校验
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }


    //配置令牌的访问端点:需要配置认证管理器,授权码服务,令牌管理服务,令牌的请求方式
    //这几个都需要以bean的形式进行配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)//认证管理器,密码模式时使用
                    .authorizationCodeServices(authorizationCodeServices) // 授权码服务,授权码模式时使用,针对授权码模式有效,会将授权码放到 auth_code 表,授权后就会删除它
                    .userDetailsService(userDetailsService) // 刷新令牌授权包含对用户信息的检查
                    .approvalStore(approvalStore())  // 授权记录
                    .tokenServices(tokenServices) // 令牌管理
                    .tokenStore(jwtTokenStore) // 基于jwt管理token
                    .accessTokenConverter(jwtAccessTokenConverter)
                    .tokenEnhancer(tokenEnhancerChain()) // 添加增强jwt
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许以POST方式获取令牌
    }
    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 从数据库中获取客户端信息
        clients.withClientDetails(clientDetails());
    }

    /**
     * 从数据库中获取客户端详情
     * @return
     */
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }


    // 配置授权码储存,可存于内存与数据库中
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        // 授权码存于数据库
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    // 配置管理令牌服务 ,DefaultTokenServices 和 RemoteTokenServices
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情,从容器中获取
        tokenServices.setTokenStore(jwtTokenStore);//基于redis存储令牌,管理token的方式

        tokenServices.setTokenEnhancer(tokenEnhancerChain());  //添加增强jwt
        // 是否支持 refreshToken
        tokenServices.setSupportRefreshToken(true);
        // 是否复用 refreshToken
        tokenServices.setReuseRefreshToken(false);
        // token有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24);
        //默认30天,这里修改
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }

    public TokenEnhancerChain tokenEnhancerChain() {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancers = new ArrayList<>();
        enhancers.add(jwtAccessTokenConverter);
        enhancers.add(jwtTokenEnhancer);
        //将自定义Enhancer加入EnhancerChain的delegates数组中
        enhancerChain.setTokenEnhancers(enhancers);
        return enhancerChain;
    }
   
    /**
     * 使用使用内存方式来保存用户的授权批准记录,也可以使用jdbc保存
     * @return
     */
    @Bean
    public ApprovalStore approvalStore(){
        // 授权码存储数据库
        return new JdbcApprovalStore(dataSource);
    }
}

2、JWT非对称加密:

2.1、生成密钥对

如何生成一个非对称密钥对。这里需要一个密钥对用来配置后面实现的授权服务器和资源服务器。这是一个非对称密钥对(这意味着授权服务器使用它的私钥签署令牌,而资源服务器则使用公钥验证签名)。为了生成该密钥对,这里使用了keytool和OpenSSL,它们是两个简单易用的命令行工具。Java的JDK会安装keytool,所以我们的计算机一般都安装了它。而对于OpenSSL.则需要从官网(https://www.openssl.org/source/)处下载它。如果使用OpenSSL自带的Git Bash,则不需要单独安装它。有了工具之后,需要运行两个命令:在java路径下的bin中找到cmd打开终端,生成文件名为adminrsa.jks

生成一个私钥
获取之前所生成私钥的公钥
命令:

  • Keytool -genkeypair -alias adminrsa -keyalg RSA -keypass ad_Min@!123 -keystore adminrsa.jks -storepass ad_Min@!123
  • alias 秘钥别名
  • keyalg 使用的hash算法
  • keypass 秘钥访问密码
  • keystore 秘钥库文件名,生成证书文件
  • storepass 证书的访问密码

2.2、导出公钥:

keytool -list -rfc --keystore adminrsa.jks | openssl x509 -inform pem -pubkey
输入口令,注意不能有换行 复制出来放在某个路径下

2.3、修改授权服务

在application.ym配置文件中添加 修改JwtTokenConfig类,如下:

@Configuration
public class JwtTokenConfig {

    @Value("${privateKey}")
    private String privateKey;

    @Value("${password}")
    private String password;

    @Value("${alias}")
    private String alias;

    @Bean
    public TokenStore jwtTokenStore() throws MalformedURLException {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() throws MalformedURLException {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
                new UrlResource(privateKey),
                password.toCharArray());
        accessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair(alias));
        return accessTokenConverter;
    }
}

授权服务类OAuthConfig:

@Configuration
@EnableAuthorizationServer
public class OAuthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public AuthUserService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private TokenStore jwtTokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private TokenEnhancer jwtTokenEnhancer;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Resource
    private DataSource dataSource;

    //配置令牌端点的安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()")
                // 第三方客户端校验token需要带入 clientId 和clientSecret来校验
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }


    //配置令牌的访问端点:需要配置认证管理器,授权码服务,令牌管理服务,令牌的请求方式
    //这几个都需要以bean的形式进行配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)//认证管理器,密码模式时使用
                    .authorizationCodeServices(authorizationCodeServices) // 授权码服务,授权码模式时使用,针对授权码模式有效,会将授权码放到 auth_code 表,授权后就会删除它                  
                    .tokenStore(jwtTokenStore) // 基于jwt管理token
                    .accessTokenConverter(jwtAccessTokenConverter)
                    .tokenEnhancer(tokenEnhancerChain()) // 添加增强jwt
                    .userDetailsService(userDetailsService) // 刷新令牌授权包含对用户信息的检查
                    .approvalStore(approvalStore())  // 授权记录
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许以POST方式获取令牌
    }
    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 从数据库中获取客户端信息
        clients.withClientDetails(clientDetails());
    }

    /**
     * 从数据库中获取客户端详情
     * @return
     */
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }


    // 配置授权码储存,可存于内存与数据库中
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        // 授权码存于数据库
        return new JdbcAuthorizationCodeServices(dataSource);
    }
       
// 增强jwt
    public TokenEnhancerChain tokenEnhancerChain() {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancers = new ArrayList<>();
        enhancers.add(jwtAccessTokenConverter);
        enhancers.add(jwtTokenEnhancer);// jwtTokenEnhancer沿用1.3节的类
        //将自定义Enhancer加入EnhancerChain的delegates数组中
        enhancerChain.setTokenEnhancers(enhancers);
        return enhancerChain;
    }

    /**
     * 使用使用内存方式来保存用户的授权批准记录,也可以使用jdbc保存
     * @return
     */
    @Bean
    public ApprovalStore approvalStore(){
        // 授权码存储数据库
        return new JdbcApprovalStore(dataSource);
    }
}

2.4、修改资源服务

2.4.1、Resource Server

资源服务想要查看 Token 持有的权限,有两种方式:

一种是利用加密算法(对称加密和非对称加密均可),先在授权服务器中(Authorization Server) 使用 私钥 加密 Token,然后在 资源服务器(Resource Server) 中使用 公钥 解密 Token,即可查看到其中的权限(Scope)。

第二种方式就是使用 OAuth2 中规定 Authorization Server 提供的两个端点(Endpoint)接口 : /oauth/check_token 和 UserInfo Endpoint

可在源码中查看 ResourceServerTokenServices 接口及其实现。主要看 DefaultTokenServices、RemoteTokenServices 和 UserInfoTokenServices 这三个实现,下面可看一下这两种方式的配置形式:

(1)Jwk加密算法:
security: 
  oauth2:
    resource:
      jwk:
        key-set-uri: http://localhost:8002/oauth/token_key

在授权服务配置令牌端点的安全约束中的tokenKeyAccess要放开

@Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")// 必须放开               
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

通过改配置,资源服务从授权服务中获取公钥,自己将token解析为明文。

(2)Jwt加密算法:
security: 
  oauth2:
    resource:
      jwt:
        key-uri: http://localhost:8002/oauth/token_key

其返回的 JSON 只要含有 value 字段,值为对应的公钥,同样令牌端点的安全约束中的tokenKeyAccess要放开,需要依赖授权服务。
或者也可以使用如下的方式直接配置公钥的值:

security: 
  oauth2:
    resource:
      jwt:
        key-value: |
          -----BEGIN CERTIFICATE-----
          MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO
          .......NyxMTXzf0=
          -----END CERTIFICATE-----
(3)Jwt KeyStore加密算法:

密钥对存储到了一个 keystore 中,因此也可以直接配置keystore

security: 
 oauth2:
   resource:
     jwt:
       key-store: test.jks
       key-store-password: test 
       key-alias: test
       key-password: test
(4)使用 Check Token Endpoint接口(Authorization Server提供的接口):
security: 
  oauth2:
    resource:
      token-info-uri: http://localhost:8002/oauth/check_token
      prefer-token-info: true
    client:
      client-id: clients-auth
      client-secret: auth3366

该方式支持JWT被收回, 在授权服务配置令牌端点的安全约束中的checkTokenAccess要放开

@Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")               
                .checkTokenAccess("permitAll()")// 必须放开
                .allowFormAuthenticationForClients();
    }
(5)使用UserInfo Endpoint接口(Authorization Server提供的接口)
security: 
  oauth2:
    resource:
      user-info-uri: http://localhost:8002/api/user/getCurrentUser

由于 UserInfo 接口的验证方式是 Bear Token ,也就是只需要传入对应的 Access Token 即可,所以不需要配置 client_id 和 client_secret。

@ConfigurationProperties(prefix = "security.oauth2.resource") public class ResourceServerProperties implements BeanFactoryAware, InitializingBean {} 配置获取application.yml中oauth2的参数配置,校验过程

接下来,使用(2)Jwt加密算法的方式来实现,不过公钥使用的是读取文件的方式,不配置在参数中具体如下:

修改资源服务的JwtTokenConfig配置类,如下:

@Configuration
public class JwtTokenConfig {

    @Value("${pubPath}")
    private String pubPath;

    //公钥缓存key值,必须唯一
    @Value("${redis_key}")
    private  String PUBLIC_KEY;

    @Resource
    private RedisTemplate redisTemplate;

    
    @Bean
    public TokenStore jwtTokenStore() throws Exception {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() throws Exception {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setVerifierKey(getPublicKey(pubPath));
       // accessTokenConverter.setSigningKey("admin-sign");
        return accessTokenConverter;
    }

    /**
     * 从redis中获取公钥
     */
    public  String getPublicKey(String path) throws Exception {

        String key = (String) redisTemplate.opsForValue().get(PUBLIC_KEY);
        if(StringUtils.isEmpty(key)){
            key = RSAUtils.getPubKey(path);
            if(!StringUtils.isEmpty(key)){
                redisTemplate.opsForValue().set(PUBLIC_KEY,key);
            }
        }
        //String key = RSAUtils.getPubKey(path);
        return key;
    }
}

添加读取文件工具类:

/**
 * 获取公钥
 */
public class RSAUtils {
    /**
     * 非对称加密公钥Key
     */
    public static String getPubKey(String pubPath) throws Exception {
        InputStream in   = null;
        BufferedReader reader = null;
        StringBuilder sb = new StringBuilder();
        try {
            URL url = new URL(pubPath);
            URLConnection conn = url.openConnection();
            in = conn.getInputStream();
            reader = new BufferedReader(new InputStreamReader(in));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            return  sb.toString();
        }catch (Exception e){
            throw new Exception("读取公钥失败");
        }finally {
            if(null != reader){
                reader.close();
            }
            if(null != in){
                in.close();
            }
        }
    }
}

修改资源服务ResourceServerConfig配置类:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Value("${security.oauth2.resource.id}")
    private String resourceId;

   @Autowired
   private AuthExceptionEntryPoint authExceptionEntryPoint;

   @Autowired
   private TokenStore jwtTokenStore;


   @Override
   public void configure(ResourceServerSecurityConfigurer resources) {
       resources.authenticationEntryPoint(authExceptionEntryPoint) // 认证失败时候调用
               .resourceId(resourceId) // 必须与认证服务的resourceIds一致,不然就干脆都不配置,ResourceServerSecurityConfigurer默认一个
               .tokenStore(jwtTokenStore)
               .stateless(true);
   }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()// 对相关请求进行授权
                .anyRequest()// 任意请求
                .authenticated()// 都要经过验证
                /*.and()
                .requestMatchers()// 设置要保护的资源
                .antMatchers("/**")*/;// 保护的资源
    }
}

十八、Oauth2 + spring cloud gateway

Spring cloud gateway的配置如果不懂,请参考官网:
https://cloud.spring.io/spring-cloud-gateway/reference/html/
对应的中文网址: https://www.springcloud.cc/spring-cloud-greenwich.html#gateway-starter
注意gateway的pom.xml 不要引入 spring-boot-starter-web 这个包,因为会与gateway的webflux冲突
会报:

Consider defining a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' in your configuration.

接下来配置路由,主要做转发:

server:
  port: 8008
spring:
  profiles:
    active: test
  application:
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848  #Nacos 链接地址
    gateway:
      routes:
        - id: cloud-oauth
          uri: lb://cloud-oauth
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
        - id: cloud-api
          uri: lb://cloud-api
          predicates:
            - Path=/gateway/api/**
          filters:
            - StripPrefix=1

空文件

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/htboy/tensquare_parent.git
git@gitee.com:htboy/tensquare_parent.git
htboy
tensquare_parent
tensquare_parent
master

搜索帮助