当前位置:   article > 正文

(九)Spring Security (spring-cloud-starter-oauth2)应用详解------JWT令牌------数据库存储_spring-cloud-starter-security

spring-cloud-starter-security

前提:在看完这两篇文章的基础上再进行本篇文章的开发
(七)Spring Security (spring-cloud-starter-oauth2)应用详解------认证授权服务------授权码模式和密码模式
(八)Spring Security (spring-cloud-starter-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

package com.cyj.security.oauth2.auth.jwt.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * @Program: Spring-Security-OAuth2
 * @Description:
 * @Author C_Y_J
 * @Create: 2021-01-31 17:53
 **/
@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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

测试

启动auth 服务
在这里插入图片描述

校验jwt令牌

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

1、将auth 认证授权服务中的TokenConfig类拷贝到order资源服务中
在这里插入图片描述

2、屏蔽order资源服务中ResouceServerConfig类的令牌解析服务
在这里插入图片描述
3、添加已经修改

    @Autowired
    TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
                //客户端访问的资源列表
                .resourceId(RESOURCE_ID)
                //验证令牌的服务
                //.tokenServices(tokenService())
                //将原来的远程调用改成jwt解析
                .tokenStore(tokenStore)
                .stateless(true);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

测试

1)申请jwt令牌

2)使用令牌请求资源
在这里插入图片描述

完善环境配置

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

在spring-security中创建如下表:

oauth_client_details 存储客户端信息

USE `spring-security` ;

/*Table structure for table `oauth_client_details` */
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$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'
  ) ;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

oauth_code 存储授权码

USE `spring-security`;

/*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 COMMENT '授权码',
  `authentication` BLOB,
  KEY `code_index` (`code`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

persistent_logins 存储用户登录信息

USE `spring-security`;

/*Table structure for table `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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

配置授权服务

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");

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        //设置授权码模式的授权码如何存取,暂时采用内存方式
//        return new InMemoryAuthorizationCodeServices();
        //设置授权码模式的授权码存储到数据库
        return new JdbcAuthorizationCodeServices(dataSource);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

bug:

The bean ‘clientDetailsService’, defined in class path resource [com/cyj/security/oauth2/auth/jwt/config/AuthorizationServer.class], could not be registered. A bean with that name has already been defined in BeanDefinition defined in class path resource [org/springframework/security/oauth2/config/annotation/configuration/ClientDetailsServiceConfiguration.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true

解决: 在application.properties添加

spring.main.allow-bean-definition-overriding = true
  • 1

测试

测试授权码模式
重启项目,登录 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
在这里插入图片描述

测试密码模式
在这里插入图片描述
原文链接

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号