OAuth2.0-2jwt令牌

JWT令牌

解决了之前普通令牌每次都要远程校验令牌带来得网络消耗;(有网友说可以将令牌验证从认证服务器上放到各个资源服务器上,不知是否可行?)

 

JWT令牌的优点:
  1、jwt基于json,非常方便解析。
  2、可以在令牌中自定义丰富的内容,易扩展。
  3、通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
  4、资源服务使用JWT可不依赖认证服务即可完成授权。

  缺点:
  1、JWT令牌较长,占存储空间比较大。

  1.1 令牌结构
    通过学习JWT令牌结构为自定义jwt令牌打好基础。
    JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz

  Header
  头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA) 一个例子如下:
  下边是Header部分的内容:

{
    "alg": "HS256",
    "typ": "JWT"
}

  将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。

Payload
  第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
  此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
  最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。

{
    "sub": "1234567890",
    "name": "456",
    "admin": true
}

Signature
  第三部分是签名,此部分用于防止jwt内容被篡改。
  这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
  一个例子:

HMACSHA256(
    base64UrlEncode(header) + "." +base64UrlEncode(payload),secret
)

base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的密钥。

 有网友说:实际投产中,使用redis好一点,如果客户端过多,jwt就会很臃肿?

 

 

 

 

 

 1.授权服务AuthorizationServerConfig

@Configuration
//开启oauth2,auth server模式
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //client的id和密码
                .withClient("client1")
                .secret(passwordEncoder.encode("123123"))
                //给client一个id,这个在client的配置里要用的
                .resourceIds("resource1")
                //允许的申请token的方式,测试用例在test项目里都有.
                //authorization_code授权码模式,这个是标准模式
                //implicit简单模式,这个主要是给无后台的纯前端项目用的
                //password密码模式,直接拿用户的账号密码授权,不安全
                //client_credentials客户端模式,用clientid和密码授权,和用户无关的授权方式
                //refresh_token使用有效的refresh_token去重新生成一个token,之前的会失效
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
                //授权的范围,每个resource会设置自己的范围.
                .scopes("scope1", "scope2")
                //这个是设置要不要弹出确认授权页面的.
                .autoApprove(false)
                //这个相当于是client的域名,重定向给code的时候会跳转这个域名
                .redirectUris("http://www.baidu.com");
    }
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private JwtAccessTokenConverter tokenConverter;
    //配置token管理服务
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        defaultTokenServices.setSupportRefreshToken(true);
        //配置token的存储方法
        defaultTokenServices.setTokenStore(tokenStore);
        defaultTokenServices.setAccessTokenValiditySeconds(300);
        defaultTokenServices.setRefreshTokenValiditySeconds(1500);
        //配置token增强,把一般token转换为jwt token
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenConverter));
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
        return defaultTokenServices;
    }
    //密码模式才需要配置,认证管理器
    @Autowired
    private AuthenticationManager authenticationManager;

    //把上面的各个组件组合在一起
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)//认证管理器
                .authorizationCodeServices(new InMemoryAuthorizationCodeServices())//授权码管理
                .tokenServices(tokenServices())//token管理
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }
    //配置哪些接口可以被访问
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")///oauth/token_key公开
                .checkTokenAccess("permitAll()")///oauth/check_token公开
                .allowFormAuthenticationForClients();//允许表单认证
    }
}
TokenConfig
@Configuration
public class TokenConfig {

    //配置如何把普通token转换成jwt token
    @Bean
    public JwtAccessTokenConverter tokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

        //使用对称秘钥加密token,resource那边会用这个秘钥校验token
        converter.setSigningKey("uaa123");
        return converter;
    }

    //配置token的存储方法 
    @Bean
    public TokenStore tokenStore() {
        //把用户信息都存储在token当中,相当于存储在客户端,性能好很多
        return new JwtTokenStore(tokenConverter());
    }
}
WebSecurityConfig
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

    //密码模式才需要配置,认证管理器
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .anyRequest().permitAll()

                .and()
                .formLogin()

                .and()
                .logout();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        /**
         * 基于内存创建用户
         */
        InMemoryUserDetailsManager manager=new InMemoryUserDetailsManager();

        manager.createUser(User.withUsername("zhangsan").password(passwordEncoder().encode("123")).authorities("admin").build());
        manager.createUser(User.withUsername("lisi").password(passwordEncoder().encode("123")).authorities("user").build());
        return manager;
    }
}

 

2.资源服务ResourceServerConfig

@Configuration
//开启oauth2,reousrce server模式
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                //设置我这个resource的id, 这个在auth中配置, 这里必须照抄
                .resourceId("resource1")
                .tokenStore(tokenStore)//原来是 .tokenServices(tokenServices())去调用远程验证,现在只需要自己验证自己即可
                //这个貌似是配置要不要把token信息记录在session中
                .stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()

                //本项目所需要的授权范围,这个scope是写在auth服务的配置里的
                .antMatchers("/**").access("#oauth2.hasScope('scope1')")

                .and()

                //这个貌似是配置要不要把token信息记录在session中
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}
TokenConfig
@Configuration
public class TokenConfig {

    //配置如何把普通token转换成jwt token
    @Bean
    public JwtAccessTokenConverter tokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

        //使用对称秘钥加密token,resource那边会用这个秘钥校验token
        converter.setSigningKey("uaa123");//密钥必须跟授权服务那边一样!
        return converter;
    }

    //配置token的存储方法
    @Bean
    public TokenStore tokenStore() {
        //把用户信息都存储在token当中,相当于存储在客户端,性能好很多
        return new JwtTokenStore(tokenConverter());
    }
}
WebSecurityConfig
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .anyRequest().permitAll();
    }
}

验证方式:参考上一章

//授权码模式
//浏览器访问
http://127.0.0.1:9011/oauth/authorize?client_id=client1&response_type=code&scope=scope1&redirect_uri=http://www.baidu.com

 

 带令牌访问资源:

 

 

 无需远程验证,直接令牌自己根据对称加解密 即可获取用户令牌内得权限信息

posted @ 2020-05-28 13:23  valar-dohaeris  阅读(595)  评论(0编辑  收藏  举报