子龙 子龙
首页
学习指南
工具
AI副业
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
AI副业
开源项目
技术书籍
  • 基础

  • JVM

  • Spring

  • 并发编程

  • Mybatis

  • 网络编程

  • 数据库

  • 缓存

  • 设计模式

  • 分布式

  • 高并发

  • SpringBoot

  • SpringCloudAlibaba

    • Spring Cloud 入门篇
    • SpringCloud 注册中心
    • SpringCloud Nacos
    • Spring Cloud Gateway
    • Spring Security 整合OAuth2
    • oauth 整合 jwt
      • 单点登录
      • Spring Cloud Feign
      • Spring Cloud Nacos Config
      • openfeign远程调用异常统一处理
      • Spring Cloud Ribbon
      • sentinel 实战
      • SpringCloud集成 报错 An attempt was made to call a method that does not exist
      • 什么是jwt
    • Nginx

    • 面试

    • 生产问题

    • 系统设计

    • 消息中间件

    • Java
    • SpringCloudAlibaba
    程序员子龙
    2024-01-29
    目录

    oauth 整合 jwt

    # 什么是jwt?

    以下是官网解释

    JSON Web Token (JWT) 是一个开放标准 ( RFC 7519 (opens new window) ),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

    虽然 JWT 可以加密以在各方之间提供保密,但我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则对其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是对其进行签名的一方。

    官网: https://jwt.io/

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

    JWT令牌的优点:

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

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

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

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

    缺点:

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

    # jwt应用场景

    • 授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够轻松跨不同域使用。
    • 信息交换:JSON Web Tokens 是一种在各方之间安全传输信息的好方法。因为 JWT 可以被签名——例如,使用公钥/私钥对——你可以确定发件人就是他们所说的那样。此外,由于使用标头和有效负载计算签名,因此您还可以验证内容是否未被篡改。

    # JWT组成

    一个JWT实际上就是一个字符串,它由用(.)分割的三部分组成,头部(header)、载荷(payload)与签名 (signature)。

    因此,JWT 通常如下所示。

    xxxxx.yyyyy.zzzzz
    
    1

    接下来分别介绍这三个部分

    # 头部(header)

    头部用于描述关于该JWT的最基本的信息:类型(即JWT)以及签名所用的算法(如 HMACSHA256或RSA)等。

    这也可以被表示成一个JSON对象:

    {  
     "alg": "HS256",
     "typ": "JWT" 
    }
    
    1
    2
    3
    4

    然后,这个 JSON 被Base64Url编码以形成 JWT 的第一部分。

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 
    
    1

    载荷(payload)

    存放有效信息的地方,包含三个部分:

    • 标准中注册的声明(建议但不强制使用),例如,iss: jwt签发者,sub: jwt所面向的用户 ,aud: 接收jwt的一方 ,exp: jwt的过期时间,这个过期时间必须要大于签发时间 ,nbf: 定义在什么时间之前,该jwt都是不可用的,iat: jwt的签发时间 ,jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
    • 公共声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不 建议添加敏感信息,因为该部分在客户端可解密。
    • 私有的声明 :私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,该部分信息可以归类为明文信息

    定义一个有效载荷

    {
      "sub": "1234567890",
      "name": "Jack",
      "iat": 1516239022
    }
    
    1
    2
    3
    4
    5

    然后将其进行base64加密,得到Jwt的第二部分:

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphY2siLCJpYXQiOjE1MTYyMzkwMjJ9
    
    1

    # 签名(signature)

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64加密后的)

    • payload (base64加密后的)

    • secret(密钥,在服务端)

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分:

    hrInLPw3Fzh2QZMSTXNYBRQbB2_WdyW0Yuemp3_Ht_A
    
    1

    把这三个部分连在一起就得到一个完整的jwt

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphY2siLCJpYXQiOjE1MTYyMzkwMjJ9.hrInLPw3Fzh2QZMSTXNYBRQbB2_WdyW0Yuemp3_Ht_A
    
    1

    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的 签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。

    # jwt使用

    是在请求头里加入Authorization,并加上Bearer标注:Authorization: Bearer <token>

    下图显示了如何获取 JWT 并将其用于访问 API 或资源:

    JSON 网络令牌如何工作

    1. 应用程序或客户端向授权服务器请求授权。

    2. 当授权被授予时,授权服务器向应用程序返回一个访问令牌。

    3. 应用程序使用访问令牌来访问受保护的资源(如 API)。

    # 快速使用

    JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

    引入依赖

      <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.6.0</version>
         </dependency>   
    
    1
    2
    3
    4
    5

    生成token

    private static final String SECRETKEY="1111111";
    
    @Test
    public void test() {
    
        Map map = new HashMap();
        map.put("phone","13211111");
        map.put("email","11@qq.com");
        //创建一个JwtBuilder对象
        JwtBuilder jwtBuilder = Jwts.builder()
                //声明的标识{"jti":"666"}
                .setId("666")
                //主体,用户{"sub":"jack"}
                .setSubject("jack")
                //创建日期{"ita":"xxxxxx"}
                .setIssuedAt(new Date())
                //设置过期时间   10分钟
                .setExpiration(new Date(System.currentTimeMillis()+600*1000))
                //claims 有效载荷
                .addClaims(map)
                .claim("roles","admin")
                //签名手段,参数1:算法,参数2:密钥
                .signWith(SignatureAlgorithm.HS256, SECRETKEY);
        //获取token   jwt
        String token = jwtBuilder.compact();
        System.out.println(token);
    
        //三部分的base64解密
        String[] split = token.split("\\.");
        //{"alg":"HS256"}
        System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
    
        //{"jti":"666","sub":"jack","iat":1636896993,"exp":1636897053,"phone":"13211111",
        "email":"11@qq.com","roles":"admin","logo":"xxx.jpg"}
        System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
        //base64无法解密
        System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
    }
    
    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

    验证解析token

    public void testParseToken(){
        //token
        String token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiJqYWNrIiwiaWF0IjoxNjM2ODk3NDk2LCJleHAiOjE2MzY4OTgwOTYsInBob25lIjoiMTMyMTExMTEiLCJlbWFpbCI6IjExQHFxLmNvbSIsInJvbGVzIjoiYWRtaW4iLCJsb2dvIjoieHh4LmpwZyJ9.VHZDARQFehcZZsp9Uurd4yWY_TwBwi8UVN01s3r7cfU";
        //解析token获取载荷中的声明对象
        Claims claims = Jwts.parser()
                .setSigningKey(SECRETKEY)
                .parseClaimsJws(token)
                .getBody();
    
        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        System.out.println("issuedAt:"+claims.getIssuedAt());
    
        DateFormat sf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("签发时间:"+sf.format(claims.getIssuedAt()));
        System.out.println("过期时间:"+sf.format(claims.getExpiration()));
        System.out.println("当前时间:"+sf.format(new Date()));
    
        System.out.println("roles:"+claims.get("roles"));
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    打印结果:

    id:666
    subject:jack
    issuedAt:Sun Nov 14 21:44:56 CST 2021
    签发时间:2021-11-14 21:44:56
    过期时间:2021-11-14 21:54:56
    当前时间:2021-11-14 21:45:30
    roles:admin
    
    1
    2
    3
    4
    5
    6
    7

    # Spring Security 整合Oauth2、JWT

    <dependency> 
    
        <groupId>org.springframework.security</groupId> 
    
        <artifactId>spring‐security‐jwt</artifactId> 
    
        <version>1.0.9.RELEASE</version> 
    
    </dependency> 
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    省略了spring security 配置

    配置JwtTokenStore

    @Configuration
    public class JwtTokenStoreConfig {
    
        @Bean
        public TokenStore jwtTokenStore(){
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
    
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(){
            JwtAccessTokenConverter accessTokenConverter = new
                    JwtAccessTokenConverter();
            //配置JWT使用的秘钥
            accessTokenConverter.setSigningKey("111111");
            return accessTokenConverter;
        }
    
        @Bean
        public JwtTokenEnhancer jwtTokenEnhancer() {
            return new JwtTokenEnhancer();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    配置扩展jwt存储内容

    public class JwtTokenEnhancer implements TokenEnhancer {
    
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
                                         OAuth2Authentication authentication) {
            Map<String, Object> info = new HashMap<>();
            info.put("phone", "1321111111");
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
            return accessToken;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    指定存储策略是jwt

    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;
    
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    
    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //配置JWT的内容增强器
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(delegates);
    
        endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
                .tokenStore(tokenStore)  //配置存储令牌策略
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(enhancerChain) //配置tokenEnhancer
                .reuseRefreshTokens(false)  //refresh_token是否重复使用
                .userDetailsService(userService) //刷新令牌授权包含对用户信息的检查
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
    }
    
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允许表单认证
        security.allowFormAuthenticationForClients();
    }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    
            clients.inMemory()
                    //配置client_id
                    .withClient("client")
                    //配置client-secret
                    .secret(passwordEncoder.encode("111111"))
                    //配置访问token的有效期
                    .accessTokenValiditySeconds(3600)
                    //配置刷新token的有效期
                    .refreshTokenValiditySeconds(864000)
                    //配置redirect_uri,用于授权成功后跳转
                    .redirectUris("http://www.baidu.com")
                    //配置申请的权限范围
                    .scopes("all")
                    /**
                     * 配置grant_type,表示授权类型
                     * authorization_code: 授权码
                     * password: 密码
                     * client_credentials: 客户端
                     * refresh_token: 更新令牌
                     */
                    .authorizedGrantTypes("authorization_code","password","refresh_token");
        }
    
    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

    通过密码方式获取token

    http://localhost:8080/oauth/token?grant_type=password&username=jack&password=123456&client_id=client&client_secret=123123&scope=all
    
    1
    {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwaG9uZSI6IjEzMjExMTExMTEiLCJ1c2VyX25hbWUiOiJqYWNrIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYzNjkwNjU4MiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYzA1ZWJlNzItMmU1Zi00OGJjLThhNzYtODAzMDIxNGM3MzliIiwiY2xpZW50X2lkIjoiY2xpZW50In0.6DdtIkeR9inSuHAxSTo4lQvanDqrKJ0v8ke6ZzWQZdA","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwaG9uZSI6IjEzMjExMTExMTEiLCJ1c2VyX25hbWUiOiJqYWNrIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImMwNWViZTcyLTJlNWYtNDhiYy04YTc2LTgwMzAyMTRjNzM5YiIsImV4cCI6MTYzNzc2Njk4MiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiOWJjMDkzM2ItMGZkNy00NTY2LWFhMTItYzlmMzYzNDBlZmY4IiwiY2xpZW50X2lkIjoiY2xpZW50In0.pcKNqiMZv4G9uKrv7P9qNYys6MQL0KJ-6MKz6HY9sbA","expires_in":3599,"scope":"all","phone":"1321111111","jti":"c05ebe72-2e5f-48bc-8a76-8030214c739b"}
    
    1

    使用token

    http://localhost:8080/user/getUser?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwaG9uZSI6IjEzMjExMTExMTEiLCJ1c2VyX25hbWUiOiJqYWNrIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYzNjkwNjU4MiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYzA1ZWJlNzItMmU1Zi00OGJjLThhNzYtODAzMDIxNGM3MzliIiwiY2xpZW50X2lkIjoiY2xpZW50In0.6DdtIkeR9inSuHAxSTo4lQvanDqrKJ0v8ke6ZzWQZdA
    
    1
    上次更新: 2024/01/30, 15:08:57
    Spring Security 整合OAuth2
    单点登录

    ← Spring Security 整合OAuth2 单点登录→

    最近更新
    01
    保姆级教程 用DeepSeek+飞书,批量写文案、写文章,太高效了
    06-06
    02
    还在为整理视频思维导图发愁?2 种超实用技巧,让你 10 分钟搞定,高效又省心!
    06-06
    03
    熬夜做PPT?AI一键生成高逼格幻灯片,效率提升10倍!
    06-06
    更多文章>
    Theme by Vdoing | Copyright © 2024-2025

        辽ICP备2023001503号-2

    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式