• Spring Security OAuth2.0认证授权三:使用JWT令牌


    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解码后的信息:

    2021-01-11_201526.png

    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/

  • 相关阅读:
    harmonOS分布式软总线
    harmonyOS的Ability是什么(一)FA的Page跳转和迁移
    转 微信支付商户如何处理微信申请退款、退款回调、查询退款接口
    转 Shiro笔记
    sql语句还能这么写(mysql脚步编写)
    Mac Shell 操作
    JSON.stringify 深拷贝的弊端以及常用场景
    elementplus elementui 官网文档如何查看
    QtQt使用QWebsocket实现服务端和客户端通信
    QtQt使用QRegExp实现正则表达式处理(Qt5.14.2+win10)
  • 原文地址:https://www.cnblogs.com/kuangdaoyizhimei/p/14264844.html
Copyright © 2020-2023  润新知