# tensquare_parent **Repository Path**: htboy/tensquare_parent ## Basic Information - **Project Name**: tensquare_parent - **Description**: OAuth2-----授权码模式使用 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-07-30 - **Last Updated**: 2025-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: SpringBoot, SpringCloud, SpringCloudAlibaba, OAuth2, JWT ## README # OAuth2-----授权码模式使用 # 一、定义 1、定义: OAuth 2.0是一种授权框架,用于授权第三方应用访问用户的资源,如照片、个人信息等,OAuth 2.0具有高度的安全性和可扩展性,被广泛应用于各种开放平台的接口鉴权,是目前应用最广泛的开放平台鉴权方式之一 2、使用场景: 例如,一个终端用户(资源所有者)可以授权一个打印服务(客户端)访问她存储在照片共享服务(资源服务器)上的受保护照片,而无需与打印服务共享她的用户名和密码。相反,她直接与受照片共享服务信任的服务器(授权服务器)进行身份验证,该服务器颁发打印服务委托特定的凭据(访问令牌)。 第三方应用授权登录:在APP或者网页接入一些第三方应用时,时常会需要用户登录另一个合作平台,比如QQ,微博,微信的授权登录,第三方应用通过oauth2方式获取用户信息 # 二、流程图 ![流程图](images/1-377.png) 参考官网定义: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、授权码模式: ![授权码模式流程图](images/1-2497.png) (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、简化模式: ![简化模式流程图](images/1-4377.png) (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、密码模式 ![密码模式流程图](images/1-5254.png) (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、客户端模式 ![客户端模式流程图](images/1-6261.png) (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中引入 ``` org.springframework.cloud spring-cloud-starter-oauth2 org.springframework.boot spring-boot-starter-data-redis ``` 对于微服务项目,引入这个spring-cloud-starter-oauth2即可,里面已经包含的oauth2和security的jar;如果是spring boot 单体项目,可引入spring-security-oauth2-autoconfigure或者其他,其他功能所需依赖jar自行按需引入。 注意: Spring Security OAuth 已经停止更新 ![过期图](images/1-7234.png) ### 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:用来配置令牌访问端点的安全约束 ## 代码如下: ~~~java @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:可从数据库获取账号信息,在这里简单封装一下 ```java @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的参数: ~~~yaml 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 ~~~ ### 代码如下: ~~~java @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版本以后该写法已经摒弃,具体请查看官网示例 ~~~java @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模式可自定义登入页面属性路径,具体操作请自行上网查看相关信息 # 九、内存方式请求 ~~~java //配置客户端 @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攻击。授权服务器必须原封不动地将这个值返回给应用。| ![登入图](images/1-26867.png) ![授权图](images/1-26870.png) 选择“Approve ”授权 重定向到百度页面,并带着授权码 ![](images/1-26903.png) # 十一、获取token 获取code=AIljsQ,再postman工具中请求access_token,请求方式请参考官网说明: 请求"/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== ~~~java 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())); } ~~~ ![](images/1-28028.png) ![](images/1-28030.png) TokenEndpoint.class的postAccessToken为生成token的方法,但在进入该方法之前, 先判断认证是走client认证还是Authorization认证。首先 在AbstractAuthenticationProcessingFilter.doFilter ![](images/1-28174.png) 在ClientCredentialsTokenEndpointFilter.ClientCredentialsRequestMatcher.matches中判断请求参数中存在client_id,则走client客户端认证方式 ![](images/1-28289.png) 如果不存在,则进入到BasicAuthenticationFilter.doFilterInternal解释出Authorization的参数, 不管哪种方式最后都会在BasicAuthenticationFilter.doFilterInternal中进入到TokenEndpoint的postAccessToken生成token # 十二、拿token获取数据 localhost:8003/api/user/getCurrentUser 加上一步返回的token 请求资源验证入口为OAuth2AuthenticationProcessingFilter.class, public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {} ![](images/1-28666.png) 进入提取token,Authentication authentication = tokenExtractor.extract(request);在BearerTokenExtractor.extractv.extractToken.extractHeaderToken中提取token ![](images/1-28813.png) 也可以通过access_token参数带着token请求,然后封装成authentication一个对象 ![](images/1-28868.png) 在进入OAuth2AuthenticationManager认证管理,封装RemoteTokenServices的token验证对象,这一步很关键 ![](images/1-28944.png) 封装请求头 ![](images/1-28952.png) 远程请求验证token ![](images/1-28966.png) 随即进入验证身份的入口:AbstractAuthenticationProcessingFilter,重复验证的步骤,还是走的Authorization认证方式 ![](images/1-29049.png) ![](images/1-29051.png) 重新把请求对封装到上下文中 ![](images/1-29067.png) 进入到授权服务的token管理服务 ![](images/1-29087.png) 进入到token的校验方法中CheckTokenEndpoint ![](images/1-29122.png) 最后验证成功后返回到远程管理token方法中来 ![](images/1-29148.png) 回到认证管理OAuth2AuthenticationManager ![](images/1-29184.png) 回到验证入口,把对象放入到上写文中OAuth2AuthenticationProcessingFilter ![](images/1-29240.png) 最后到调用方法 ![](images/1-29250.png) 返回结果 ![](images/1-29258.png) # 十三、JDBC方式 ### 1、数据库添加一条数据 ~~~sql 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、修改授权服务器: ~~~java @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、修改资源服务器: ~~~java @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、请求: 授权码: ![](images/1-34896.png) 获取token: ![](images/1-34907.png) 先到数据库查询是否存在token ![](images/1-34926.png) 不存在时,则插入token到数据库 ![](images/1-34946.png) ![](images/1-34948.png) 请求资源,到数据库查询token ![](images/1-34968.png) ![](images/1-34970.png) ![](images/1-34973.png) # 十四、Redis方式 ### 1、配置redis对象 ~~~java @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的管理方式,其他信息,如客户端,授权码等仍存于数据库 ~~~java @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、修改资源服务器 ~~~java @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、请求 ![](images/1-40294.png) ![](images/1-40296.png) # 十五、Jwt是什么 JSON Web令牌(JWT)(全称:Json Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于作为JSON对象在各方之间安全地传输信息。此信息是经过数字签名的,因此可以验证和信任 官方: ---加解密 ![](images/1-40575.png) # 十六、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 ```java @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、修改授权服务 ~~~java @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、配置: ```yaml security: oauth2: resource: id: admin_resourceids #// 必须与授权服务的resourceIds一致,不然就干脆都不配置,ResourceServerSecurityConfigurer默认一个 ``` #### 1.2.2、添加jwt: ~~~java @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 ~~~java @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("/**")*/;// 保护的资源 } } ~~~ ![](images/1-47652.png) ![](images/1-47654.png) ## 1.3、增强jwt 添加增强类: ~~~java @Component public class JwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map 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; } } ~~~ ### 修改授权服务: ~~~java @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 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); } } ~~~ ![](images/1-52728.png) ## 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 证书的访问密码 ![](images/1-53300.png) ### 2.2、导出公钥: keytool -list -rfc --keystore adminrsa.jks | openssl x509 -inform pem -pubkey 输入口令,注意不能有换行 ![](images/1-53404.png) 复制出来放在某个路径下 ### 2.3、修改授权服务 在application.ym配置文件中添加 ![](images/1-53452.png) 修改JwtTokenConfig类,如下: ~~~java @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: ~~~java @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 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加密算法: ```yaml security: oauth2: resource: jwk: key-set-uri: http://localhost:8002/oauth/token_key ``` 在授权服务配置令牌端点的安全约束中的tokenKeyAccess要放开 ```java @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()")// 必须放开 .checkTokenAccess("permitAll()") .allowFormAuthenticationForClients(); } ``` 通过改配置,资源服务从授权服务中获取公钥,自己将token解析为明文。 ##### (2)Jwt加密算法: ```yaml security: oauth2: resource: jwt: key-uri: http://localhost:8002/oauth/token_key ``` 其返回的 JSON 只要含有 value 字段,值为对应的公钥,同样令牌端点的安全约束中的tokenKeyAccess要放开,需要依赖授权服务。 或者也可以使用如下的方式直接配置公钥的值: ```yaml security: oauth2: resource: jwt: key-value: | -----BEGIN CERTIFICATE----- MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO .......NyxMTXzf0= -----END CERTIFICATE----- ``` ##### (3)Jwt KeyStore加密算法: 密钥对存储到了一个 keystore 中,因此也可以直接配置keystore ```yaml security: oauth2: resource: jwt: key-store: test.jks key-store-password: test key-alias: test key-password: test ``` ##### (4)使用 Check Token Endpoint接口(Authorization Server提供的接口): ```yaml 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要放开 ```java @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()") .checkTokenAccess("permitAll()")// 必须放开 .allowFormAuthenticationForClients(); } ``` ##### (5)使用UserInfo Endpoint接口(Authorization Server提供的接口) ```yaml 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的参数配置,校验过程 ![](images/1-60191.png) ![](images/1-60193.png) ![](images/1-60195.png) ##### 接下来,使用(2)Jwt加密算法的方式来实现,不过公钥使用的是读取文件的方式,不配置在参数中具体如下: ![](images/1-60252.png) 修改资源服务的JwtTokenConfig配置类,如下: ~~~java @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; } } ~~~ 添加读取文件工具类: ~~~java /** * 获取公钥 */ 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配置类: ~~~java @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("/**")*/;// 保护的资源 } } ~~~ ![](images/1-63473.png) ![](images/1-63475.png) # 十八、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冲突 会报: ~~~java Consider defining a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' in your configuration. ~~~ ![](images/1-63477.png) 接下来配置路由,主要做转发: ```yaml 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 ```