spring-cloud-oauth2 涉及知识

给自己主要是了解其思路,从而得到自己的想法,去实现自己的一套权限校验认证模块。实际上本文档基于Spring-cloud-oauth2来编写,与spring-security-oauth2有些许出入,以后续文章为主,主要是提供大致描述

一、附带的sql

oauth_client_details (授权码)

主要操作oauth_client_details表的类是JdbcClientDetailsService.java

DROP TABLE IF EXISTS `oauth_client_details`;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(255) NOT NULL COMMENT '客户端标识',
  `resource_ids` varchar(255) DEFAULT NULL COMMENT '接入资源列表',
  `client_secret` varchar(255) DEFAULT NULL COMMENT '客户端秘钥',
  `scope` varchar(255) DEFAULT NULL, 
  `authorized_grant_types` varchar(255) DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) DEFAULT NULL,
  `authorities` varchar(255) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` longtext,
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `archived` tinyint(4) DEFAULT NULL,
  `trusted` tinyint(4) DEFAULT NULL,
  `autoapprove` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='接入客户端信息';
字段名类型描述
client_idvarchar主键,必须唯一,不能为空. 用于唯一标识每一个客户端(client);
resource_idsvarchar客户端所能访问的资源id集合,多个资源时用逗号(,)
client_secretvarchar用于指定客户端(client)的访问密匙
scopevarchar指定客户端申请的权限范围,可选值包括read,write,trust,all(可自定义);若有多个权限范围用逗号(,)分隔
authorized_grant_typesvarchar就是5种授权模式:authorization_code,password,refresh_token,implicit,client_credentials, 若支持多个grant_type用逗号(,)分隔,
web_server_redirect_urivarchar重定向地址,在oauth中会校验
authoritiesvarchar指定客户端所拥有的权限值,可选, 若有多个权限值,用逗号(,)分隔。若授权模式中不需要账户密码的建议设;若授权模式需要账户密码的,可以不设立
access_token_validityint设定客户端的access_token的有效时间值(单位:秒) 【默认12小时】
refresh_token_validityint设定客户端的refresh_token的有效时间值(单位:秒)【默认30天】
additional_informationlongtext可以额外附带的信息,若赋值,则需要json规范
create_timetimestamp数据的创建时间,精确到秒,由数据库在插入数据时取当前系统时间自动生成(扩展字段)
archivedtinyint标识是否存档,默认为0
trustedtinyint标识是否受信任,默认0(0-不信任,1-信任)
autoapprovevarchar设置用户是否自动Approval操作,通常只在authorization_code 模式有效

oauth_code(支持授权码获取accessToken)

CREATE TABLE IF NOT EXISTS `oauth_code` (
  `code` VARCHAR(256) NULL DEFAULT NULL,
  `authentication` BLOB NULL DEFAULT NULL)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

oauth_access_token(余下方式使用)

oauth_client_token表的主要操作在JdbcClientTokenServices.java,实际上未使用到

CREATE TABLE IF NOT EXISTS `oauth_access_token` (
  `token_id` VARCHAR(256) NULL DEFAULT NULL,
  `token` BLOB NULL DEFAULT NULL,
  `authentication_id` VARCHAR(128) NOT NULL,
  `user_name` VARCHAR(256) NULL DEFAULT NULL,
  `client_id` VARCHAR(256) NULL DEFAULT NULL,
  `authentication` BLOB NULL DEFAULT NULL,
  `refresh_token` VARCHAR(256) NULL DEFAULT NULL,
  PRIMARY KEY (`authentication_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
字段名类型描述
token_idvarchar从服务器端获取到的access_token的值.
tokenBLOB这是一个二进制的字段, 存储的数据OAuth2AccessToken.java对象序列化后的二进制数据.
authentication_idvarchar该字段具有唯一性, 是根据当前的username(如果有),client_id与scope通过MD5加密生成的. 具体实现请参考DefaultClientKeyGenerator.java类.
user_namevarchar登录时的用户名
client_idvarchar主键,必须唯一,不能为空. 用于唯一标识每一个客户端(client);
authenticationvarchar存储将OAuth2Authentication.java对象序列化后的二进制数据.
refresh_tokenvarchar该字段的值是将refresh_token的值通过MD5加密后存储的.

oauth_refresh_token(余下方式使用)

CREATE TABLE IF NOT EXISTS `oauth_refresh_token` (
  `token_id` VARCHAR(256) NULL DEFAULT NULL,
  `token` BLOB NULL DEFAULT NULL,
  `authentication` BLOB NULL DEFAULT NULL)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
字段名类型描述
token_idvarchar从服务器端获取到的access_token的值.
tokenBLOB这是一个二进制的字段, 存储的数据OAuth2AccessToken.java对象序列化后的二进制数据.
authenticationvarchar存储将OAuth2Authentication.java对象序列化后的二进制数据.

二、AuthorizationServerConfigurer(授权服务配置)

总体围绕着 AuthorizationServerConfigurer的实现进行设计,我们需要自行实现其三个抽象方法,若需要启用Oauth2授权,则需要加入 @EnableAuthorizationServer(这个注释 位置不限定)

实际上实现类`OAuth2AuthorizationServerConfiguration` 可以当做用于参考自定义实现时使用
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {}

public void configure(ClientDetailsServiceConfigurer clients) throws Exception {}

public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {}

1、ClientDetailsServiceConfigurer (配置客户端详情)

可以选择客户端模式:通常有内存模式jdbc模式

配置用户资源,说白了就是校验的资源
其中需要包含主要参数,鉴权时会根据参数得到不一样的结果。
# client_id: 也就是校验唯一值,通常针对客户

# client_secret: 加密方式(通常设置方法,可自行加盐)

# resource_ids: 针对资源的唯一值

#authorized_grant_types: 支持授权的方式,包含如下5种
 - authorization_code  授权码方式 【需要授权页面】
 - password   密码模式 【使用账户密码来发令牌】
 - client_credentials  客户凭证模式 【验证 client_id 与 client_scret(也就是约定的密钥)即可】
 - implicit    简化模式(也有隐藏式)【直接发令牌】
 - refresh_token  刷新令牌

#scopes: 代表授权范围
  如果需要可以直接 all
  
#autoApprove : 如果为true 会自动跳过授权过程

#redirect_uri:顾名思义的跳转地址

2、AuthorizationServerSecurityConfigurer(配置令牌安全约束)

一般我们会在这里配置几个参数

①allowFormAuthenticationForClients(允许表单验证)

②passwordEncoder(设置oauth_client_details加密方式)

通常为 默认的BCryptPasswordEncoder()

③tokenKeyAccess (查询token信息管理)

若默认则拒绝通过access_token查询token信息 ,默认为:denyAll()

可选值: isAuthenticated(挡住匿名者)、isFullyAuthenticated(挡住匿名者和记住我)、permitAll(允许所有)、denyAll(拒绝所有)

这里引用jdbcStore的话需要重写一下 `readAccessToken`方法,否则会出 `Failed to find access token for token XX`错误。实际上已经从jdbc中获取了值

    ... extends JdbcTokenStore   
    @Override
    public OAuth2AccessToken readAccessToken(String tokenValue) {
        OAuth2AccessToken accessToken = null;

        try {
            accessToken = new DefaultOAuth2AccessToken(tokenValue);
        } catch (EmptyResultDataAccessException e) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Failed to find access token for token " + tokenValue);
            }
        } catch (IllegalArgumentException e) {
            LOG.warn("Failed to deserialize access token for " + tokenValue, e);
            removeAccessToken(tokenValue);
        }

        return accessToken;
    }

④checkTokenAccess(校验token是否可用)

若默认则拒绝通过access_token查询token信息 ,默认为:denyAll()

可选值: isAuthenticated(挡住匿名者)、isFullyAuthenticated(挡住匿名者和记住我)、permitAll(允许所有)、denyAll(拒绝所有)

其中basic auth 使用对应 client-id 与 client-secret即可

3、AuthorizationServerEndpointsConfigurer(配置令牌端点服务)

一般来说我们需要配置如下几个参数

①authenticationManager(密码模式服务)

如果不需要兼容 password 这种授权模式的话,可以不配置

②authorizationCodeServices(授权码模式服务)

如果不配置的话,其实默认是走内存模型

通常我们会配合jdbc来可视化这些数据。也就是oauth_code表

主要为 authorization_code模式服务,获取access_token用到

// 可以参考类AuthorizationServerEndpointsConfigurer

private AuthorizationCodeServices authorizationCodeServices() {
    if (authorizationCodeServices == null) {
        authorizationCodeServices = new InMemoryAuthorizationCodeServices();
    }
    return authorizationCodeServices;
}

③tokenServices (令牌管理服务)

如何控制令牌存放,已经超时,可刷新等问题

    @Autowired
    private ClientDetailsService clientDetailsService;//基于1选择的客户端模式会自动生成jdbc或内存的具体实现

    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService);
        services.setSupportRefreshToken(true);//是否支持刷新令牌
        services.setTokenStore(tokenStore); //token的存放模式
        services.setAccessTokenValiditySeconds(7200); //控制超时时间
        services.setRefreshTokenValiditySeconds(259200);//控制刷新令牌的时间
        return services;
    }
TokenStore参数
这个实际上可以有 `5 种方式实现`,参考TokenStore实现类

1、JdbcTokenStore   直接基于jdbc存贮

2、JwkTokenStore     直接拓展支持了JSON Web Key (JWK)、JSON Web Token (JWT)与JSON Web Signature (JWS)

3、InMemoryTokenStore  基于内存管理

4、RedisTokenStore   基于redis管理

5、JwtTokenStore   基于jwt 存储,难以限定时间,需要自己让其变得无状态

④allowedTokenEndpointRequestMethods

就很直接发放令牌的方式,当然最好以post请求最好,当然你也可以填其他

allowedTokenEndpointRequestMethods(HttpMethod.POST);

⑤userDetailsService(密码服务校验用)

可以直接设置进来,也可以直接 实现UserDetailsService类,效果是一致的

校验所需授权客户端传入的参数,说白了就是帐号密码

⑥pathMapping(替换默认授权路径)

第一个为默认端点url地址,第二个为自定义的端点url地址

# IDEA 启动的话可以在Endpoints的mapping下查看到具体类路径

/oauth/authorize :授权的端点

/oauth/token :令牌的端点。

/oauth/confirm_access :用户确认授权提交端点(也就是自定义的授权页面端点)。

/oauth/error :授权服务错误信息端点。

/oauth/check_token :用于资源服务访问的令牌解析端点。

/oauth/token_key :如果使用JWT令牌的话,这个端点可以提供公有密匙。

三、授权模式

(1)授权码模式(authorization_code)

access_token 只能通过 authorization_code的方式获取真正的access_token

①获取授权码 ②根据授权码去获取真正的access_token

获取授权码

接口地址: ${ip}:{port}/oauth/authorize
请求方式: Get
字段名描述
client_id改值必须和配置在clients中的值保持一致
response_type固定传值code表示使用授权码模式进行认证
scope改值必须配置的clients中的值一致
redirect_uri获取code之后重定向的地址,必须和配置的clients一致

获取accessToken

接口地址: ${ip}:{port}/oauth/token
请求方式: Post
字段名描述
client_id改值必须和配置在clients中的值保持一致
client_secret未加密前的密钥,默认使用Bcrypt方法
code第一步中获取的授权码
grant_type固定传值authorization_code表示使用授权码获取accessToken
redirect_uri获取code之后重定向的地址,必须和配置的clients一致
{
    "access_token": "2d4ca73a-b55c-44c6-9888-07c688df484d",
    "token_type": "bearer",
    "refresh_token": "9506e65a-2415-4a14-b9d0-4ab953d681af",
    "expires_in": 29,
    "scope": "all"
}

(2)简化模式(implicit)

撤销授权码,直接把access_token放到浏览器地址上,与授权码模式只是response_type不一样

接口地址: ${ip}:{port}/oauth/authorize
请求方式: Get
字段名描述
client_id改值必须和配置在clients中的值保持一致
response_type固定传值token表示使用授权码模式进行认证
scope改值必须配置的clients中的值一致
redirect_uri获取code之后重定向的地址,必须和配置的clients一致

(3)密码模式 (password)

用户密码帐号直接包含在请求上,通常需要开发环境在能掌控范围内

接口地址: ${ip}:{port}/oauth/token
请求方式: Post
字段名描述
client_id改值必须和配置在clients中的值保持一致
client_secret改值必须和配置在clients中的值保持一致
grant_type在密码模式下,该值固定为password
username用户名
password密码
{
    "access_token": "e8c49c64-e419-426d-aecc-c38a9c9c236e",
    "token_type": "bearer",
    "refresh_token": "4801611a-3cc8-46f3-9b0e-b26b858adde0",
    "expires_in": 29,
    "scope": "all"
}

(4)客户端模式(client_credentials)

通常为给对接方提供,双方百分百安全的情况下比较稳妥,因为会直接发令牌

接口地址: ${ip}:{port}/oauth/token
请求方式: Post
字段名描述
client_id改值必须和配置在clients中的值保持一致
client_secret改值必须和配置在clients中的值保持一致
grant_type在密码模式下,该值固定为client_credentials
{
    "access_token": "ffe20306-723c-4007-a595-70a9df4f4eb8",
    "token_type": "bearer",
    "expires_in": 29,
    "scope": "all"
}

刷新密钥(refresh_token)

通常需要一个refresh_token才能执行这个方法

接口地址: ${ip}:{port}/oauth/token
请求方式: Post
字段名描述
client_id该值必须和配置在clients中的值保持一致
client_secret该值必须和配置在clients中的值保持一致
grant_type如果想根据refresh_token换新的token,这里固定传值refresh_token
refresh_token未失效的refresh_token
{
    "access_token": "3974646c-f764-493e-ba99-64fe6cbc11a3",
    "token_type": "bearer",
    "refresh_token": "2d266ae6-b620-4243-baf0-5f1974709015",
    "expires_in": 29,
    "scope": "all"
}

四、定义权限控制(配置url的权限控制)

构造一个实现WebSecurityConfigurerAdapter的类,利用**@configuration** 交由spring管理

// WebSecurityConfig
   //安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()   //主要避开所有请求校验头中含有token的问题
                .authorizeRequests()
                .antMatchers("/login*", "/css/*").permitAll()
                .anyRequest().authenticated()
                /*登陆相关*/
                .and()
                .formLogin()//允许表单提交登陆
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                /*退出相关*/
                .and()
                .logout()
                .permitAll();
    }

1、url限定权限登入

①根据角色

     //下列中都皆校验 需要 ADMIN 角色,其中 仅有hasAuthority及衍生hasAnyAuthority不会在校验前给你角色默认增加 ROLE_ 前缀
     .antMatchers("/test/admin").hasRole("ADMIN")
     
     .antMatchers("/test/admin").access("hasRole('ROLE_NORMAL')") 
     
     .antMatchers("/test/admin").hasAuthority("ADMIN")

②根据ip

.antMatchers("/test/admin").hasIpAddress("192.168.2.212")

五、常用权限注解(3个)

实际运用上,我们需要对资源进行管理,搭配自己设定的权限内容进行限定(也就是authorities),说到底也是整合了spring-security的方式

@Secured()

在任意配置类上增加@EnableGlobalMethodSecurity(securedEnabled = true),角色上需要附带前缀 ROLE_,否则直接不予辨认

//AbstractAccessDecisionManager类可以参考,org.springframework.security.access.vote.AbstractAccessDecisionManager
//参考 supports 方法,最终会调用默认值前缀验证匹配 `ROLE_`

eg: @Secured(“ROLE_user”),必须包含 ROLE_ 前缀,多选则 @Secured({“ROLE_user”,“ROLE_test”})

@PreAuthorize() (我常用)

在任意配置类上增加 @EnableGlobalMethodSecurity(prePostEnabled = true)

即可支持 对应org.springframework.security.access.prepost下的所有注解

只需要注释到所需要校验的资源上即可,另外其支持SpEL表达式,对应spring-security表达式的位置`org.springframework.security.access.expression.SecurityExpressionRoot`

通常使用到的可能就 hasRole()及 hasAuthority()

这里要注意,除非你的角色中本身存在 `ROLE_` 前缀,否则还是使用hasAuthority 替代 hasRole,我目前找不到对应配置能直接改默认前缀,除非重写

eg: @PreAuthorize(“hasRole(‘user’)”) 等同于@PreAuthorize(“hasRole(‘ROLE_user’)”) 相当于 @PreAuthorize(“hasAuthority(‘ROLE_user’)”)

@RolesAllowed()

在任意配置类上增加@EnableGlobalMethodSecurity(jsr250Enabled = true)

即可支持对应javax.annotation.security下的所有注解

这里与@PreAuthorize一致,会默认增加前缀 ROLE_

eg: @RolesAllowed(“user”) 等于@RolesAllowed(“ROLE_user”) ,相当于校验 ROLE_user

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值