Spring Cloud Oauth2实现分布式权限认证(JWT版)

目录

环境:

摘要说明:

步骤:

一、什么是JWT

二、实现JWT版授权服务

1、公共模块(oauth2-common)

2、授权服务(oauth2-server)

3、应用服务(oauth2-client)

三、测试

四、jwt的优缺点

jwt的优点:

jwt的缺点:

五、源码地址

环境:

JDK1.8,spring-boot(2.0.3.RELEASE),spring cloud(Finchley.RELEASE)

摘要说明:

       上一节中我们oauht2+redis实现了分布式授权服务,但反过来总结下就会发现该模式还是中心化授权;即任何应用服务的接口访问都必须通过token去访问授权服务(oauth2-server)是否满足授权,即所有应用服务都必须配置授权服务获取用户信息接口;

       但考虑到微服务往往意味着高并发,高流量;故这种中心化的授权服务会成为瓶颈,所以如何实现去中心化的授权服务就需要依赖本章节的JWT;

步骤:

一、什么是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可以使用对称的加密密钥,但更安全的是使用非对称的密钥;下面就让我们使用jdk自带的keytool生成非对称的公钥、私钥;

生成公钥:

keytool -genkeypair -alias spring-jwt -validity 3650 -keyalg RSA -dname "CN=Victor,OU=Karonda,O=Karonda,L=Shenzhen,S=Guangdong,C=CN" -keypass admin123456 -storepass admin123456 -keystore spring-jwt.jks

这里面主要是-keypass 密钥 -storepass 密钥

生产私钥:

keytool -list -rfc --keystore spring-jwt.jks | openssl x509 -inform pem -pubkey

会提示输入上述私钥的密码:

接着加下面这部分cope出生成public.cert;

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgsMnUy3qsvv9RypsSH/t
4/Kqo6xAW0yegdTBnXxbw/FJiTt9FSEg17yIm7Emg09vUJTKjUMFUMMT9fJ+r29j
LZuG88yTgQXOkqk64tUYh56M8GMKSXKRhyQdgdLQVi3lhaBh977/3aCtDerjzbkk
kFGDm8psDf26sqHj6Derka+M4V+/f4fz7CRu4QSfMGUePbT2V7mI3Y2kS0DHRl0f
K7SzbFkBr+MCBe7fO0wPklhx2W18/V7dYQe2ssd8Et+AzVI+yjbreqM+775b7Imw
bgxv1iJbi5j1JZ3AzsuViZ6OktLyCpRBGNZX7C/8xyNv9m/QjTsHJy/nSrLdbJQ5
2wIDAQAB
-----END PUBLIC KEY-----

将生成的spring-jwt.jks和 public.cert分别放到授权服务(oauth2-server)和应用服务(oauth2-client)的resource下

二、实现JWT版授权服务

         在上一节的基础上想实现JWT授权服务其实很简单,只需要修改以下几点

1、公共模块(oauth2-common)

添加JWT转换器配置(JwtConfig)用户应用服务解析前端传入的jwt形式token:

@Configuration
public class JwtConfig {
    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        Resource resource = new ClassPathResource("public.cert"); // 公钥

        String publicKey;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        converter.setVerifierKey(publicKey);

        return converter;
    }
}

2、授权服务(oauth2-server)

修改授权服务配置(AuthorizationServerConfiguration):

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    /**
     * 认证管理器
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 
     * @方法名:tokenStore
     * @方法描述:自定义储存策略
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午1:56:02
     * @修改人:cc
     * @修改时间:2019年11月19日 下午1:56:02
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtTokenEnhancer());
    }

    /**
     * 定义令牌端点上的安全性约 束
     * 
     * (non-Javadoc)
     * 
     * @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer)
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    /**
     * 用于定义客户端详细信息服务的配置程序。可以初始化客户端详细信息;
     * 
     * (non-Javadoc)
     * 
     * @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer)
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // clients.withClientDetails(clientDetails());
        clients.inMemory().withClient("android").scopes("read").secret(DigestUtil.encrypt("android"))
                .authorizedGrantTypes("password", "authorization_code", "refresh_token").and().withClient("webapp")
                .scopes("read").authorizedGrantTypes("implicit").and().withClient("browser")
                .authorizedGrantTypes("refresh_token", "password").scopes("read");
    }

    @Bean
    public WebResponseExceptionTranslator webResponseExceptionTranslator() {
        return new MssWebResponseExceptionTranslator();
    }

    /**
     * 定义授权和令牌端点以及令牌服务
     * 
     * (non-Javadoc)
     * 
     * @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer)
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer())
                .authenticationManager(authenticationManager).exceptionTranslator(webResponseExceptionTranslator());
    }

    /**
     * <p>
     * 注意,自定义TokenServices的时候,需要设置@Primary,否则报错,
     * </p>
     * 
     * @return
     */
    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setTokenEnhancer(jwtTokenEnhancer());
        // tokenServices.setClientDetailsService(clientDetails());
        // token有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 7);
        // tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
        // refresh_token默认30天
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 7);
        // tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }

    /**
     * 定义jwt的生成方式
     *
     * @return JwtAccessTokenConverter
     */
    @Bean
    public JwtAccessTokenConverter jwtTokenEnhancer() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 非对称加密,但jwt长度过长
        KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("spring-jwt.jks"), "admin123456".toCharArray())
                .getKeyPair("spring-jwt");
        converter.setKeyPair(keyPair);
        // 对称加密
        // converter.setSigningKey("admin123");
        return converter;
    }
}

修改资源服务配置(ResourceServerConfig):


@Configuration
@EnableResourceServer
@Order(3)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    TokenStore tokenStore;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().exceptionHandling()
                .authenticationEntryPoint(
                        (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and().requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**")
                .authenticated().and().httpBasic();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore);
    }
}

3、应用服务(oauth2-client)

去除配置里授权服务的链接配置:

修改资源服务配置(ResourceServerConfig),只能有上述公共模块(oauth2-common)生成的tokenStore进行解析

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    TokenStore tokenStore;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().exceptionHandling()
                .authenticationEntryPoint(
                        (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and().requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**")
                .authenticated().and().httpBasic();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore);
    }
}

三、测试

1、先后启动服务:

注册中心(eureka-server),

授权服务(oauth2-server),

网关服务(oauth2-gateway),

应用服务(auth2-client)

2、进行登录,输入授权类型,用户名,密码。并设置client的名称及密码,返回token:

登录成功后我们可以看到token的长度明显比上一章

这里面要说明的是需要注明授权类型,并输入客户端用户名及密码:

3、先后测试应用服务(oauth2-cliant)的三个接口/api/current、/api/hello、/api/query效果如下,与redis版一致

四、jwt的优缺点

基于session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。

jwt的优点:

1. 可扩展性好

应用程序分布式部署的情况下,session需要做多机数据共享,通常可以存在数据库或者redis里面。而jwt不需要。

2. 无状态

jwt不在服务端存储任何状态。RESTful API的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。用户的认证状态引入这种附加影响,这破坏了这一原则。另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。

 

jwt的缺点:

1. 安全性

由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。

2. 性能

jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage里面。并且用户在系统中的每一次http请求都会把jwt携带在Header里面,http请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用jwt的http请求比使用session的开销大得多。

3. 一次性

无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。

(1)无法废弃

通过上面jwt的验证机制可以看出来,一旦签发一个jwt,在到期之前就会始终有效,无法中途废弃。例如你在payload中存储了一些信息,当信息需要更新时,则重新签发一个jwt,但是由于旧的jwt还没过期,拿着这个旧的jwt依旧可以登录,那登录后服务端从jwt中拿到的信息就是过时的。为了解决这个问题,我们就需要在服务端部署额外的逻辑,例如设置一个黑名单,一旦签发了新的jwt,那么旧的就加入黑名单(比如存到redis里面),避免被再次使用。

(2)续签

如果你使用jwt做会话管理,传统的cookie续签方案一般都是框架自带的,session有效期30分钟,30分钟内如果有访问,有效期被刷新至30分钟。一样的道理,要改变jwt的有效时间,就要签发新的jwt。最简单的一种方式是每次请求刷新jwt,即每个http请求都返回一个新的jwt。这个方法不仅暴力不优雅,而且每次请求都要做jwt的加密解密,会带来性能问题。另一种方法是在redis中单独为每个jwt设置过期时间,每次访问时刷新jwt的过期时间。

五、源码地址

https://github.com/cc6688211/oauth2/tree/jwt

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
Spring Cloud OAuth2 JWT是一种基于Spring Cloud的身份验证和授权解决方案。OAuth2是一种开放标准,用于授权第三方应用程序访问用户资源的安全协议。JWT(JSON Web Token)是一种用于安全传输声明的开放标准。 Spring Cloud OAuth2 JWT通过使用OAuth2和JWT的结合,提供了一种安全的身份验证和授权机制。它使用JWT作为令牌来在用户和资源服务器之间进行认证授权JWT包含了加密的用户信息和访问权限,并以密钥进行签名来确保其真实性。 使用Spring Cloud OAuth2 JWT,我们可以实现以下功能: 1. 提供用户身份验证和授权:通过验证用户的凭证和生成JWT令牌来验证用户身份,并授权给他们访问受保护资源的权限。 2. 单点登录(SSO):用户只需登录一次,即可获得访问多个应用程序的令牌,并可以在不再需要登录的情况下访问这些应用程序。 3. 远程授权:通过OAuth2授权服务器,支持远程对受保护资源的访问授权,确保只有被授权的用户能够访问资源。 4. 令牌刷新:JWT令牌在一段时间后会过期,但可以使用刷新令牌来重新生成新的令牌,以延长访问权限。 总之,Spring Cloud OAuth2 JWT提供了一个安全、可靠的身份验证和授权解决方案,可以在分布式系统环境下实现用户身份验证和授权的功能。它结合了OAuth2和JWT标准,提供了一种方便、灵活的身份验证和授权机制,使得开发者能够更好地保护应用程序和用户的安全。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值