概述

在 OAuth2 体系中认证通过后返回的令牌信息分为两大类:不通明令牌(opaque tokens)通明令牌(not opaque tokens)。

不通明令牌 就是一种无可读性的令牌,一般来说就是一段一般的 UUID 字符串。应用不通明令牌会升高零碎性能和可用性,并且减少提早,因为资源服务不晓得这个令牌是什么,代表谁,须要调用认证服务器获取用户信息接口,如下就是咱们在资源服务器中的配置,须要指明认证服务器的接口地址。

security:  oauth2:    resource:      user-info-uri: http://localhost:5000/user/current/get      id: account-service

通明令牌的典型代表就是 JWT 了,用户信息保留在 JWT 字符串中,资源服务器本人能够解析令牌不再须要去认证服务器校验令牌。

之前的章节中咱们是应用了不通明令牌access_token,但思考到在微服务体系中这种中心化的受权服务会成为瓶颈,本章咱们就应用jwt来替换之前的access_token,专(zhuang)业(bi)点就叫去中心化。

jwt 是什么

Json web token (JWT), 是为了在网络应用环境间传递申明而执行的一种基于JSON的凋谢规范(RFC 7519)。该token被设计为紧凑且平安的,特地实用于分布式站点的单点登录(SSO)场景。JWT的申明个别被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也能够减少一些额定的其它业务逻辑所必须的申明信息,该token也可间接被用于认证,也可被加密。

简略点说就是一种固定格局的字符串,通常是加密的;

它由三局部组成,头部、载荷与签名,这三个局部都是json格局。

  • Header 头部:JSON形式形容JWT根本信息,如类型和签名算法。应用Base64编码为字符串
  • Payload 载荷: JSON形式形容JWT信息,除了规范定义的,还能够增加自定义的信息。同样应用Base64编码为字符串。

    1. iss: 签发者
    2. sub: 用户
    3. aud: 接管方
    4. exp(expires): unix工夫戳形容的过期工夫
    5. iat(issued at): unix工夫戳形容的签发工夫
  • Signature 签名: 将前两个字符串用 . 连贯后,应用头部定义的加密算法,利用密钥进行签名,并将签名信息附在最初。

JWT能够应用对称的加密密钥,但更平安的是应用非对称的密钥,本篇文章应用的是对称加密。

代码批改

数据库

原来应用access_token的时候咱们建设了7张oauth2相干的数据表

应用jwt的话只须要在数据库存储一下client信息即可,所以咱们只须要保留数据表oauth_client_details。

其余数据表已不再须要,大家能够删除。

认证服务 AuthorizationServerConfig

  1. 批改 AuthorizationServerConfig 中TokenStore的相干配置
@Beanpublic TokenStore tokenStore() {    //return new JdbcTokenStore(dataSource);        return new JwtTokenStore(jwtTokenEnhancer());}/** * JwtAccessTokenConverter * TokenEnhancer的子类,帮忙程序在JWT编码的令牌值和OAuth身份验证信息之间进行转换。 */@Beanpublic JwtAccessTokenConverter jwtTokenEnhancer(){        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        // 设置对称签名        converter.setSigningKey("javadaily");        return converter;}

之前咱们是将access_token存入数据库,应用jwt后不再须要存入数据库,所以咱们须要批改存储形式。

jwt须要应用加密算法对信息签名,这里咱们先应用 对称秘钥 (javadaily)来签订咱们的令牌,对称秘钥当然这也认为着资源服务器也须要应用雷同的秘钥。

  1. 批改 configure(AuthorizationServerEndpointsConfigurer endpoints) 办法,配置jwt
@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {        //如果须要应用refresh_token模式则须要注入userDetailService        endpoints.authenticationManager(this.authenticationManager)                        .userDetailsService(userDetailService)                        .tokenStore(tokenStore())                        .accessTokenConverter(jwtTokenEnhancer());}

这里次要是注入accessTokenConverter,即下面配置的token转换器。

通过下面的配置,认证服务器曾经能够帮咱们生成jwt token了,这里咱们先应用Postman调用一下,看看生成的jwt token。

从上图看出曾经失常生成jwt token,咱们能够将生成的jwt token拿到https://jwt.io/网站上进行解析。

如果大家对生成jwt token的逻辑不是很理解,能够在 DefaultTokenServices#createAccessToken(OAuth2Authentication authentication)JwtAccessTokenConverter#enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication)上打个断点,察看代码执行的成果。
  1. 删除认证服务器提供给资源服务器获取用户信息的接口
/** * 获取受权的用户信息 * @param principal 以后用户 * @return 受权信息 */@GetMapping("current/get")public Principal user(Principal principal){        return principal;}

用了通明令牌jwt token后资源服务器能够间接解析验证token,不再须要调用认证服务器接口,所以此处能够间接删除。

  1. 批改jwt token有效期(可选)

    jwt token的默认有效期为12小时,refresh token的有效期为30天,如果要批改默认工夫能够注入 DefaultTokenServices 并批改无效工夫。

@Primary@Beanpublic DefaultTokenServices tokenServices(){        DefaultTokenServices tokenServices = new DefaultTokenServices();        tokenServices.setTokenEnhancer(jwtTokenEnhancer());        tokenServices.setTokenStore(tokenStore());        tokenServices.setSupportRefreshToken(true);        //设置token有效期,默认12小时,此处批改为6小时   21600        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 6);        //设置refresh_token的有效期,默认30天,此处批改为7天        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);        return tokenServices;}

而后在 configure() 办法中增加tokenServices

@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {                //如果须要应用refresh_token模式则须要注入userDetailService        endpoints.authenticationManager(this.authenticationManager)                        .userDetailsService(userDetailService)            //注入自定义的tokenservice,如果不应用自定义的tokenService那么就须要将tokenServce里的配置移到这里                        .tokenServices(tokenServices());}

资源服务器 ResourceServerConfig

  1. 删除资源服务器中配置认证服务器的接口属性 user-info-uri
security:      oauth2:          resource:              id: account-service
  1. 注入TokenStore 和 JwtAccessTokenConverter
@Beanpublic TokenStore tokenStore() {        return new JwtTokenStore(jwtTokenEnhancer());}@Beanpublic JwtAccessTokenConverter jwtTokenEnhancer(){        JwtAccessTokenConverter jwtTokenEnhancer = new JwtAccessTokenConverter();        jwtTokenEnhancer.setSigningKey("javadaily");        return jwtTokenEnhancer;}
留神:对称加密算法须要跟认证服务器秘钥保持一致,当然这里能够提取到配置文件中。
  1. 增加 configure(ResourceServerSecurityConfigurer resources) 办法,退出token相干配置
@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {        resources.resourceId(resourceId)                        .tokenStore(tokenStore());}
思考题:资源服务器应用jwt后从哪校验token呢?

给利用增加 @EnableResourceServer 注解后会给Spring Security的FilterChan增加一个 OAuth2AuthenticationProcessingFilterOAuth2AuthenticationProcessingFilter 会应用 OAuth2AuthenticationManager 来验证token。

校验逻辑主体代码执行程序如下:

倡议大家在 OAuth2AuthenticationProcessingFilter#doFilter() 处打个断点领会一下校验过程。

网关 SecurityConfig

  1. 创立 ReactiveJwtAuthenticationManager 从tokenStore加载 OAuth2AccessToken

    因为原来的access_token是存储在数据库中,所以咱们编写了 ReactiveJdbcAuthenticationManager 来从数据库获取access_token,当初应用jwt咱们也须要定义一个jwt的相干类 ReactiveJwtAuthenticationManager,代码跟 ReactiveJdbcAuthenticationManager 一样,这里就不再贴出。

  2. 注入TokenStore 和 JwtAccessTokenConverter
@Bean  public TokenStore tokenStore() {        return new JwtTokenStore(jwtTokenEnhancer());}@Bean  public JwtAccessTokenConverter jwtTokenEnhancer(){        JwtAccessTokenConverter jwtTokenEnhancer = new JwtAccessTokenConverter();        jwtTokenEnhancer.setSigningKey("javadaily");        return jwtTokenEnhancer;}
留神:对称加密算法须要跟认证服务器秘钥保持一致,当然这里能够提取到配置文件中。
  1. 批改 SecurityConfig#SecurityWebFilterChain() 办法,替换 ReactiveJdbcAuthenticationManager
ReactiveAuthenticationManager tokenAuthenticationManager = new ReactiveJwtAuthenticationManager(tokenStore());

只须要将tokenStore传入结构器即可。

测试

大家自行测试。

小结

应用jwt token 和 access_token 最大的区别就是资源服务器不再须要去认证服务器校验token,晋升了零碎整体性能,应用jwt后我的项目的流程架构如下:

本系列文章目前是第19篇,如果大家对之前的文章感兴趣能够移步至 http://javadaily.cn/tags/SpringCloud 查看

应用了jwt咱们不仅要看到jwt的长处,也要看到它的毛病,这样咱们能力依据理论场景自由选择,上面是jwt最大的两个毛病:

  • jwt是一次性的,一旦token被签发,那么在到期工夫之前都是无效的,无奈废除。如果你中途批改了用户权限须要更新信息那就只能从新签发一个jwt,然而旧的jwt还是能够失常应用,应用旧的jwt拿到的信息也就是过期的。
  • jwt 蕴含了认证信息,一旦泄露,任何人都能够取得该令牌的所有权限。为了避免盗用,jwt的无效工夫不应该设置过长。