Oauth2+JWT 加密token


JWT 对称加密

JWT将 相关信息放在 令牌里

jwt全称 JSON Web Token。这个实现方式不用管如何进行存储(内存或磁盘),
因为它可以把相关信息数据编码存放在令牌里
。JwtTokenStore 不会保存任何数据,
但是它在转换令牌值以及授权信息方面与 DefaultTokenServices 所扮演的角色是一样的。

安全性

OAuth2提供了JwtAccessTokenConverter实现,添加jwtSigningKey,以此生成秘钥,以此进行签名,只有jwtSigningKey才能获取信息。
认证服务器
    /**
     * 对Jwt签名时,增加一个密钥
     * JwtAccessTokenConverter:对Jwt来进行编码以及解码的类
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("test-secret");
        return converter;
    }
    /**
     * 设置token 由Jwt产生,不使用默认的透明令牌
     */
    @Bean
    public JwtTokenStore jwtTokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }




    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.authenticationManager(authenticationManager)
        .tokenStore(jwtTokenStore())
        .accessTokenConverter(accessTokenConverter())
        .allowedTokenEndpointRequestMethods(HttpMethod.GET,
                HttpMethod.POST);
资源服务器
security:
  oauth2:
    resource:
      user-info-uri: http://localhost:9098/users/current
      jwt:
        key-value: test-secret
认证
http://localhost:9098/oauth/token?grant_type=authorization_code&code=ABB3bH&client_id=client_2&client_secret=123456&redirect_uri=http://www.baidu.com

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjI5MTIyOTIsInVzZXJfbmFtZSI6InVzZXJfMSIsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiZDgwODZjMDEtYzczZi00ODgzLTgzODctMDBiYjBmZTY1MGY5IiwiY2xpZW50X2lkIjoiY2xpZW50XzIiLCJzY29wZSI6WyJhbGwiXX0.nQfDpO5fIZ5BJQr8FyxPkqosHRMxPDhMFjyimIC5FNg",
    "token_type": "bearer",
    "expires_in": 604799,
    "scope": "all",
    "jti": "d8086c01-c73f-4883-8387-00bb0fe650f9"
}
token

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjIzNDUzNjgsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUExBVEZPUk1fTElTVCIsIkFQUExJQ0FUSU9OX0xJU1QiXSwianRpIjoiNTc5ODE1NjctMDEzMC00ZTIzLTkwZjMtZDk1ZmYzNzg3YTE5IiwiY2xpZW50X2lkIjoiY2R0eWUtaXRwcy1kc2MtY2xpZW50Iiwic2NvcGUiOlsicmVhZCIsIndyaXRlIiwidHJ1c3QiXX0.J8Hqf2fhD5_7_2-tYWOqHOk6WDBjF1wH0venMFZOQIY

解析后内容

    {
     alg: "HS256",
     typ: "JWT"
    }.
    {
     exp: 1562345368,
     user_name: "admin",
     authorities: [
      "PLATFORM_LIST",
      "APPLICATION_LIST"
     ],
     jti: "57981567-0130-4e23-90f3-d95ff3787a19",
     client_id: "cdtye-itps-dsc-client",
     scope: [
      "read",
      "write",
      "trust"
     ]
    }.

token
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjI5MTIyOTIsInVzZXJfbmFtZSI6InVzZXJfMSIsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiZDgwODZjMDEtYzczZi00ODgzLTgzODctMDBiYjBmZTY1MGY5IiwiY2xpZW50X2lkIjoiY2xpZW50XzIiLCJzY29wZSI6WyJhbGwiXX0.nQfDpO5fIZ5BJQr8FyxPkqosHRMxPDhMFjyimIC5FNg

解析后内容

    {
     alg: "HS256",
     typ: "JWT"
    }.
    {
     exp: 1562912292,
     user_name: "user_1",
     authorities: [
      "USER"
     ],
     jti: "d8086c01-c73f-4883-8387-00bb0fe650f9",
     client_id: "client_2",
     scope: [
      "all"
     ]
    }.

JWT 非对称加密(公钥秘钥)
生成jks 文件
keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass
导出公钥
keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey

拷贝创建public.txt文件
mytest.jks  public.txt 放入认证中心 resource资源路径
认证服务端
  /**
     * 对Jwt签名时,增加一个密钥
     * JwtAccessTokenConverter:对Jwt来进行编码以及解码的类
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext();
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(cx.getResource("classpath:mytest.jks"), "mypass".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
        return converter;
    }
    /**
     * 设置token 由Jwt产生,不使用默认的透明令牌
     */
    @Bean
    public JwtTokenStore jwtTokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
    /**
     * 用于扩展JWT
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(name = "jwtTokenEnhancer")
    public TokenEnhancer jwtTokenEnhancer(){
        return new MyJwtTokenEnhancer();
    }


        endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService).reuseRefreshTokens(true)
                 .tokenStore(jwtTokenStore())
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,
                HttpMethod.POST);
        // 自定义token生成方式
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new MyJwtTokenEnhancer(),accessTokenConverter()));
        endpoints.tokenEnhancer(tokenEnhancerChain);

    public class MyJwtTokenEnhancer implements TokenEnhancer {
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
            final Map<String, Object> additionalInfo = new HashMap<>();
            User user = (User) authentication.getUserAuthentication().getPrincipal();
            additionalInfo.put("username", user.getUsername());
            additionalInfo.put("authorities_", user.getAuthorities());
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
            }
    }
资源服务端
ResourceServerConfigurerAdapter

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext();
        Resource resource = cx.getResource("classpath:public.txt");
        String publicKey = null;
        try {
            publicKey = inputStream2String(resource.getInputStream());
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
        converter.setVerifierKey(publicKey);
        return converter;
    }

    private String inputStream2String(InputStream inputStream) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
        StringBuffer buffer = new StringBuffer();
        String line = "";
        while ((line = in.readLine()) != null){
            buffer.append(line);
        }
        return buffer.toString();
    }
token

    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjI5MTg3MzQsInVzZXJfbmFtZSI6InVzZXJfMSIsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiMjM5ZjNmZDctOGVlYS00ZmQ0LWE3MTYtY2IyODJjY2ExODAxIiwiY2xpZW50X2lkIjoiY2xpZW50XzIiLCJzY29wZSI6WyJhbGwiXX0.eXsTBbbtnUILD7KwNnhW8yAlH3FvDZE7k8RnLze5dJ9vkI_L_qVCOJ86DdcM_W8HEBTfZ3GetWkASBfmfrdH9pmDMU6EP--4dcIBTJ6-BYTp5nHpuzkREzZx6mjepDgfuKYpueJ6zqNzuaTJalozb7zsuGm8c8syv5urM9PK5iI5ndudG_w3RoW6PlkFVLdDcST5NF-K_flcOS7g40rw16gzLo-I2FH5JEHKVFbInlCvOlaITc9ren90aN3E1YKfngaPoY7-r0HUSqduqQsqVlLk3ckg4pitTuxVjyfU5PtCQZNKL4-TIB5IaotnshnSg15sAg9vnu0wvCnGhHOlqQ

解析后内容

    {
     alg: "RS256",
     typ: "JWT"
    }.
    {
     exp: 1562918734,
     user_name: "user_1",
     authorities: [
      "USER"
     ],
     jti: "239f3fd7-8eea-4fd4-a716-cb282cca1801",
     client_id: "client_2",
     scope: [
      "all"
     ]
    }.

携带自定义信息的token

    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyXzEiLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNTYyOTE5NjY5LCJhdXRob3JpdGllc18iOlt7ImF1dGhvcml0eSI6IlVTRVIifV0sImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiMjZkMWZkOGMtNTFiYS00NzIxLTg4ZDYtYzUxNTcwNmZhMDY2IiwiY2xpZW50X2lkIjoiY2xpZW50XzIiLCJ1c2VybmFtZSI6InVzZXJfMSJ9.EnhmeHF8Bykd6-AjerrMjYrb6orH7foRvturU62j6j76iqoSEQ-LtjTOAxVUlSjkkIjblzdD0qgJ_HNnKTZROttPqF59Ej8IbCtfrPh08JxaaeBBkhpMHwSM178Zd70eBUGVUt9k28ujF7l9C5HBxVZtxTEo4x7SuWlo5M1x0_mKyktvyGBzBWxievEhHVH59rtmh9vMc6RIfbMx-bzrVeDj6Pw2ARv9miyEqekh7sULTJugRZ4iQhg6H4j8NuodvV7UsLzQXm-5rpDiue69_WgYXMhtYYYdDdrVcBvBLnGjOuZ8u40aLls9uRFoiNPXdBxNhzTSjrXC2Z8pdvTsKw

    {
     alg: "RS256",
     typ: "JWT"
    }.
    {
     user_name: "user_1",
     scope: [
      "all"
     ],
     exp: 1562919669,
     authorities_: [
      {
    authority: "USER"
    }
     ],
     authorities: [
      "USER"
     ],
     jti: "26d1fd8c-51ba-4721-88d6-c515706fa066",
     client_id: "client_2",
     username: "user_1"
    }.

JWT 自定义token客户端验证
  • RemoteTokenServices 调认证中心CheckToken
    @Override
    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {

        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
        formData.add(tokenName, accessToken);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
        Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);

        if (map.containsKey("error")) {
            if (logger.isDebugEnabled()) {
                logger.debug("check_token returned error: " + map.get("error"));
            }
            throw new InvalidTokenException(accessToken);
        }

        // gh-838
        if (map.containsKey("active") && !"true".equals(String.valueOf(map.get("active")))) {
            logger.debug("check_token returned active attribute: " + map.get("active"));
            throw new InvalidTokenException(accessToken);
        }

        return tokenConverter.extractAuthentication(map);
    }
  • 拓展JWT 携带用户信息

  • 重写资源服务器处理行为 重写 remoteTokenServices