springcloud 微服务权限校验JWT模式获取 token 实战(十二)

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 的使用已经分析完成,从下一篇开始我们从源码的角度依次分析各组件的原理,敬请期待!

猜你喜欢

转载自blog.csdn.net/nandao158/article/details/108417629