OAuth2.0实战使用JWT令牌认证

时间:2024-01-23 20:44:53

JWT分为三部分:头部、载荷、签名。如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE3MDU5OTQ0MzgsImF1dGhvcml0aWVzIjpbIlJPTEVfYWRtaW4iXSwianRpIjoiM2RiYjVkNGUtN2Q3My00ODI3LTlkOGYtMmI3OGVmMmVmZTExIiwiY2xpZW50X2lkIjoiYzEifQ.
eAVj9_lLpUC99jJbLwV2OwNrPvjGqoKgtHm5_jlgqBg

头部定义了JWT基本信息,如类型和签名

载荷包含了一些基本信息(签发时间、过期时间),另外还可以添加一些自定义的信息,比如用户的部分信息。

签名部分将前两个字符串用.连接后,使用头部定义的加密算法,利用密钥进行签名,并将签名信息附在最后。

OAuth2.0分为认证授权中心、资源服务,认证中心用于颁发令牌,资源服务解析令牌并且提供资源。

令牌配置

@Configuration
public class TokenConfig {

    /**
     * 对称密钥,资源服务器使用该密钥来验证
     */
    private final static String SIGNING_KEY = "uaa123";

    @Bean
    public TokenStore tokenStore(){
        //JWT令牌存储方案
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }

   /* @Bean
	 public TokenStore tokenStore() {
		 //使用内存存储令牌(普通令牌)
		 return new InMemoryTokenStore();
	 }*/

}

1、JwtAccessTokenConverter

令牌增强类,用于JWT令牌和OAuth身份进行转换

2、TokenStore

令牌的存储策略,这里使用的是JwtTokenStore,使用JWT的令牌生成方式,其实还有以下两个比较常用的方式

RedisTokenStore:将令牌存储到Redis中,此种方式相对于内存方式来说性能更好。

JdbcTokenStore:将令牌存储到数据库中,需要新建对应的表。

3、SIGNING_KEY

JWT签名的密钥,这里使用的是对称加密,资源服务也要使用相同的密钥进行校验和解析JWT令牌。

认证授权中心

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private AuthenticationManager authenticationManager;

    //客户端详情服务,也就是configure(ClientDetailsServiceConfigurer clients)方法
    @Autowired
    private ClientDetailsService clientDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //这里是第三方合作用户的客户id,密钥的配置
        //使用in-memory存储
        clients.inMemory()
                // 设置我们接受的客户端的编号
                .withClient("c1")
                // secret密码
                .secret(passwordEncoder().encode("123456"))
                // 认证模式:password模式
                .authorizedGrantTypes("password", "refresh_token")
                // token的过期时间
                .accessTokenValiditySeconds(1800)
                // 资源id
                .resourceIds("res1")
                //作用域  读写 针对资源的操作权限
                .scopes("all");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 令牌管理服务
     */
    @Bean
    public AuthorizationServerTokenServices tokenService() {

        DefaultTokenServices service = new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);
        service.setSupportRefreshToken(true);// 支持刷新
        service.setTokenStore(tokenStore);// 令牌存储
        //设置令牌增强,使用JwtAccessTokenConverter进行转换
        service.setTokenEnhancer(accessTokenConverter);
        // 令牌默认有效期2小时
        service.setAccessTokenValiditySeconds(7200); 
        // 刷新令牌默认有效期3天
        service.setRefreshTokenValiditySeconds(259200); 
        return service;
    }
    
    /**
    * 配置令牌访问的端点
    */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
                // 指定认证管理器
                .authenticationManager(authenticationManager)
                // 授权码模式需要
                .authorizationCodeServices(authorizationCodeServices())
                // 令牌管理服务
                .tokenServices(tokenService())
                // jwt格式Token
                .accessTokenConverter(accessTokenConverter)
                // 允许post提交
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    /**
     * 授权码服务器
     */
    @Bean   
    public AuthorizationCodeServices authorizationCodeServices() {
        // 授权码模式的授权码采用内存方式存储
        return new InMemoryAuthorizationCodeServices();
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 提供公有密匙的端点,如果你使用JWT令牌的话, 允许
                .tokenKeyAccess("permitAll()")
                // oauth/check_token:用于资源服务访问的令牌解析端点,允许
                .checkTokenAccess("permitAll()")
                // 表单认证,申请令牌
                .allowFormAuthenticationForClients();
    }

令牌管理服务,使用的是DefaultTokenService这个实现类,其中可以配置令牌相关的内容,如access_token、refresh_token的过期时间,默认时间分别为12小时、30天。

设置令牌增强,使用JWT方式生成令牌,如下:

service.setTokenEnhancer(accessTokenConverter);

web安全配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder.userDetailsService(myUserDetailsService());
    }

    @Bean
    public UserDetailsService myUserDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("admin").build());
        return userDetailsService;
    }
}

UserDetailsServices使用内存方式保存,创建一个用户。


OAuth2.0资源服务

@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    // 令牌存储策略
    private TokenStore tokenStore;

    // 授权服务的资源列表
    public static final String RESOURCE_ID = "res1";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
                // 资源 id
                .resourceId(RESOURCE_ID)
//                // 令牌服务
//                .tokenServices(tokenService())
                // 令牌服务
                .tokenStore(tokenStore)
                .stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests().antMatchers("/**")
                // 所有的访问,授权访问都要是all,和认证服务器的授权范围一一对应
                .access("#oauth2.hasScope('all')")
                //去掉防跨域攻击
                .and().csrf().disable()
                //session管理
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

资源id和令牌服务配置到ResourceServerSecurityConfiguer中。

由于使用了JWT这种透明令牌,令牌本身携带着部分用户信息,不需要远程调用认证中心的接口校验令牌。

测试

1、使用密码模式获取令牌

OAuth2.0实战使用JWT令牌认证_jwt

可以看到已经成功返回了JWT令牌。

2、携带令牌调用资源服务

OAuth2.0实战使用JWT令牌认证_jwt_02