ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring Security OAuth2:认证服务器策略配置

2021-03-09 12:36:45  阅读:186  来源: 互联网

标签:令牌 OAuth2 VARCHAR Spring token oauth Security 256 public


接着前一篇博客的代码: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

标签:令牌,OAuth2,VARCHAR,Spring,token,oauth,Security,256,public
来源: https://www.cnblogs.com/wwjj4811/p/14504825.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有