Spring-Cloud之OAuth2的JWT保护-12
一、JWT:JSON Web Token ( JWT)是一种开放的标准( RFC 7519 ), JWT 定义了一种紧凑且自包含的标准,该标准旨在将各个主体的信息包装为 JSON 对象。主体信息是通过数字签名进行和验证的。常使用 HMAC算法或 RSA (公钥/私钥 非对称性 密〉算法对 JWT 进行签名,安全性很高。
1)特点:
(1)紧凑型( compact):由于是加密后的字符串,JWT数据体积非常小,可通过POST请求参数或 HTTP 请求头发送。另外,数据体积小意味着传输速度很快。
(2)自包含(self-contained):JWT 包含了主体的所有信息,所以避免了每个请求都需要Uaa 服务验证身份,降低了服务器的负载。
2)结构:包含3个部分,通过".(点)"分割。
(1)Header (头)。
(2)Payload (有效载荷〉。
(3)Signature (签名)。
大概样子如下:
解析后的样子:
二、JWT的应用场景。
1)认证:这是使用 JWT 最常见的场景。一旦用户登录成功获取 JWT 后,后续的每个请求将携带该 JWT 。该 JWT 包含了用户信息、权限点等信息,根据 JWT 含的信息,资源服务可以控制该 JWT 可以访问的资源范围。因为 JWT 开销很小,并且能够在不同的域中使用,单点登录是一个广泛使用 JWT 的场景。
2)信息交换:JWT 是在各方之间安全传输信息的一种方式,使用签名加密,安全性很高。另外,当使用 Header Payload算签名时,还可以验证内容是否被篡改。
三、JWT如何使用。
客户端通过提供用户名、密码向服务器请求获取JWT ,服务器判断用户名和密码正确无误之后,将用户信息和权限点经过加密以JWT形式返回给客户端。在以后的每次请求中获取到该 JWT 客户端都需要携带该JWT这样做的好处就是以后的请求都不需要通过 认证服务来判断该请求的用户以及该用户的权限。在微服务系统中,可以利用 JWT 实现单点登录。
四、具体的实现过程。(基本和上一章(Spring-Cloud之OAuth2开放授权-11)的代码一样,我这里只说核心修改的部分)
1、认证服务器修改部分
package com.cetc.config; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; @Configuration @EnableAuthorizationServer public class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter{ @Autowired private AuthDetailsService authDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private ClientDetailsService clientDetailsService; @Bean public ClientDetailsService clientDetailsService(HikariDataSource dataSource) { //使用数据库的配置方式 return new JdbcClientDetailsService(dataSource); } @Bean public TokenStore tokenStore() { //token也使用数据的方式,后面会将JWT的使用方式 return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean protected JwtAccessTokenConverter jwtAccessTokenConverter() { ClassPathResource resource = new ClassPathResource("jwt/jwt.jks"); KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, "auth_jwt".toCharArray()); JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("oauth2-jwt")); return jwtAccessTokenConverter; } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security //token获取方式 .tokenKeyAccess("permitAll()") //检测加入权限 .checkTokenAccess("isAuthenticated()") //允许表单认证 .allowFormAuthenticationForClients(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //这里就是具体的授权管理过程了 clients.withClientDetails(clientDetailsService); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints //这里使用的认证方式为security配置方式 .authenticationManager(authenticationManager) //提供get和post的认证方式 .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET) //这里一定要配置userDetailsService,不然刷新token会出错,refresh_token .userDetailsService(authDetailsService) .tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter()) //自定义认证页面 .pathMapping("/oauth/confirm_access", "/oauth/confirm_access"); } }
2、资源服务器
package com.cetc.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 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; import org.springframework.util.FileCopyUtils; import java.io.IOException; import java.util.Arrays; @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{ @Override public void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .anyRequest().authenticated(); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() throws IOException { JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); ClassPathResource resource = new ClassPathResource("jwt/jwt.cert"); jwtAccessTokenConverter.setVerifierKey(new String(FileCopyUtils.copyToByteArray(resource.getInputStream()))); return jwtAccessTokenConverter; } @Bean public TokenStore tokenStore() throws IOException { return new JwtTokenStore(jwtAccessTokenConverter()); } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenStore(tokenStore()); } }
3、第三方或者SSO客户端,不做修改。因为,JWT令牌是认证服务器给出的。解析JWT令牌的为资源服务器,所以SSO客户端只需要按原来的方式进行调用就可以了。
五、jks文件生成,上面使用jks文件为文件token密钥,所以我们在使用的时候需要自己加入。
1)生成jks文件
keytool -genkeypair -alias oauth2-jwt -keyalg RSA -keypass auth_jwt -storepass auth_jwt -keystore jwt.jks
2)获取公钥,这里最好在linux服务器上面进行,本地安装OpenSSL过于麻烦
keytool -list -rfc --keystore jwt.jks | openssl x509 -inform pem -pubkey
赋值公钥到jwt.cert文件中
3)按照配置的指定路径放入jwt.jks和jwt.cert
六、测试资源服务器访问,启动Eureka-Server、Eureka-Client、Auth-Server-Jwt、Auth-Resource-Jwt端口为8670、8673、8697、8698.
1)添加客户端到数据库
2)获取令牌:
(1)获取授权码
oauth/authorize?response_type=code&client_id=&redirect_uri=
(2)获取令牌
oauth/token?client_id=&client_secret=&grant_type=authorization_code&redirect_uri=&code=
3)携带令牌访问资源服务器
七、JWT的基本使用就差不多这个样子了,JWT的目的是在较少认证服务器的访问。那么这个也存在问题就是如果用户权限修改或者其他部分修改,那么在令牌的使用就不是最新的,这里就会导致权限错误问题。当然这个问题可以通过配置网关,在网关处缓存,如果存在修改这清楚缓存,要求重新登录。
八、本编源码:https://github.com/lilin409546297/spring-cloud/tree/master/oauth2-jwt