JWT:json web token 是一种无状态的权限认证方式,token信息不需要存到数据库,下游服务通过网关拿到token后,不在请求认证服务器做验证,减少了一次交互请求;一般用于前后端分离,时效性比较快 的权限校验,防止恶意攻击者通过抓包等手段拿到token之后进行恶意请求,当然采用Https的方式也可以避免,jwt 模式获取 token 跟前面的,客户端,密码,授权码模式是一样的,只是需要配置秘钥。下面我们分析一下JWT的整个流程:
1、jar 包引入:JWT模式依赖此jar
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
2、创建权限效验的服务端:micro-jwt,创建JWT模式的配置类:
@Configuration
@EnableAuthorizationServer //权限效验的注解
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
private UserServiceDetail userServiceDetail;
@Autowired
private DataSource dataSource;
/* @Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 将客户端的信息存储在内存中
clients.inMemory()
// 配置一个客户端
.withClient("micro-order")
.secret("123456")
// 配置客户端的域
.scopes("service")
// 配置验证类型为refresh_token和password
.authorizedGrantTypes("refresh_token", "password")
// 配置token的过期时间为1h
.accessTokenValiditySeconds(30);
}*/
@Bean // 声明 ClientDetails实现
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
@Override //作用是用于token的解密和加密的
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 配置token的存储方式为JwtTokenStore
endpoints.tokenStore(tokenStore())
// 配置用于JWT私钥加密的增强器
.tokenEnhancer(jwtTokenEnhancer())
// 配置安全认证管理
.authenticationManager(authenticationManager)
.userDetailsService(userServiceDetail);
}
@Bean//JWT模式处理token的类,点击JwtTokenStore 后可以看到,token 没有做保存操作,是无状态的。
public TokenStore tokenStore() {
return new JwtTokenStore(jwtTokenEnhancer());
}
@Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
// 配置jks文件
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("micro-jwt.jks"), "123456".toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("micro-jwt"));
return converter;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
// 对获取Token的请求不再拦截
oauthServer.tokenKeyAccess("permitAll()")
// 验证获取Token的验证信息
.checkTokenAccess("isAuthenticated()");
}
2、创建用户效验的类:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceDetail userServiceDetail;
@Override
public @Bean
AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/*
* access(String) 如果给定的SpEL表达式计算结果为true,就允许访问
anonymous() 允许匿名用户访问
authenticated() 允许认证的用户进行访问
denyAll() 无条件拒绝所有访问
fullyAuthenticated() 如果用户是完整认证的话(不是通过Remember-me功能认证的),就允许访问
hasAuthority(String) 如果用户具备给定权限的话就允许访问
hasAnyAuthority(String…)如果用户具备给定权限中的某一个的话,就允许访问
hasRole(String) 如果用户具备给定角色(用户组)的话,就允许访问/
hasAnyRole(String…) 如果用户具有给定角色(用户组)中的一个的话,允许访问.
hasIpAddress(String 如果请求来自给定ip地址的话,就允许访问.
not() 对其他访问结果求反.
permitAll() 无条件允许访问
rememberMe() 如果用户是通过Remember-me功能认证的,就允许访问
*
* */
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();//关闭CSRF
// .exceptionHandling()
// .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
// .and()
// .authorizeRequests()
// .antMatchers("/oauth/**").permitAll()
.antMatchers("/**").authenticated()
// .and()
// .httpBasic();
http.requestMatchers().anyRequest()
.and()
.authorizeRequests()
.antMatchers("/oauth/**").permitAll();
}
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public static NoOpPasswordEncoder noOpPasswordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
@Override //获取用户进行校验
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceDetail).passwordEncoder(passwordEncoder());
}
}
3、生成秘钥文件 :服务端加密RSA用的 ,这和分布式配置加密的流程是一样的,前面已经讲过,这里简单过一下。
cd 到 jdk 的 bin 目录执行该指令,会在 bin 目录下生成 micro-jwt.jks 文件,把该文件放到认 证服务工程里面的 resources 目录下:
keytool -genkeypair -alias micro-jwt
-validity 3650
-keyalg RSA
-dname "CN=jwt,OU=jtw,O=jwt,L=zurich,S=zurich, C=CH"
-keypass 123456
-keystore micro-jwt.jks
-storepass 123456
4、生成公钥 :客户端用来解密的
keytool -list -rfc --keystore micro-jwt.jks | openssl x509 -inform pem -pubkey
把生成的公钥内容放到 public.cert 文件中,内容如下:
把公钥文件放到客户端的 resources 目录下。
5、创建客户端微服务并进行解密代码配置:
@Configuration
public class JwtConfig {
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Bean
@Qualifier("tokenStore")
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Bean
public JwtAccessTokenConverter jwtTokenEnhancer() {
// 用作JWT转换器
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;
}
}
6、解密后的访问代码设置:保存token 并设置接口访问权限
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/login","/user/register").permitAll()
.antMatchers("/**").authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
}
6、客户端配置文件配置:
security.oauth2.client.clientId=pc
security.oauth2.client.client-secret=123456
#启动时访问,验证是否通畅
security.oauth2.resource.jwt.key-uri=http://localhost:7070/jwt/oauth/token_key
5、密码模式获取 jwt token:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODQ5ODc0ODMsInVzZXJfbmFtZSI6ImphbWVzIiwiYXV0aG9yaXRpZXMiOl
siUk9MRV9BRE1JTiJdLCJqdGkiOiIxNTAwNDcwOC1kZjVlLTQ1NzMtODExZi0xOWExMDA2ZjI3NmMiLCJjbGllbnRfaWQiOiJwYyIsInNjb
3BlIjpbImFsbCJdfQ.HoUFEnGVG2FLCOvtIK02RmZGovpWUvcsH0TO-jyes1rj1ZqT_GeQ5uU0LMHIddZ0nYOBXCYJgR5vQkC-OOT64LpP0
ypLbp9mPbEtYrzl3iT91cqpb_gcBFDZR7Wzi5eW9_B7BtfF9BvgEp51KicnpYgsN7yb4t5OXcn1Ves4uYSeNG96N9Yt0bgiA34-r8cZfA8_
UePMY1sZRS3jgmBt--TcjXqJy-GRcL6_ilGgbwQyt-znOqxOxUg7glm9Zixbf27FmPkB0mqJ2qsNqqLz3Cc_RMTi24myRMVW6vSlx789s6t
Eh74lIwdEAzO73q_HPAvmOJO0RQNow9LhXFve6g
6、打开https://jwt.io 进行解密验证:
Jwt 的 token 信息分成三个部分,用“.”号分割的。
第一步部分:头信息,通过 base64 加密生成
第二部分:有效载荷,通过 base64 加密生成
第三部分:签名,根据头信息中的加密算法通过,RSA(base64(头信息) + “.” + base64(有效载
荷))生成的第三部分内容
可以到 jwt 的官网看看这三部分信息的具体内容:jwt 官网 jwt.io 如上图
7、启动相关客户端 ,加上token访问客户端接口:
8、客户端接口访问通了,并且micro-jwt 服务端没有日志,说明下游客户端访问时没有去鉴权服务端请求验证,因此减少了一次请求,优化了性能。
9、为什么客户端不请求鉴权服务,就直接进行业务操作了你呢?
这就是因为前面生成的一对私钥和公钥,公钥依据私钥生成的,鉴权服务器生成token 时 通过私钥加密,生成字符串形式的密文token;客户端访问时拿着此token ,流转到下游服务时,通过公钥解密,只要解密比对成功,证明了这个token是正常的,没有经过恶意篡改,不需要再去鉴权服务器请求认证了,可以放行。小伙伴理解了吗!!!!
这就是微服务获取token的JWT解决方案,有不明白的地方欢迎留言!到这里springcloud 的使用已经分析完成,从下一篇开始我们从源码的角度依次分析各组件的原理,敬请期待!