从零开始SpringCloud Alibaba实战(34)——spring-cloud-starter-oauth2 JWT令牌

oauth2应用弊端

当资源服务和授权服务不在一起时,资源服务使用RemoteTokenServices 远程请求授权服务每次验证token,如果访问量较大将会影响系统的性能 。

解决上边问题:
令牌采用JWT格式即可解决上边的问题,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带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可不依赖认证服务即可完成授权。
JWT令牌的缺点:
1)JWT令牌较长,占存储空间比较大。

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

Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)

Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。

此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。

最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。

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

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

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

配置JWT令牌服务

在auth服务中配置jwt令牌服务,即可实现生成jwt格式的令牌。

补充:为了方便管理,我将AuthorizationServer 类中 这令牌存储策略删除,在接下来的TokenConfig类中我重新注入了一个新的TokenStore

新建 TokenConfig


@Configuration
public class TokenConfig {

    /**
     * 密钥  分为对称性和非对称性 这里采用对称性
     */
    private String SIGNING_KEY = "auth123";

    @Bean
    public TokenStore tokenStore() {
        //JWT令牌存储方案
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //对称秘钥,资源服务器使用该秘钥来验证
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
}

AuthorizationServer

    /**
     * jwt存储方案
     */
    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    /**
     * 令牌管理服务
     *
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service = new DefaultTokenServices();
        //客户端详情服务
        service.setClientDetailsService(clientDetailsService);
        //支持刷新令牌
        service.setSupportRefreshToken(true);
        //令牌存储策略
        service.setTokenStore(tokenStore);

        //jwt令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);


        // 令牌默认有效期2小时
        service.setAccessTokenValiditySeconds(7200);
        // 刷新令牌默认有效期3天
        service.setRefreshTokenValiditySeconds(259200);
        return service;
    }

测试
启动auth 服务

校验jwt令牌

在article 服务中配置校验jwt令牌服务,即可实现解析jwt令牌。

1、将auth 认证授权服务中的TokenConfig类拷贝到article资源服务中
2、屏蔽article资源服务中ResouceServerConfig类的令牌解析服务
3、添加已经修改

    @Autowired
    TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
                //客户端访问的资源列表
                .resourceId(RESOURCE_ID)
                //验证令牌的服务
                //.tokenServices(tokenService())
                //将原来的远程调用改成jwt解析
                .tokenStore(tokenStore)
                .stateless(true);
    }

测试

1)申请jwt令牌

2)使用令牌请求资源

完善环境配置

截止目前客户端信息和授权码仍然存储在内存中,生产环境中通过会存储在数据库中,下边完善环境的配置:

在spring-security中创建如下表:

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 = '接入客户端信息' ;


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$27P99rHURZHuws1f6LLOouh/hYPh3I0TDCg1LWtiYJ7oupdmZ7mlO',
    'ROLE_ADMIN,ROLE_USER,ROLE_API',
    'client_credentials,password,authorization_code,implicit,refresh_token',
    'http://www.baidu.com',
    NULL,
    7200,
    259200,
    NULL,
    '2020-12-13 15:28:13',
    0,
    0,
    'false'
  ),
  (
    'c2',
    'res2',
    '$2a$10$27P99rHURZHuws1f6LLOouh/hYPh3I0TDCg1LWtiYJ7oupdmZ7mlO',
    'ROLE_API',
    'client_credentials,password,authorization_code,implicit,refresh_token',
    'http://www.baidu.com',
    NULL,
    31536000,
    2592000,
    NULL,
    '2020-12-13 15:28:30',
    0,
    0,
    'false'
  ) ;


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 COMMENT '授权码',
  `authentication` BLOB,
  KEY `code_index` (`code`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

persistent_logins 存储用户登录信息




DROP TABLE IF EXISTS `persistent_logins`;

CREATE TABLE `persistent_logins` (
  `username` VARCHAR(64) NOT NULL,
  `series` VARCHAR(64) NOT NULL,
  `token` VARCHAR(64) NOT NULL,
  `last_used` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

配置授权服务

AuthorizationServer

    /**
     * 客户端密码加密规则
     */
    @Autowired
    PasswordEncoder passwordEncoder;

    /**
     * 将客户端信息存储到数据库
     *
     * @param dataSource
     * @return
     */
    @Bean
    public ClientDetailsService clientDetailsService(DataSource dataSource) {
        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    /**
     * 客户端详情
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        //客户端信息存储到数据库
        clients.withClientDetails(clientDetailsService);

//        clients
//                // 使用in‐memory存储
//                .inMemory()
//                // client_id
//                .withClient("c1")
//                //客户端密钥
//                .secret(new BCryptPasswordEncoder().encode("secret"))
//                //客户端访问的资源列表
//                .resourceIds("res1")
//                // 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials
//                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
//                // 允许的授权范围
//                .scopes("all")
//                //false 允许跳转到授权页面
//                .autoApprove(false)
//                //加上验证回调地址
//                .redirectUris("http://www.baidu.com");

    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        //设置授权码模式的授权码如何存取,暂时采用内存方式
//        return new InMemoryAuthorizationCodeServices();
        //设置授权码模式的授权码存储到数据库
        return new JdbcAuthorizationCodeServices(dataSource);
    }

测试
测试授权码模式
重启项目,登录 http://localhost:8080/auth/login

紧接着我们访问 http://localhost:8080/auth/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_API&redirect_uri=http://www.baidu.com

注意 参数 scope = ROLE_API 。这个参数值要和数据库保存的值一致,三个值随便哪个都可以
在这里插入图片描述

返回的code值是1D71hR

在这里插入图片描述

注意: 如果要去访问资源服务,就会发现出问题

原因是在资源服务设置成all,你可以修改代码成数据库里的值,也可以在数据库添加all
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值