OAuth--JWT令牌

JWT令牌

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

令牌采用JWT格式即可解决上边的问题,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信 息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证 服务完成授权。

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

官网:https://jwt.io/
标准:https://tools.ietf.org/html/rfc7519

JWT令牌的优点:

  • jwt基于json,非常方便解析。
  • 可以在令牌中自定义丰富的内容,易扩展。
  • 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
  • 资源服务使用JWT可不依赖认证服务即可完成授权。

缺点:

  • JWT令牌较长,占存储空间比较大。

JWT令牌结构

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

  • Header
    头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA) 一个例子如下:
    下边是Header部分的内容
{
	"alg": "HS256", 
	"typ": "JWT"
}

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

  • Payload
    第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
    此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。 最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。 一个例子:
{
	"sub": "1234567890", 
	"name": "456", 
	"admin": true
}
  • Signature
  • 第三部分是签名,此部分用于防止jwt内容被篡改。
    这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明 签名算法进行签名。
    一个例子:
 HMACSHA256(
	base64UrlEncode(header) + "." + base64UrlEncode(payload), secret
)

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

配置JWT令牌服务

1、TokenConfig

@Configuration
public class TokenConfig {
    
    public static final String SIGNING_KEY = "uaa123";

    @Bean
    public TokenStore tokenStore() {
        //使用内存存储令牌(普通令牌)
        //return new InMemoryTokenStore();
        //使用JWT令牌存储
        return new JwtTokenStore(accessTokenConverter());
    }
    
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);//对称密钥,资源服务器使用该密钥来验证
        return converter;
    }
}

2、定义JWT令牌服务

	@Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    /**
     * 令牌管理服务
     */
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService);//客户端详情服务
        services.setSupportRefreshToken(true);//支持刷新
        services.setTokenStore(tokenStore);//令牌类型
        //设置令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        services.setTokenEnhancer(tokenEnhancerChain);

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

生成jwt令牌

在这里插入图片描述

校验令牌

通过暴露的端点校验
在这里插入图片描述

资源服务校验配置:

资源服务需要和授权服务拥有一致的签字、令牌服务等:
1、将授权服务中的TokenConfig类拷贝到资源 服务中
2、屏蔽资源 服务原来的令牌服务类


@Autowired
private TokenStore tokenStore;
    
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources
            .resourceId(RESOURCE_ID)//资源id
            .tokenStore(tokenStore)
            //.tokenServices(tokenService())//验证令牌的服务
            .stateless(true);
}

    // //资源服务临牌解析服务
    // @Bean
    // public ResourceServerTokenServices tokenService() {
    //     //使用远程服务请求授权服务器token,必须指定校验token的url、client_id,client_secret
    //     RemoteTokenServices service = new RemoteTokenServices();
    //     service.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");
    //     service.setClientId("c1");
    //     service.setClientSecret("secret");
    //     return service;
    // }

3、测试
1)申请jwt令牌
2)使用令牌请求资源

在这里插入图片描述
在这里插入图片描述

完善环境配置

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

在user_db中创建如下表:

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 COMMENT '客户端标 识',
`resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '接入资源列表',
`client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '客户端秘钥',
`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` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL, `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE
CURRENT_TIMESTAMP(0),
`archived` tinyint(4) NULL DEFAULT NULL,
`trusted` tinyint(4) NULL DEFAULT NULL,
`autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '接入客户端信息' ROW_FORMAT = Dynamic;

INSERT INTO `oauth_client_details` VALUES ('c1', 'res1', '$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm', 'ROLE_ADMIN,ROLE_USER,ROLE_API', 'client_credentials,password,authorization_code,implicit,refresh_token', 'http://www.baidu.com', NULL, 7200, 259200, NULL, '2019‐09‐09 16:04:28', 0, 0, 'false');

INSERT INTO `oauth_client_details` VALUES ('c2', 'res2', '$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm', 'ROLE_API', 'client_credentials,password,authorization_code,implicit,refresh_token', 'http://www.baidu.com', NULL, 31536000, 2592000, NULL, '2019‐09‐09 21:48:51', 0, 0, 'false');

oauth_code表,Spring Security OAuth2使用,用来存储授权码:

 DROP TABLE IF EXISTS `oauth_code`; CREATE TABLE `oauth_code` (
`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `authentication` blob NULL,
INDEX `code_index`(`code`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

配置授权服务

(1)修改AuthorizationServer:

ClientDetailsService和AuthorizationCodeServices从数据库读取数据。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {

    //令牌
    @Autowired
    private TokenStore tokenStore;

    //客户端详细信息服务
    @Autowired
    private ClientDetailsService clientDetailsService;

    //授权码服务
    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    //认证管理器
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Autowired
    PasswordEncoder passwordEncoder;


    /**
     * 配置客户端详情服务
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
        // clients.inMemory()//使用in-memory存储
        //         .withClient("c1")//client_id
        //         .secret(new BCryptPasswordEncoder().encode("secret"))//客户端密钥
        //         .resourceIds("res1")//资源列表
        //         .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
        //         .scopes("all")//允许的授权范围
        //         .autoApprove(false)//跳转到授权页面
        //         .redirectUris("http://www.baidu.com");//添加验证回调地址
    }

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

    /**
     * 令牌管理服务
     */
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService);//客户端详情服务
        services.setSupportRefreshToken(true);//支持刷新
        services.setTokenStore(tokenStore);//令牌类型
        //设置令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        services.setTokenEnhancer(tokenEnhancerChain);

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

    /**
     * 令牌访问端点
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)//认证管理器
                .authorizationCodeServices(authorizationCodeServices)//授权码服务
                .tokenServices(tokenServices())//令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    /**
     * 安全约束
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll")// /oauth/token_key是公开的
                .checkTokenAccess("permitAll")// /oauth_check_token公开
                .allowFormAuthenticationForClients();  // 表单认证(申请令牌)
    }

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

    //jdbc方式存储授权码
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

}

测试

1、测试申请令牌
使用密码模式申请令牌,客户端信息需要和数据库中的信息一致。
在这里插入图片描述

校验:得到的信息与数据库中一致
在这里插入图片描述

2、测试授权码模式
生成的授权存储到数据库中。

localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_ADMIN&redirect_uri=http://www.baidu.com

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

发布了892 篇原创文章 · 获赞 2314 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/cold___play/article/details/105235785