在上一节我们讲述的配置是把授权码存储在redis中,把相应的请求的路径用使用in-memory存储 ,这个是放在了内存中,但是实际开发我们的数据希望是从数据表中查询的,那应该怎么做呢?

1.回顾in-memory存储

/**
* inMemory是存储到内存中 并未到数据库
*/
clients.inMemory()
//client Id
.withClient("normal-app")
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ROLE_CLIENT")
.scopes("read","write")
.resourceIds(resourceId)
.accessTokenValiditySeconds(accessTokenValiditySeconds)//授权码存活时间
.and()
.withClient("trusted-app")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.resourceIds(resourceId)
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.secret("secret");

如果使用的是这种方式,我们对应的授权码的请求路径如下:

http://localhost:8787/oauth/authorize?client_id=normal-app&response_type=code&scope=read&redirect_uri=/resources/user

相应的参数请对照上

然后我们使用的是jwt的令牌方式,相应的请求路径如下:

http://localhost:8787/oauth/token?code=r8YBUL&grant_type=authorization_code&client_id=normal-app&redirect_uri=/resources/user

这个是放在内存中的存储方式

2.如果我需要从数据库读取相应的字段的参数 可如下配置:

  @Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //默认值InMemoryTokenStore对于单个服务器是完全正常的(即,在发生故障的情况下,低流量和热备份备份服务器)。大多数项目可以从这里开始,也可以在开发模式下运行,以便轻松启动没有依赖关系的服务器。
//这JdbcTokenStore是同一件事的JDBC版本,它将令牌数据存储在关系数据库中。如果您可以在服务器之间共享数据库,则可以使用JDBC版本,如果只有一个,则扩展同一服务器的实例,或者如果有多个组件,则授权和资源服务器。要使用JdbcTokenStore你需要“spring-jdbc”的类路径。 //这个地方指的是从jdbc查出数据来存储
clients.withClientDetails(clientDetails()); }

这里可以看到我们是把之前的从内存读取的方式给去掉了,取而代之的是clientDetails()这个方法,然后我们看下这个方法:

 @Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}

只需配置这个bean即可 但是我们的datasource是在yml配置文件中配置好了的,只需要注入:

import javax.sql.DataSource;
@Resource
private DataSource dataSource;

但是这里还没完,我们首先要讲下JdbcClientDetailsService是如何从数据库读取的,我们可以点击进入查看相应的源码,如下所示:

public JdbcClientDetailsService(DataSource dataSource) {
this.updateClientDetailsSql = DEFAULT_UPDATE_STATEMENT;
this.updateClientSecretSql = "update oauth_client_details set client_secret = ? where client_id = ?";
this.insertClientDetailsSql = "insert into oauth_client_details (client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove, client_id) values (?,?,?,?,?,?,?,?,?,?,?)";
this.selectClientDetailsSql = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove from oauth_client_details where client_id = ?";
this.passwordEncoder = NoOpPasswordEncoder.getInstance();
Assert.notNull(dataSource, "DataSource required");
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.listFactory = new DefaultJdbcListFactory(new NamedParameterJdbcTemplate(this.jdbcTemplate));
}

我们可以看到,他自己是有一个默认的字段的表的,里面有相应的查询的方法,所以我们需要建立一个这样的表,sql如下:

-- ----------------------------
-- Table structure for oauth_client_details 将请求的路径存在数据表
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这个是默认的类的表,一般用它默认的即可,我们这边就需要根据以上的字段配置相关的内容,如下:

这里配置好了之后我们的访问路径为:

//步骤:客户端向认证服务器申请令牌
http://localhost:8787/oauth/token?client_id=normal-app&grant_type=authorization_code&code=1oCj8e&redirect_uri=http://localhost:8787/resources/user

然后令牌的访问路径为:

//拿到令牌后访问资源:
http://localhost:8787/resources/user?access_token=9d62c7b0-780e-4c6a-ad5a-56d79a089342

记得code要换成上一步生成的code

3.掉坑回顾:

之前我们用的jwt来存储令牌token,后来我发现怎么也不出现jwttoken,经过多次检查发现了错误,代码如下:

package urity.demo.oauth2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import urity.demo.service.RedisAuthenticationCodeServices;
import urity.demo.support.MyUserDetailService; import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit; @Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Resource
private DataSource dataSource; @Value("${resource.id:spring-boot-application}")//默认是spring-boot-application
private String resourceId; @Value("${access_token.validity_period:36000}")
private int accessTokenValiditySeconds = 36000; //认证管理 很重要 如果security版本高可能会出坑哦
@Resource
private AuthenticationManager authenticationManager; @Resource
private RedisAuthenticationCodeServices redisAuthenticationCodeServices; @Resource
private MyUserDetailService myUserDetailService; //security
//定义令牌端点上的安全约束。
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')");
oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); } //这个是定义授权的请求的路径的Bean
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
} // @Bean // 声明TokenStore实现
// public JdbcTokenStore jdbcTokenStore() {
// return new JdbcTokenStore(dataSource);
// } //将ClientDetailsServiceConfigurer(从您的回调AuthorizationServerConfigurer)可以用来在内存或JDBC实现客户的细节服务来定义的。客户端的重要属性是
//clientId:(必填)客户端ID。
//secret:(可信客户端需要)客户机密码(如果有)。没有可不填
//scope:客户受限的范围。如果范围未定义或为空(默认值),客户端不受范围限制。read write all
//authorizedGrantTypes:授予客户端使用授权的类型。默认值为空。
//authorities授予客户的授权机构(普通的Spring Security权威机构)。
//客户端的详细信息可以通过直接访问底层商店(例如,在数据库表中JdbcClientDetailsService)或通过ClientDetailsManager接口(这两种实现ClientDetailsService也实现)来更新运行的应用程序。
//注意:JDBC服务的架构未与库一起打包(因为在实践中可能需要使用太多变体)
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //默认值InMemoryTokenStore对于单个服务器是完全正常的(即,在发生故障的情况下,低流量和热备份备份服务器)。大多数项目可以从这里开始,也可以在开发模式下运行,以便轻松启动没有依赖关系的服务器。
//这JdbcTokenStore是同一件事的JDBC版本,它将令牌数据存储在关系数据库中。如果您可以在服务器之间共享数据库,则可以使用JDBC版本,如果只有一个,则扩展同一服务器的实例,或者如果有多个组件,则授权和资源服务器。要使用JdbcTokenStore你需要“spring-jdbc”的类路径。 // /**
// * inMemory是存储到内存中 并未到数据库
// */
// clients.inMemory()
// //client Id
// .withClient("normal-app")
// .authorizedGrantTypes("authorization_code", "implicit")
// .authorities("ROLE_CLIENT")
// .scopes("read","write")
// .resourceIds(resourceId)
// .accessTokenValiditySeconds(accessTokenValiditySeconds)//授权码存活时间
// .and()
// .withClient("trusted-app")
// .authorizedGrantTypes("client_credentials", "password")
// .authorities("ROLE_TRUSTED_CLIENT")
// .scopes("read", "write")
// .resourceIds(resourceId)
// .accessTokenValiditySeconds(accessTokenValiditySeconds)
// .secret("secret"); //这个地方指的是从jdbc查出数据来存储
clients.withClientDetails(clientDetails()); } //AuthorizationEndpoint可以通过以下方式配置支持的授权类型AuthorizationServerEndpointsConfigurer。默认情况下,所有授权类型均受支持,除了密码(有关如何切换它的详细信息,请参见下文)。以下属性会影响授权类型:
//authenticationManager:通过注入密码授权被打开AuthenticationManager。
//userDetailsService:如果您注入UserDetailsService或者全局配置(例如a GlobalAuthenticationManagerConfigurer),则刷新令牌授权将包含对用户详细信息的检查,以确保该帐户仍然活动
//authorizationCodeServices:定义AuthorizationCodeServices授权代码授权的授权代码服务(实例)。
//implicitGrantService:在批准期间管理状态。
//tokenGranter:(TokenGranter完全控制授予和忽略上述其他属性)
//在XML授予类型中包含作为子元素authorization-server。 /**
* /oauth/authorize您可以从该请求中获取所有数据,
* 然后根据需要进行渲染,
* 然后所有用户需要执行的操作都是回复有关批准或拒绝授权的信息。
* 请求参数直接传递给您UserApprovalHandler,
* AuthorizationEndpoint所以您可以随便解释数据
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
endpoints.accessTokenConverter(accessTokenConverter());//jwt
//从数据库查请求的路径
// endpoints.tokenStore(jdbcTokenStore());
//从jwt来数据
endpoints.tokenStore(jwtStore()); //授权码存储
endpoints.authorizationCodeServices(redisAuthenticationCodeServices); endpoints.userDetailsService(myUserDetailService); // 配置TokenServices参数 注意这个是默认的uuid的存储设置 与jwt无关 如果要用jwt请注释掉
// DefaultTokenServices tokenServices = new DefaultTokenServices(); //获取令牌的是否从jdbc查 显然 这里是的
// tokenServices.setTokenStore(endpoints.getTokenStore()); //我们可以用jwt来存放token
// tokenServices.setTokenStore(jwtStore());
// tokenServices.setSupportRefreshToken(false);
// tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
// tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天
// endpoints.tokenServices(tokenServices); // @Bean
// RedisTokenStore redisTokenStore(){
// return new RedisTokenStore(redisConnectionFactory);
// }
// endpoints.tokenStore(redisTokenStore());
} //定义jwttoken的某些属性
@Bean
public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
/**
* 重写增强token的方法
* 自定义返回相应的信息
*
*/ @Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { String userName = authentication.getUserAuthentication().getName();
// 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration}
User user = (User) authentication.getUserAuthentication().getPrincipal();
/** 自定义一些token属性 ***/
final Map<String, Object> additionalInformation = new HashMap<>();
additionalInformation.put("userName", userName);
additionalInformation.put("roles", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
return enhancedToken;
} };
// 测试用,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式
accessTokenConverter.setSigningKey("123");
return accessTokenConverter; } @Bean
public TokenStore jwtStore() { TokenStore tokenStore = new JwtTokenStore(accessTokenConverter()); return tokenStore;
} /**
* 创建一个默认的资源服务token
*
* @return
*/
@Bean
public ResourceServerTokenServices defaultTokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
defaultTokenServices.setTokenStore(jwtStore());
return defaultTokenServices;
} }

请注意:DefaultTokenServices与jwttoken的配置不能都存在,否则系统只找DefaultTokenServices的配置, 也就是生成的token会一直是默认的UUID,这里我们只能两者选其一配置在代码中

/ 配置TokenServices参数 注意这个是默认的uuid的存储设置 与jwt无关 如果要用jwt请注释掉
// DefaultTokenServices tokenServices = new DefaultTokenServices(); //获取令牌的是否从jdbc查 显然 这里是的
// tokenServices.setTokenStore(endpoints.getTokenStore()); //我们可以用jwt来存放token
// tokenServices.setTokenStore(jwtStore());
// tokenServices.setSupportRefreshToken(false);
// tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
// tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天
// endpoints.tokenServices(tokenServices);

4.小技巧:

我们从路径授权后获得了code,就可以用code请求相应对的路径换取jwttoken,我们用postman来进行测试:

这里我们用basic Auth的方式 只需要填写name:normal-app即可,密码可以不填

http://localhost:8787/oauth/token?client_id=normal-app&grant_type=authorization_code&code=csTjhK&redirect_uri=http://localhost:8787/resources/user

我们可以看下获得的json:

这里生成的jwttoken中携带了相应这个是jwt的信息,这个一段字符串实际上是Header和Payload加密后拼接而成的,相应的可以查看下一篇jwt的相关解析.

我们可以访问:https://www.jsonwebtoken.io/ 来解析下这个token里面的信息:

这里可以看到Header和Payload的信息,Header主要存储的是type和加密算法,这里是HS256,我们主要看Payload的信息:

{
"aud": [
"resourceId"
],
"user_name": "test",
"scope": [
"read"
],
"roles": [
{
"authority": "ROLE_USER"
},
{
"authority": "admin"
}
],
"exp": 1532662701,
"userName": "test",
"authorities": [
"admin",
"ROLE_USER"
],
"jti": "066cefa0-0a7a-40da-87a0-133c5a9c64d3",
"client_id": "normal-app",
"iat": 1532659101
}

这里可以看到登录的用户名,token的生命周期等.我们就可以更清晰了解生成的jwttoken携带的信息有哪些了.

oauth2.0通过JdbcClientDetailsService从数据库读取相应的配置的更多相关文章

  1. OAuth2.0授权码模式实战

    OAuth2.0是目前比较流行的一种开源授权协议,可以用来授权第三方应用,允许在不将用户名和密码提供给第三方应用的情况下获取一定的用户资源,目前很多网站或APP基于微信或QQ的第三方登录方式都是基于O ...

  2. mysql timestamp为0值时,python读取后的对象为None

    MySQL数据表中,如果timestamp类型的字段,值为0, python从数据库读取数据后,得到对象是什么类型,是否为None呢? 下面来测试下. 创建数据表 首先创建数据表,其中字段pr_rul ...

  3. SpringBootSecurity学习(17)前后端分离版之 OAuth2.0 数据库(JDBC)存储客户端

    自动批准授权码 前面我们授权的流程中,第一步获取授权码的时候,都会经历一个授权是否同意页面: 这个流程就像第三方登录成功后,提问是否允许获取昵称和头像信息的页面一样,这个过程其实是可以自动同意的,需要 ...

  4. [更新]一份包含: 采用RSA JWT(Json Web Token, RSA加密)的OAUTH2.0,HTTP BASIC,本地数据库验证,Windows域验证,单点登录的Spring Security配置文件

    没有任何注释,表怪我(¬_¬) 更新: 2016.05.29: 将AuthorizationServer和ResourceServer分开配置 2016.05.29: Token获取采用Http Ba ...

  5. Spring Cloud 微服务中搭建 OAuth2.0 认证授权服务

    在使用 Spring Cloud 体系来构建微服务的过程中,用户请求是通过网关(ZUUL 或 Spring APIGateway)以 HTTP 协议来传输信息,API 网关将自己注册为 Eureka ...

  6. 整合spring cloud云架构 - SSO单点登录之OAuth2.0登录认证(1)

    之前写了很多关于spring cloud的文章,今天我们对OAuth2.0的整合方式做一下笔记,首先我从网上找了一些关于OAuth2.0的一些基础知识点,帮助大家回顾一下知识点: 一.oauth中的角 ...

  7. Spring Cloud云架构 - SSO单点登录之OAuth2.0登录认证(1)

    今天我们对OAuth2.0的整合方式做一下笔记,首先我从网上找了一些关于OAuth2.0的一些基础知识点,帮助大家回顾一下知识点: 一.oauth中的角色 client:调用资源服务器API的应用 O ...

  8. 使用SpringSecurity Oauth2.0实现自定义鉴权中心

    Oauth2.0是什么不在赘述,本文主要介绍如何使用SpringSecurity Oauth2.0实现自定义的用户校验 1.鉴权中心服务 首先,列举一下我们需要用到的依赖,本文采用的是数据库保存用户信 ...

  9. 使用Owin中间件搭建OAuth2.0认证授权服务器

    前言 这里主要总结下本人最近半个月关于搭建OAuth2.0服务器工作的经验.至于为何需要OAuth2.0.为何是Owin.什么是Owin等问题,不再赘述.我假定读者是使用Asp.Net,并需要搭建OA ...

随机推荐

  1. HTTP请求头中各字段解释

    Accept : 浏览器(或者其他基于HTTP的客户端程序)可以接收的内容类型(Content-types),例如 Accept: text/plain Accept-Charset:浏览器能识别的字 ...

  2. python3_猜数字

    import random count = 0while count<3: count +=1 number = int(input("猜数字:").strip()) num ...

  3. 安装docker以及问题解决办法

    1.使用官方推荐的方式安装 yum-utilsyum install -y yum-utils2.使用如下的命令设置稳定版的 repositoryyum-config-manager \    --a ...

  4. 【C语言编程练习】5.9 爱因斯坦的阶梯问题

    1. 题目要求 有一个长阶梯,每2步上,最后剩1个台阶,若每3步上,最后剩2个台阶.若每5步上,最后剩4个台阶,若每6步上,最后剩5个台阶.只有每步上7阶,才可以刚好走完,请问台阶至少有多少阶? 2. ...

  5. 【面试题】Java实现String的IndexOf方法

    先说题后感:程序员这一行,很多时候,自驱学习能力是自我成长一个很重要的因素(当然技术最好的学习途径都是通过项目实践去学习.理解.掌握).而自学方法中,除了看官方文档.技术博客等途径之外,学习源码也是一 ...

  6. deepin 开机进入 initramfs,无法开机

    原因 这个问题多是由于不正常关机造成文件系统出问题导致的 解决 fsck -t ext4 /dev/sda6 这个命令用于修复磁盘,一直选择y即可. 如果sda6 不行就换位sda1 再试. 执行完后 ...

  7. BOM与DOM操作

    BOM: OM-JavaScript是运行在浏览器中的,所以提供了一系列对象用于和浏览器窗口进行交互,这些对象主要包括window.document.location.navigator和screen ...

  8. Mesos源码分析(4) Mesos Master的启动之三

    3. ModuleManager::load(flags.modules.get())如果有参数--modules或者--modules_dir=dirpath,则会将路径中的so文件load进来   ...

  9. rz快速上传文件到ssh服务器

    [root@k8s01 ~]# rz --helprz version 0.12.20Usage: rz [options] [filename.if.xmodem]Receive files wit ...

  10. 最新Java技术

    最近在网上查资料碰到好多没接触过的技术,先汇总在这里备用,以后慢慢吸收 1. JNA JNI的替代品,调用方式比JNI更直接,不再需要JNI那层中间接口,几乎达到Java直接调用动态库 2. Smal ...