Spring Security Oauth2.0 知识点总结

Oauth2.0部分

  1. AuthorizationServerConfigurerAdapter 授权服务器核心配置类

  2. @EnableAuthorizationServer 启用授权服务器

  3. TokenStore 令牌存储

  4. AuthorizationServerTokenServices 令牌服务

  5. 为什么授权码模式要通过重定向来的到授权码而不是直接返回授权码

    请求授权服务器以后,授权服务器返回一个是否授权的页面,用户点击同意以后才能获取授权码,这个是否同意授权的页面在某些情景是必须的,并且只能是客户端来调起,毕竟权限是客户授予,不能是服务器端调起。也就是授权操作应该由位于客户端的客户发起,并且同意授权,这时候如果直接返回,那么客户端就会直接获取到授权码,服务器拿到的就是二手数据,只能是客户端上报客户服务器,不可靠。所以只能通过重定向到客户服务器,客户服务器就能拿到一手的授权码,然后发起获取token的的操作。

  6. bearer 是什么
    bearer一种access_token类型的类型,适用于https类型的请求,与之对应的还有一种叫做MAC的access_token类型,适用月http协议的请求。和bearer相比MAC 类型的token还要携带时间戳,nonce,以及在客户端计算得到的mac值等信息,并通过这些额外的信息来保证传输的可靠性

  7. 刷新令牌

  8. 资源服务器 使用security oauth2授权模式
    第1步:引入依赖

         <!--security-jwt-->
         <dependency>
             <groupId>org.springframework.security</groupId>
             <artifactId>spring-security-jwt</artifactId>
             <version>1.1.1.RELEASE</version>
         </dependency>
         <!--security oauth2.0-->
         <dependency>
             <groupId>org.springframework.security.oauth.boot</groupId>
             <artifactId>spring-security-oauth2-autoconfigure</artifactId>
             <version>2.5.5</version>
         </dependency>
    

    第2步:继承AuthorizationServerConfigurerAdapter,并且重写对应的三个配置方法

/**
 *  oauth 授权服务配置
 *
 * @Author ZHANGYUKUN
 * @Date 2022/11/13
 */
@EnableAuthorizationServer
@Configuration
public class MyAuthorizationServerConfigurerAdapter extends AuthorizationServerConfigurerAdapter {

    /**
     * 令牌服务
     */
    @Autowired
    AuthorizationServerTokenServices tokenService;

    /**
     * 授权码服务
     */
    @Autowired
    public AuthorizationCodeServices authorizationCodeServices;

    /**
     * 认证管理器
     */
    @Autowired
    AuthenticationManager authenticationManager2;

    /**
     * 安全配置
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll");
        security.checkTokenAccess("permitAll");
        security.allowFormAuthenticationForClients();
    }

    /**
     * 客户信息
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("c1")       //客户端id
                .secret(  new BCryptPasswordEncoder().encode("secret")) //客户端的账号和密钥
                .resourceIds("res1") //客户端的资源地址
                .authorizedGrantTypes(
                    "authorization_code",
                    "password",
                    "client_credentials",
                    "implicit",
                    "refresh_token"
                )//对应这可以使用的授权模式
                .scopes("all") //什么意思
                .autoApprove(false)  //授权页面是否自动同意,自动同意就不会出现让选着的界面,敏感信息都不应该自动同意
                .redirectUris("http://www.baidu.com");//客户端重定向地址
    }


    /**
     * 提供授权的enppoint
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager( authenticationManager2 ) //用户名密码模式需要的认证管理者
                 .authorizationCodeServices( authorizationCodeServices )//授权码模式需要的授权码服务
                 .tokenServices( tokenService ) //令牌服务,管理令牌的存储,有效期
                 .allowedTokenEndpointRequestMethods(HttpMethod.POST); //???

    }






}

token服务

/**
 * 令牌服务配置
 *
 * @Author ZHANGYUKUN
 * @Date 2022/11/13
 */
@Configuration
public class TokenServiceConfig {


    @Autowired
    TokenStore tokenStore;

    @Autowired
    ClientDetailsService clientDetailsService;

    /**
     * 令牌存储对象
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        return  new InMemoryTokenStore();
    }


    /**
     * 令牌服务
     *
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService( clientDetailsService );
        services.setTokenStore(tokenStore);
        services.setSupportRefreshToken( true );
        services.setAccessTokenValiditySeconds(7200);
        services.setRefreshTokenValiditySeconds(259200);
        return services;
    }

}

授权码模式需要用的的授权码服务的配置

/**
 *  授权码服务配置
 *
 * @Author ZHANGYUKUN
 * @Date 2022/11/13
 */
@Configuration
public class AuthorizationCodeServiceConfig {

    /**
     * 授权码服务
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new InMemoryAuthorizationCodeServices();
    }

}

用户名密码模式需要用到的认证服务(WebSecurityConfigurerAdapter中已经初始化了认证管理者,我们直接去这里面拿到放入spring容器中就行了)

    /**
     * 认证管理器
     * @return
     */
    @Bean
    public AuthenticationManager authenticationManager2() throws Exception {
        return  this.authenticationManager();
    }

配置完成以后就能使用使用授权服务了,需要注意的是使用@EnableAuthorizationServer直接开启授权服务器

  1. 获取授权的方式

授权码模式,账号密码模式,客户端模式,匿名模式
        对应 ClientDetailsServiceConfigurer的authorizedGrantTypes方法设置的授予权限方式,只有这里面配置的授权模式才能用。

  1. 授权码模式(最安全,但是也是复杂的模式)
    第1步:前端get请求向授权服务器请求授权,oauth/authorize?client_id=客户端ID&response_type=code&scope=all&redirect_uri=重定向地址

    第2步:授权服务器返回前端是否授权的页面,用户在这个页面可以同意或者拒绝授权

    第3步:用户点击同意授权,然后授权服务器从定向到指定地址,并且重定向地址后面带上授权码
    授权服务器重定向地址和参数格式类似:重定向地址?code=授权码

    第4步:这时候重定向地址一般是客户端对应的三方服务器的一个接口,这接口里面会取出授权码,然后想授权服务器请求token,拿到token以后就可以通过token获取客户在资源服务器中的数据了。

  2. 授权码简化模式
    主要面向没有服务端的三方引用,授权直接拿到的就是token,而不是授权码
    请求格式如下:http://127.0.0.1/te/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=重定向地址,区别在于response_type是token,而不是code
    简化模式重定向地址里面直接带有token,而不是code:重定向地址/#access_token=wESGfmOuPinUMfl4G2HY_HII-HA&token_type=bearer&expires_in=2526
    指的注意的是简化模式没用用到客户端密钥,为什么可以不用密钥?

  3. 账号密码模式(我们自己的客户端用这种,三方客户端不适合)
    这种模式,用户会在客户端输入密码,也就是说客户端是可以获取并记录用户密码的,只能用于我们自己内部开发的客户端

  4. 客户端认证模式(完全信赖这个客户端的时候才能用)

  5. 资源服务器配置的适用授权服务器的token
    继承ResourceServerConfigurerAdapter并重写关于资源安全配置和http安全配置的2个方法

/**
 * 资源服务器配置
 *
 * @Author ZHANGYUKUN
 * @Date 2022/11/13
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true)
public class MyResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res1")
                .tokenServices(tokenServoces());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
               // .antMatchers("/res/**").access("#oauth2.hasScope('all')")
                .anyRequest().permitAll() //其他接口都不拦截
                .and().csrf().disable()
                .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS );
    }


    /**
     * 这个 http 安全配置会覆盖 WebSecurityConfigurerAdapter 里面的 http 安全配置
     * @param http
     * @throws Exception
     */
    private ResourceServerTokenServices tokenServoces() {
        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        remoteTokenServices.setCheckTokenEndpointUrl("http://127.0.0.1/te/oauth/check_token");
        remoteTokenServices.setClientId("c1");
        remoteTokenServices.setClientSecret("secret");
        return remoteTokenServices;
    }
}   

需要注意的是 @EnableResourceServer 注解回去校验token,
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true) 注解才会是方法上面的验证权限注解生效,两个注解都不能少

  1. oauth2协议规定的鞋带token格式

    hearderName为Authorization,headerValue为bearer token的请求头

  2. 资源ID有什么用
    在资源服务器配置的时候,我们给这个资源服务器指定一个资源ID

        @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res1")//资源服务器的ID,一般可以设置成appName,我们通过资源ID限制客户访问部分服务器
                //.tokenServices(tokenServoces());//远程认证token需要
                .tokenStore( tokenStore );//本地认证token需要
                //.stateless(true);
    }
    

    在授权服务器上,给客户指定资源Ids,用于限制客户可以可以访问的资源服务器

    /**
     * 客户信息
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {


        clients.withClientDetails( new MyClientDetailsService() );

        /*clients.inMemory()
                .withClient("c1")       //客户端id
                .secret(  new BCryptPasswordEncoder().encode("secret")) //客户端的账号和密钥
                .resourceIds("res1","res1" ) //客户端的资源ID
                .authorizedGrantTypes(
                    "authorization_code",
                    "password",
                    "client_credentials",
                    "implicit",
                    "refresh_token"
                )//对应这可以使用的授权模式
                .scopes("all") //什么意思
                .autoApprove(false)  //授权页面是否自动同意,自动同意就不会出现让选着的界面,敏感信息都不应该自动同意
                .redirectUris("http://www.baidu.com");//客户端重定向地址*/
    }

上面设置的 .resourceIds("res1","res1" ) //客户端的资源ID 就是客户可以使用的资源服务,我们可以这样理解,用户拥有的权限是用户能访问的最多资源,但是我们和三方客户合作的时候不会全都允许他们范围所有资源服务器,这时候通过资源ID来对权限分组,和客户签约那些资源服务器就给与那些资源Ids。

  1. scopes 什么意思
    scope 是权限的分级,可以理解成权限的细化,一个权限可以分成读写或者更多的子权限
    如下面的配置,我们要求/res/**,都有all的范围,也可以定义成read,write等等

    /**
     * 这个 http 安全配置会覆盖 WebSecurityConfigurerAdapter 里面的 http 安全配置
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/res/**").access("#oauth2.hasScope('all')")
                .and().csrf().disable()
                .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS );
    }
    
  2. .allowedTokenEndpointRequestMethods(HttpMethod.POST); 是什么意思?
    限制只能post的方式获取token

  3. JwtAccessTokenConverter 作用
    提供token和map之间的相互装换,接口定义如下

    public interface AccessTokenConverter {
    String AUD = "aud";
    String CLIENT_ID = "client_id";
    String EXP = "exp";
    String JTI = "jti";
    String GRANT_TYPE = "grant_type";
    String ATI = "ati";
    String SCOPE = "scope";
    String AUTHORITIES = "authorities";
    
    Map<String, ?> convertAccessToken(OAuth2AccessToken var1, OAuth2Authentication var2);
    
    OAuth2AccessToken extractAccessToken(String var1, Map<String, ?> var2);
    
    OAuth2Authentication extractAuthentication(Map<String, ?> var1);
    }
    
  4. 使用jwt 格式的token
    在授权服务器端配置jwtTokenStore

        /**
     * jwt令牌存储对象
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter);
        return  new InMemoryTokenStore();
    }

    /**
     *jwt token 装换器
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey(SIGNKEY);
        return  jwtAccessTokenConverter;
    }

在授权服务器端tokenService 上面设置增强

        /**
     * 令牌服务
     *
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService( clientDetailsService );
        services.setTokenStore(tokenStore);
        services.setSupportRefreshToken( true );
        services.setAccessTokenValiditySeconds(7200);
        services.setRefreshTokenValiditySeconds(259200);

        //使用jwttoken以后设置token增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList( jwtAccessTokenConverter ) );
        services.setTokenEnhancer( tokenEnhancerChain );

        return services;
    }

在资源服务器上设置和认证服务相同的密钥的tokenStore

    /**
     * 令牌存储(jwt 格式token的时候不,用它本地校验token)
     */
    @Autowired
    TokenStore tokenStore;



    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res1")
                //.tokenServices(tokenServoces());//远程认证token需要
                .tokenStore( tokenStore );//本地认证token需要
                //.stateless(true);
    }

jwt 格式的token 非常适合自己的服务器集群授权,不适合三方服务器授权,因为它可以通过签名验证token的合法性,节省了验证token的网络请求,但是在三方授权的系统只,不可能获取到授权服务器的token生成密钥。

  1. 关于 客户 用户 资源ID 资源服务器 授权服务 权限 scope 的解释
    客户:指的三方机构

    用户:就是程序的使用人员
    资源服务器:提供资源的服务器器
    资源ID:资源服务器的ID,一个资源ID对应着一堆权限
    授权服务器:授予用户和客户访问资源服务器里面资源权限的服务器

    权限:资源服务器里面的一个接口的使用权限
    scope:可以理解成权限范围,或者说一个权限可以分成多个,比如读写

  2. 关于分布式集群token的校验位置
    第一种:是在同一在网关校验,然后别的节点都在网关的内网,不在做权限,有问题网关就返回了,优点校验逻辑统一,缺点,节点没有权限认证,不能对外网开发,并且如果校验token逻辑复杂,有网络请求,会加重网关负载。

    第二种:直接在资源服务器上网关只做转发,校验由资源服务器来做,优点,校验压力分散,各节点可以对外提供服务。缺点,校验逻辑需要每个资源服务器都有,即便他们是完全一样的。

  3. 资源服务器和授权服务器不应该是一个程序,如果只有一个程序那么其实不适合oatuh2的协议,直接用security就行了。甚至如果我们自己的服务器集群也不需要auth2协议,直接使用security+jwt格式的token就行了。

posted on 2022-11-12 23:53  zhangyukun  阅读(517)  评论(0编辑  收藏  举报

导航