zoukankan      html  css  js  c++  java
  • Spring Security OAuth2:认证服务器策略配置

    接着前一篇博客的代码:https://www.cnblogs.com/wwjj4811/p/14503898.html

    刷新令牌

    如果用户访问的时候,客户端的"访问令牌"已经过期,则需要使用"更新令牌"申请一个新的访问令牌。

    客户端发出更新令牌的HTTP请求,包含以下参数:

    • grant_type:表示使用的授权模式,此处的值固定为refresh_token,必选项。
    • refresh_token:表示早前收到的更新令牌,必选项。
    • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次相同

    注意: 刷新令牌只在授权码模式和密码模式中才有, 对应的指定这两种模式时, 类型加上refresh_token即可

    UserDetailsService实现

    创建 com.wj.oauth2.server.service.CustomUserDetailsService 实现 UserDetailsService 接口

    @Component
    public class CustomUserDetailsService implements UserDetailsService {
        @Autowired
        private PasswordEncoder passwordEncoder;
        //这里写死的,也可以从数据库查询
        @Override
        public UserDetails loadUserByUsername(String u) throws UsernameNotFoundException {
            return new User("admin", passwordEncoder.encode("1234"),
                    AuthorityUtils.commaSeparatedStringToAuthorityList("product"));
        }
    }
    

    配置SpringSecurityConfig

    @EnableWebSecurity
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Autowired
        private CustomUserDetailsService customUserDetailsService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(customUserDetailsService);
        }
    
        /**
         * password密码模式需要使用此认证管理器
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    

    配置AuthorizationServerConfig

    @Configuration
    @EnableAuthorizationServer//开启认证服务器功能
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private CustomUserDetailsService customUserDetailsService;
    
        /**  配置被允许访问此认证服务器的客户端详情信息
         * 方式1:内存方式管理
         * 方式2:数据库管理
         * localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 使用内存方式
            clients.inMemory()
                    // 客户端id
                    .withClient("wj-pc")
            // 客户端密码,要加密,不然一直要求登录, 获取不到令牌, 而且一定不能被泄露
            .secret(passwordEncoder.encode("wj-secret"))
            // 资源id, 如商品资源
            .resourceIds("product-server")
            // 授权类型, 可同时支持多种授权类型
            .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
            // 授权范围标识,哪部分资源可访问(all是标识,不是代表所有)
            .scopes("all")
            // false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码,
            .autoApprove(false)
            .redirectUris("http://www.baidu.com/");// 客户端回调地址
        }
    
        /**
         * 重写父类的方法
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //密码模式需要设置此认证管理器
            endpoints.authenticationManager(authenticationManager);
            // 刷新令牌获取新令牌时需要
            endpoints.userDetailsService(customUserDetailsService);
        }
    }
    

    测试

    以密码认证模式为例:先获取到refesh_token

    image-20210309103030517

    测试刷新token请求:http://localhost:8090/auth/oauth/token

    image-20210309103513012

    grant_type填refresh_token,refresh_token填上一步获取到的refresh_token

    image-20210309103638860

    发送请求后,access_token就被刷新了

    令牌管理策略

    默认情况下,令牌通过 randomUUID 产生32位随机数的来进行填充的,而产生的令牌默认是存储在内存中。

    内存存储采用的是TokenStore接口的默认实现类InMemoryTokenStore , 开发时方便调试,适用单机版。

    RedisTokenStore将令牌存储到 Redis 非关系型数据库中,适用于并发高的服务。

    JdbcTokenStore基于 JDBC 将令牌存储到关系型数据库中,可以在不同的服务器之间共享令牌。

    JwtTokenStore(JSON Web Token)将用户信息直接编码到令牌中,这样后端可以不用存储它,前端拿到令牌可以直接解析出用户信息

    redis存储令牌

    pom中需要引入redis的starter,上一篇博客已经引入过了。

    新增TokenConfig 配置类:向容器中添加RedisTokenStore

    @Configuration
    public class TokenConfig {
    
        @Autowired
        RedisConnectionFactory redisConnectionFactory;
    
        @Bean
        public TokenStore tokenStore(){
            return new RedisTokenStore(redisConnectionFactory);
        }
    }
    

    修改AuthorizationServerConfig,令牌管理策略添加到端点:

        @Autowired
        private TokenStore tokenStore;
    
        /**
         * 重写父类的方法
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //密码模式需要设置此认证管理器
            endpoints.authenticationManager(authenticationManager);
            // 刷新令牌获取新令牌时需要
            endpoints.userDetailsService(customUserDetailsService);
            //设置token存储策略
            endpoints.tokenStore(tokenStore);
        }
    

    yml配置redis连接信息:

    spring:
      redis:
        port: 6379
        host: 192.168.1.43
    

    重启应用再请求令牌,发现令牌已经存储在redis中了:

    image-20210309105220289

    jdbc存储令牌

    oauth2相关sql:mysq5.7

    create table oauth_client_details (
      client_id VARCHAR(256) PRIMARY KEY,
      resource_ids VARCHAR(256),
      client_secret VARCHAR(256),
      scope VARCHAR(256),
      authorized_grant_types VARCHAR(256),
      web_server_redirect_uri VARCHAR(256),
      authorities VARCHAR(256),
      access_token_validity INTEGER,
      refresh_token_validity INTEGER,
      additional_information VARCHAR(4096),
      autoapprove VARCHAR(256)
    );
    
    create table oauth_client_token (
      token_id VARCHAR(256),
      token BLOB,
      authentication_id VARCHAR(256) PRIMARY KEY,
      user_name VARCHAR(256),
      client_id VARCHAR(256)
    );
    
    create table oauth_access_token (
      token_id VARCHAR(256),
      token BLOB,
      authentication_id VARCHAR(256) PRIMARY KEY,
      user_name VARCHAR(256),
      client_id VARCHAR(256),
      authentication BLOB,
      refresh_token VARCHAR(256)
    );
    
    create table oauth_refresh_token (
      token_id VARCHAR(256),
      token BLOB,
      authentication BLOB
    );
    
    create table oauth_code (
      code VARCHAR(256), authentication BLOB
    );
    
    create table oauth_approvals (
    	userId VARCHAR(256),
    	clientId VARCHAR(256),
    	scope VARCHAR(256),
    	status VARCHAR(10),
    	expiresAt TIMESTAMP,
    	lastModifiedAt TIMESTAMP
    );
    
    -- customized oauth_client_details table
    create table ClientDetails (
      appId VARCHAR(256) PRIMARY KEY,
      resourceIds VARCHAR(256),
      appSecret VARCHAR(256),
      scope VARCHAR(256),
      grantTypes VARCHAR(256),
      redirectUrl VARCHAR(256),
      authorities VARCHAR(256),
      access_token_validity INTEGER,
      refresh_token_validity INTEGER,
      additionalInformation VARCHAR(4096),
      autoApproveScopes VARCHAR(256)
    );
    

    修改TokenConfig

    @Configuration
    public class TokenConfig {
    
    /*    @Autowired
        RedisConnectionFactory redisConnectionFactory;
    
        @Bean
        public TokenStore tokenStore(){
            return new RedisTokenStore(redisConnectionFactory);
        }*/
    
        @Bean
        public TokenStore tokenStore(DataSource dataSource){
            return new JdbcTokenStore(dataSource);
        }
    }
    

    修改完成后,重启服务并请求令牌,发现access_token相关信息已经存储到mysql中:

    image-20210309110517206

    jdbc管理授权码

    授权码主要操作oauth_code表的,只有当 grant_type 为 "authorization_code" 时,该表中才会有数据产生; 其他的grant_type没有使用该表。更多的细节请参考 JdbcAuthorizationCodeServices

    默认情况下并未将授权码保存到 oauth_code 表中,原因是 JdbcAuthorizationCodeServices 没有添加到容器中。

    开启后,会将授权码放到auth_code表,授权后就会删除它

    修改AuthorizationServerConfig:

    @Configuration
    @EnableAuthorizationServer//开启认证服务器功能
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private CustomUserDetailsService customUserDetailsService;
    
        /**  配置被允许访问此认证服务器的客户端详情信息
         * 方式1:内存方式管理
         * 方式2:数据库管理
         * localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 使用内存方式
            clients.inMemory()
                    // 客户端id
                    .withClient("wj-pc")
            // 客户端密码,要加密,不然一直要求登录, 获取不到令牌, 而且一定不能被泄露
            .secret(passwordEncoder.encode("wj-secret"))
            // 资源id, 如商品资源
            .resourceIds("product-server")
            // 授权类型, 可同时支持多种授权类型
            .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
            // 授权范围标识,哪部分资源可访问(all是标识,不是代表所有)
            .scopes("all")
            // false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码,
            .autoApprove(false)
            .redirectUris("http://www.baidu.com/");// 客户端回调地址
        }
    
        @Autowired
        private TokenStore tokenStore;
    
        @Autowired
        private DataSource dataSource;
    
        /**
         * 重写父类的方法
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //密码模式需要设置此认证管理器
            endpoints.authenticationManager(authenticationManager);
            // 刷新令牌获取新令牌时需要
            endpoints.userDetailsService(customUserDetailsService);
            //设置token存储策略
            endpoints.tokenStore(tokenStore);
            endpoints.authorizationCodeServices(authorizationCodeServices());
        }
    
        // 向容器中导入AuthorizationCodeServices
        @Bean
        public AuthorizationCodeServices authorizationCodeServices(){
            return new JdbcAuthorizationCodeServices(dataSource);
        }
    }
    

    重启应用并获取code码:

    image-20210309111330751

    jdbc存储客户端信息

    oauth_client_details:客户端详情记录表

    image-20210309112201087

    注意:要使用BCryptPasswordEncoder为client_secret客户端密码加密

    向该表插入测试数据:client_secret是wj-secret加密后的结果

    INSERT INTO `study-security`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('wj-pc', 'product-server', '$2a$10$fTo73KCRzU3HXcPGtaTmxu9zDIrnoud6GvhlKF0sIxWzm7awSkGOK', 'all', 'authorization_code,password,implicit,client_credentials,refresh_token', 'http://www.baidu.com', NULL, 50000, NULL, NULL, 'false');
    

    修改AuthorizationServerConfig:修改客户端管理为jdbc方式

    @Configuration
    @EnableAuthorizationServer//开启认证服务器功能
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private CustomUserDetailsService customUserDetailsService;
    
        // 授权码管理策略
        @Bean
        public JdbcClientDetailsService JdbcClientDetailsService(){
            return new JdbcClientDetailsService(dataSource);
        }
    
        /**  配置被允许访问此认证服务器的客户端详情信息
         * 方式1:内存方式管理
         * 方式2:数据库管理
         * localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.withClientDetails(JdbcClientDetailsService());
        }
    
        @Autowired
        private TokenStore tokenStore;
    
        @Autowired
        private DataSource dataSource;
    
        /**
         * 重写父类的方法
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //密码模式需要设置此认证管理器
            endpoints.authenticationManager(authenticationManager);
            // 刷新令牌获取新令牌时需要
            endpoints.userDetailsService(customUserDetailsService);
            //设置token存储策略
            endpoints.tokenStore(tokenStore);
            endpoints.authorizationCodeServices(authorizationCodeServices());
        }
    
        // 向容器中导入AuthorizationCodeServices
        @Bean
        public AuthorizationCodeServices authorizationCodeServices(){
            return new JdbcAuthorizationCodeServices(dataSource);
        }
    }
    

    测试密码认证模式,成功。

    image-20210309115653211

    令牌端点的安全策略

    • /oauth/authorize:申请授权码 code, 涉及的类AuthorizationEndpoint
    • /oauth/token:获取令牌 token, 涉及的类TokenEndpoint
    • /oauth/check_token:用于资源服务器请求端点来检查令牌是否有效, 涉及的类CheckTokenEndpoint
    • /oauth/confirm_access:用户确认授权提交, 涉及的类WhitelabelApprovalEndpoint
    • /oauth/error:授权服务错误信息, 涉及的类WhitelabelErrorEndpoint
    • /oauth/token_key:提供公有密匙的端点,使用 JWT 令牌时会使用 , 涉及的类TokenKeyEndpoint

    默认情况下/oauth/check_token和/oauth/token_key端点默认是denyAll()

    拒绝访问的权限,要将这两个端点认证或授权后可以访问,因为后面资源服务器,要通过此端点检验令牌是否有效

    image-20210309121509698

    配置AuthorizationServerConfig,重写configure方法

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //所有人可以访问/oauth/token_key后面获取公钥,默认拒绝访问
        security.tokenKeyAccess("permitAll()");
        //认证后可访问/oauth/check_token,默认拒绝访问
        security.checkTokenAccess("isAuthenticated()");
    }
    

    修改后,重新访问

    image-20210309121601587

  • 相关阅读:
    threading学习
    Python基础-5
    BS4
    requests基础
    Python基础-4
    Python基础-3
    Python基础-2
    Python基础-1
    DevOps
    nginx配置ssl证书实现https
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/14504825.html
Copyright © 2011-2022 走看看