Spring Security OAuth2.0系列文章:

前面两篇文章详细讲解了如何基于spring boot + oath2.0搭建认证中心和资源中心,本篇文章将会讲解集成jwt以及将客户端信息和授权码信息保存到数据库。

一、 JWT

1. JWT简介

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。

官网:https://jwt.io/

标准: https://tools.ietf.org/html/rfc7519

JWT令牌的优点:

1)jwt基于json,非常方便解析。

2)可以在令牌中自定义丰富的内容,易扩展。

3)通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。

4)资源服务使用JWT可不依赖认证服务即可完成授权。

缺点:

1)JWT令牌较长,占存储空间比较大,这意味着会耗费一定的带宽资源

2)JWT签名和验签都要耗费处理器资源

2. JWT令牌结构

JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz

2.1 Header

头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)一个例子如下:

下边是Header部分的内容

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

将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。

2.2 Payload

第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。

一个例子:

{
"sub": "1234567890",
"name": "456",
"admin": true
}

2.3 Signature

第三部分是签名,此部分用于防止jwt内容被篡改。

这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。

一个例子:

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
  • base64UrlEncode(header):jwt令牌的第一部分。
  • base64UrlEncode(payload):jwt令牌的第二部分。
  • secret:签名所使用的密钥。

二、配置JWT

1.认证服务配置JWT

TokenConfig类的修改

@Configuration
public class TokenConfig { private static final String SIGNING_KEY = "auth123"; @Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
} @Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//对称秘钥,资源服务器使用该秘钥来验证
return jwtAccessTokenConverter;
}
}

AuthorizationServerTokenServices设置Token增强类

@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter; @Bean
public AuthorizationServerTokenServices tokenServices(){
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService);
services.setSupportRefreshToken(true);
services.setTokenStore(tokenStore);
services.setAccessTokenValiditySeconds(7200);
services.setRefreshTokenValiditySeconds(259200); TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(jwtAccessTokenConverter));
services.setTokenEnhancer(tokenEnhancerChain); return services;
}

然后就可以测试了:

POST请求接口:http://127.0.0.1:30000/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=zhangsan&password=123

得到响应结果:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiLCJST0xFX0FQSSJdLCJleHAiOjE2MTAzNzI5MzUsImF1dGhvcml0aWVzIjpbInAxIiwicDIiXSwianRpIjoiOWQzMzRmZGMtOTcwZC00YmJkLWI2MmMtZDU4MDZkNTgzM2YwIiwiY2xpZW50X2lkIjoiYzEifQ.gZraRNeX-o_jKiH7XQgg3TlUQBpxUcXa2-qR_Treu8U",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiLCJST0xFX0FQSSJdLCJhdGkiOiI5ZDMzNGZkYy05NzBkLTRiYmQtYjYyYy1kNTgwNmQ1ODMzZjAiLCJleHAiOjE2MTA2MjQ5MzUsImF1dGhvcml0aWVzIjpbInAxIiwicDIiXSwianRpIjoiN2U1NzE0NTgtNmU2Zi00YjlmLTkxODQtOWUzZmVmZmQ1YTNjIiwiY2xpZW50X2lkIjoiYzEifQ.wyiS-z-xhBPZSODXZHQVDJCQ6dcmeJjAwBPWe2GhT94",
"expires_in": 7199,
"scope": "ROLE_ADMIN ROLE_USER ROLE_API",
"jti": "9d334fdc-970d-4bbd-b62c-d5806d5833f0"
}

会发现accessToken长了很多,这是因为token是jwt字符串,分为三部分,第二部分payload携带了很多信息,打开jwt.io网站,将上面的accessToken贴上去,可以看到Base64解码后的信息:

2.资源服务配置

第一步,将认证服务中的TokenConfig直接拷贝到资源服务中

第二步,修改ResouceServerConfig 类

@Autowired
private TokenStore tokenStore; @Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.resourceId(RESOURCE_ID)
// .tokenServices(resourceServerTokenServices)//令牌服务
.tokenStore(tokenStore)
.stateless(true);
}

即可完成资源服务集成jwt的功能。

3.接口测试

POST请求 http://127.0.0.1:30000/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=zhangsan&password=123 获取令牌,获取到accessToken之后携带token GET 请求 http://127.0.0.1:30001/r1 ,得到响应结果:

访问资源r1

即可证明成功。

三、客户端信息保存到数据库

认证服务客户端信息还是保存在内存中,现在将其改造放到数据库中

1. 新建表

DROP TABLE IF EXISTS `oauth_client_details`;

CREATE TABLE `oauth_client_details` (
`client_id` varchar(255) NOT NULL COMMENT '客户端标识',
`resource_ids` varchar(255) DEFAULT NULL COMMENT '接入资源列表',
`client_secret` varchar(255) DEFAULT NULL COMMENT '客户端秘钥',
`scope` varchar(255) DEFAULT NULL,
`authorized_grant_types` varchar(255) DEFAULT NULL,
`web_server_redirect_uri` varchar(255) DEFAULT NULL,
`authorities` varchar(255) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` longtext,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`archived` tinyint(4) DEFAULT NULL,
`trusted` tinyint(4) DEFAULT NULL,
`autoapprove` varchar(255) DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='接入客户端信息'; /*Data for the table `oauth_client_details` */ 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`,`create_time`,`archived`,`trusted`,`autoapprove`) values
('c1','res1','$2a$10$X2xVwW.7cOEh2niPqHYAne9EnjRJFj7QI4TqfmnDou9fT/45sCFEm','ROLE_ADMIN,ROLE_USER,ROLE_API','client_credentials,password,authorization_code,implicit,refresh_token','https://www.baidu.com',NULL,7200,259200,NULL,'2021-01-11 09:09:53',0,0,'false'),
('c2','res2','$2a$10$X2xVwW.7cOEh2niPqHYAne9EnjRJFj7QI4TqfmnDou9fT/45sCFEm','ROLE_API','client_credentials,password,authorization_code,implicit,refresh_token','https://www.baidu.com',NULL,31536000,2592000,NULL,'2021-01-11 09:09:56',0,0,'false'); /*Table structure for table `oauth_code` */ DROP TABLE IF EXISTS `oauth_code`; CREATE TABLE `oauth_code` (
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`code` varchar(255) DEFAULT NULL,
`authentication` blob,
KEY `code_index` (`code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

上述SQL新建了两张表oauth_client_details以及oauth_code分别用于存储客户端信息以及授权码信息,由于使用了jwt token(jwt token本身就存储了数据),所以不再保存数据库,两张表均为spring oauth2.0内置表,不需要写SQL,内置框架自动识别表。

2.修改配置

对应上述两张表,分别修改Bean对象的创建为jdbc类型的:

@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource){
return new JdbcAuthorizationCodeServices(dataSource);
} @Bean
public ClientDetailsService clientDetailsService(DataSource dataSource) {
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
clientDetailsService.setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}

之后修改客户端配置对象:

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
// clients.inMemory()
// .withClient("c1")
// .secret(new BCryptPasswordEncoder().encode("secret"))//$2a$10$0uhIO.ADUFv7OQ/kuwsC1.o3JYvnevt5y3qX/ji0AUXs4KYGio3q6
// .resourceIds("r1")
// .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
// .scopes("all")
// .autoApprove(false)
// .redirectUris("https://www.baidu.com");
}

完成修改。

3.接口测试

GET请求:http://127.0.0.1:30000/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_API&redirect_uri=https://www.baidu.com 获取授权码后,观察表oauth_code,里面应当已经有了授权码数据。

四、源码地址

源码地址:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v4.0.0

我的博客地址:https://blog.kdyzm.cn/

Spring Security OAuth2.0认证授权三:使用JWT令牌的更多相关文章

  1. Spring security OAuth2.0认证授权学习第三天(认证流程)

    本来之前打算把第三天写基于Session认证授权的,但是后来视屏看完后感觉意义不大,而且内容简单,就不单独写成文章了; 简单说一下吧,就是通过Servlet的SessionApi 通过实现拦截器的前置 ...

  2. Spring security OAuth2.0认证授权学习第四天(SpringBoot集成)

    基础的授权其实只有两行代码就不单独写一个篇章了; 这两行就是上一章demo的权限判断; 集成SpringBoot SpringBoot介绍 这个篇章主要是讲SpringSecurity的,Spring ...

  3. Spring security OAuth2.0认证授权学习第一天(基础概念-认证授权会话)

    这段时间没有学习,可能是因为最近工作比较忙,每天回来都晚上11点多了,但是还是要学习的,进过和我的领导确认,在当前公司的技术架构方面,将持续使用Spring security,暂不做Shiro的考虑, ...

  4. Spring security OAuth2.0认证授权学习第二天(基础概念-授权的数据模型)

    如何进行授权即如何对用户访问资源进行控制,首先需要学习授权相关的数据模型. 授权可简单理解为Who对What(which)进行How操作,包括如下: Who,即主体(Subject),主体一般是指用户 ...

  5. Spring security OAuth2.0认证授权学习第二天(基础概念-RBAC)

    RBAC 基于角色的访问控制 基于角色的访问控制用代码实现一下其实就是一个if的问题if(如果有角色1){ } 如果某个角色可以访问某个功能,当某一天其他的另一个角色也可以访问了,那么代码就需要变化, ...

  6. springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)

    项目security_simple(认证授权项目) 1.新建springboot项目 这儿选择springboot版本我选择的是2.0.6 点击finish后完成项目的创建 2.引入maven依赖  ...

  7. Spring Cloud Zuul 网关使用与 OAuth2.0 认证授权服务

    API 网关的出现的原因是微服务架构的出现,不同的微服务一般会有不同的服务地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题: 客户端会 ...

  8. 使用Owin中间件搭建OAuth2.0认证授权服务器

    前言 这里主要总结下本人最近半个月关于搭建OAuth2.0服务器工作的经验.至于为何需要OAuth2.0.为何是Owin.什么是Owin等问题,不再赘述.我假定读者是使用Asp.Net,并需要搭建OA ...

  9. Spring Security OAuth2.0 - AuthorizationServer和ResourceServer分离

    <Spring Security实现OAuth2.0授权服务 - 基础版>和<Spring Security实现OAuth2.0授权服务 - 进阶版>两篇文章中介绍如何搭建OA ...

  10. 基于spring boot2.0+spring security +oauth2.0+ jwt微服务架构

    github地址:https://github.com/hankuikuide/microservice-spring-security-oauth2 项目介绍 该项目是一个演示项目,主要演示了,基于 ...

随机推荐

  1. xamarin UWP中MessageDialog与ContentDialog的区别

    MessageDialog与ContentDialog的异同点解析: 相同点一:都是uwp应用上的一个弹窗控件.都能做为弹出应用. 相异点一:所在命名空间不同,MessageDialog在Window ...

  2. SVG基础图形与参数

    SVG是什么 SVG 指可伸缩矢量图形 (Scalable Vector Graphics) SVG 用来定义WEB上使用的矢量图 SVG 使用 XML 格式定义图形 SVG 图像在缩放时其图形质量不 ...

  3. 【C++面试】常考题复习:排序算法

    // Sort.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <stdlib.h> /*********** ...

  4. 一站式学习Wireshark(五):TCP窗口与拥塞处理

    https://community.emc.com/message/821593#821593 介绍 TCP通过滑动窗口机制检测丢包,并在丢包发生时调整数据传输速率.滑动窗口机制利用数据接收端的接收窗 ...

  5. Extjs3 Combo实现百度搜索查询

    在Extjs中实现Combo手输模糊筛选出下拉框数据.之前一直利用的Combo的keyup来实时的请求数据库进行查询.最近发现了一个更好的方式:只需要引用一个ComboBoxQuery Ext.ns( ...

  6. 移动端 iphone touchmove滑到边界(浏览器地址拦及以上) touchend失效解决办法

    在移动端h5页面:尤其那些全屏幕的盒展示切换页面,当用户无意中将手指滑到了 浏览器地址拦以上(中国移动这快区域):此时,手指已经离开屏幕了,但是ios上无法监听到touchend 事件:touchen ...

  7. jemeter正则表达式

  8. python数据结构(二)------元组

    元组是不可变序列,因此,元组的操作非常简单,本文就简单介绍一下,并解释下元组存在的意义: 2.2.1 元组的创建 2.2.2 tuple函数 2.2.3 基本元组操作 2.2.4 元组存在的意义 2. ...

  9. PyCharm 2019 最新激活方式总结(最新最全最有效!!!

    host 注册码 http://idea.lanyus.com/           https://www.cnblogs.com/yjd_hycf_space/p/9110550.html     ...

  10. Oracle左连接、右连接、全外连接以及(+)号用法

    1.准备工作 Oracle  外连接(OUTER JOIN)包括以下: 左外连接(左边的表不加限制) 右外连接(右边的表不加限制) 全外连接(左右两表都不加限制) 对应SQL:LEFT/RIGHT/F ...