Spring Cloud OAuth2 实现单点登录

文章较长,建议好看,建议转发,建议收藏。

OAuth 2 有四种授权模式,分别是授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials),具体 OAuth2 是什么,可以参考这篇文章。(http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)

本文我们将使用授权码模式和密码模式两种方式来实现用户认证和授权管理。

OAuth2 其实是一个关于授权的网络标准,它制定了设计思路和运行流程,利用这个标准我们其实是可以自己实现 OAuth2 的认证过程的。今天要介绍的 spring-cloud-starter-oauth2 ,其实是 Spring Cloud 按照 OAuth2 的标准并结合 spring-security 封装好的一个具体实现。

什么情况下需要用 OAuth2

首先大家最熟悉的就是几乎每个人都用过的,比如用微信登录、用 QQ 登录、用微博登录、用 Google 账号登录、用 github 授权登录等等,这些都是典型的 OAuth2 使用场景。假设我们做了一个自己的服务平台,如果不使用 OAuth2 登录方式,那么我们需要用户先完成注册,然后用注册号的账号密码或者用手机验证码登录。而使用了 OAuth2 之后,相信很多人使用过、甚至开发过公众号网页服务、小程序,当我们进入网页、小程序界面,第一次使用就无需注册,直接使用微信授权登录即可,大大提高了使用效率。因为每个人都有微信号,有了微信就可以马上使用第三方服务,这体验不要太好了。而对于我们的服务来说,我们也不需要存储用户的密码,只要存储认证平台返回的唯一ID 和用户信息即可。

以上是使用了 OAuth2 的授权码模式,利用第三方的权威平台实现用户身份的认证。当然了,如果你的公司内部有很多个服务,可以专门提取出一个认证中心,这个认证中心就充当上面所说的权威认证平台的角色,所有的服务都要到这个认证中心做认证。

这样一说,发现没,这其实就是个单点登录的功能。这就是另外一种使用场景,对于多服务的平台,可以使用 OAuth2 实现服务的单点登录,只做一次登录,就可以在多个服务中自由穿行,当然仅限于授权范围内的服务和接口。

实现统一认证功能

本篇先介绍密码模式实现的单点登录,下一篇再继续说授权码模式。

在微服务横行的今天,谁敢说自己手上没几个微服务。微服务减少了服务间的耦合,同时也在某些方面增加了系统的复杂度,比如说用户认证。假设我们这里实现了一个电商平台,用户看到的就是一个 APP 或者一个 web 站点,实际上背后是由多个独立的服务构成的,比如用户服务、订单服务、产品服务等。用户只要第一次输入用户名、密码完成登录后,一段时间内,都可以任意访问各个页面,比如产品列表页面、我的订单页面、我的关注等页面。

我们可以想象一下,自然能够想到,在请求各个服务、各个接口的时候,一定携带着什么凭证,然后各个服务才知道请求接口的用户是哪个,不然肯定有问题,那其实这里面的凭证简单来说就是一个 Token,标识用户身份的 Token。

系统架构说明

认证中心:oauth2-auth-server,OAuth2 主要实现端,Token 的生成、刷新、验证都在认证中心完成。

订单服务:oauth2-client-order-server,微服务之一,接收到请求后会到认证中心验证。

用户服务:oauth2-client-user-server,微服务之二,接收到请求后会到认证中心验证。

客户端:例如 APP 端、web 端 等终端

上图描述了使用了 OAuth2 的客户端与微服务间的请求过程。大致的过程就是客户端用用户名和密码到认证服务端换取 token,返回给客户端,客户端拿着 token 去各个微服务请求数据接口,一般这个 token 是放到 header 中的。当微服务接到请求后,先要拿着 token 去认证服务端检查 token 的合法性,如果合法,再根据用户所属的角色及具有的权限动态的返回数据。

创建并配置认证服务端

配置最多的就是认证服务端,验证账号、密码,存储 token,检查 token ,刷新 token 等都是认证服务端的工作。

1、引入需要的 maven 包

 

spring-cloud-starter-oauth2包含了 spring-cloud-starter-security,所以不用再单独引入了。之所以引入 redis 包,是因为下面会介绍一种用 redis 存储 token 的方式。

2、配置好 application.yml

将项目基本配置设置好,并加入有关 redis 的配置,稍后会用到。

 

3、spring security 基础配置

 

使用@EnableWebSecurity注解修饰,并继承自WebSecurityConfigurerAdapter类。

这个类的重点就是声明 PasswordEncoder 和 AuthenticationManager两个 Bean。稍后会用到。其中 BCryptPasswordEncoder是一个密码加密工具类,它可以实现不可逆的加密,AuthenticationManager是为了实现 OAuth2 的 password 模式必须要指定的授权管理 Bean。

4、实现 UserDetailsService

如果你之前用过 Security 的话,那肯定对这个类很熟悉,它是实现用户身份验证的一种方式,也是最简单方便的一种。另外还有结合 AuthenticationProvider的方式,有机会讲 Security 的时候再展开来讲吧。

UserDetailsService的核心就是 loadUserByUsername方法,它要接收一个字符串参数,也就是传过来的用户名,返回一个 UserDetails对象。

 

这里为了做演示,把用户名、密码和所属角色都写在代码里了,正式环境中,这里应该是从数据库或者其他地方根据用户名将加密后的密码及所属角色查出来的。账号 admin ,密码 123456,稍后在换取 token 的时候会用到。并且给这个用户设置 “ROLE_ADMIN” 角色。

5、OAuth2 配置文件

创建一个配置文件继承自 AuthorizationServerConfigurerAdapter.

 

有三个 configure 方法的重写。

AuthorizationServerEndpointsConfigurer参数的重写

 

authenticationManage() 调用此方法才能支持 password 模式。

userDetailsService() 设置用户验证服务。

tokenStore() 指定 token 的存储方式。

redisTokenStore Bean 的定义如下:

 

ClientDetailsServiceConfigurer参数的重写,在这里定义各个端的约束条件。包括

ClientId、Client-Secret:这两个参数对应请求端定义的 cleint-id 和 client-secret

authorizedGrantTypes 可以包括如下几种设置中的一种或多种:

  • authorization_code:授权码类型。

  • implicit:隐式授权类型。

  • password:资源所有者(即用户)密码类型。

  • client_credentials:客户端凭据(客户端ID以及Key)类型。

  • refresh_token:通过以上授权获得的刷新令牌来获取新的令牌。

accessTokenValiditySeconds:token 的有效期

scopes:用来限制客户端访问的权限,在换取的 token 的时候会带上 scope 参数,只有在 scopes 定义内的,才可以正常换取 token。

上面代码中是使用 inMemory 方式存储的,将配置保存到内存中,相当于硬编码了。正式环境下的做法是持久化到数据库中,比如 mysql 中。

具体的做法如下:

1. 在数据库中增加表,并插入数据

 

注意: client_secret 字段不能直接是 secret 的原始值,需要经过加密。因为是用的 BCryptPasswordEncoder,所以最终插入的值应该是经过 BCryptPasswordEncoder.encode()之后的值。

2. 然后在配置文件 application.yml 中添加关于数据库的配置

 

Spring Boot 2.0 之后默认使用 hikari 作为数据库连接池。如果使用其他连接池需要引入相关包,然后对应的增加配置。

3. 在 OAuth2 配置类(OAuth2Config)中增加 DataSource 的注入

 

4. 将 public void configure(ClientDetailsServiceConfigurer clients)重写方法修改为如下:

 

还有一个重写的方法 public void configure(AuthorizationServerSecurityConfigurer security),这个方法限制客户端访问认证接口的权限。

 

第一行代码是允许客户端访问 OAuth2 授权接口,否则请求 token 会返回 401。

第二行和第三行分别是允许已授权用户访问 checkToken 接口和获取 token 接口。

完成之后,启动项目,如果你用的是 IDEA 会在下方的 Mapping 窗口中看到 oauth2 相关的 RESTful 接口。

 

主要有如下几个:

 

创建用户客户端项目

上面创建完成了认证服务端,下面开始创建一个客户端,对应到我们系统中的业务相关的微服务。我们假设这个微服务项目是管理用户相关数据的,所以叫做用户客户端。

1、引用相关的 maven 包

 

2、application.yml 配置文件

 

上面是常规配置信息以及 redis 配置,重点是下面的 security 的配置,这里的配置稍有不注意就会出现 401 或者其他问题。

client-id、client-secret 要和认证服务中的配置一致,如果是使用 inMemory 还是 jdbc 方式。

user-authorization-uri 是授权码认证方式需要的,下一篇文章再说。

access-token-uri 是密码模式需要用到的获取 token 的接口。

authorization.check-token-access 也是关键信息,当此服务端接收到来自客户端端的请求后,需要拿着请求中的 token 到认证服务端做 token 验证,就是请求的这个接口

3、资源配置文件

在 OAuth2 的概念里,所有的接口都被称为资源,接口的权限也就是资源的权限,所以 Spring Security OAuth2 中提供了关于资源的注解 @EnableResourceServer,和 @EnableWebSecurity的作用类似。

 

因为使用的是 redis 作为 token 的存储,所以需要特殊配置一下叫做 tokenService 的 Bean,通过这个 Bean 才能实现 token 的验证。

4、最后,添加一个 RESTful 接口

 

一个 RESTful 方法,只有当访问用户具有 ROLE_ADMIN 权限时才能访问,否则返回 401 未授权。

通过 Authentication 参数或者 SecurityContextHolder.getContext().getAuthentication() 可以拿到授权信息进行查看。

测试认证功能

1、启动认证服务端,启动端口为 6001

2、启动用户服务客户端,启动端口为6101

3、请求认证服务端获取 token

我是用 REST Client 来做访问请求的,请求格式如下:

 

假设咱们在一个 web 端使用,grant_type 是 password,表明这是使用 OAuth2 的密码模式。

username=admin 和 password=123456 就相当于在 web 端登录界面输入的用户名和密码,我们在认证服务端配置中固定了用户名是 admin 、密码是 123456,而线上环境中则应该通过查询数据库获取。

scope=all 是权限有关的,在认证服务的 OAuthConfig 中指定了 scope 为 all 。

Authorization 要加在请求头中,格式为 Basic 空格 base64(clientId:clientSecret),这个微服务客户端的 client-id 是 user-client,client-secret 是 user-secret-8888,将这两个值通过冒号连接,并使用 base64 编码(user-client:user-secret-8888)之后的值为 dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==,可以通过 https://www.sojson.com/base64.html 在线编码获取。

运行请求后,如果参数都正确的话,获取到的返回内容如下,是一段 json 格式

 

access_token :  就是之后请求需要带上的 token,也是本次请求的主要目的 token_type:为 bearer,这是 access token 最常用的一种形式 refresh_token:之后可以用这个值来换取新的 token,而不用输入账号密码 expires_in:token 的过期时间(秒)

4、用获取到的 token 请求资源接口

我们在用户客户端中定义了一个接口 http://localhost:6101/client-user/get,现在就拿着上一步获取的 token 来请求这个接口。

 

同样需要请求头 Authorization,格式为 bearer + 空格 + token,正常情况下根据接口的逻辑,会把 token 原样返回。

5、token 过期后,用 refresh_token 换取 access_token

一般都会设置 access_token 的过期时间小于 refresh_token 的过期时间,以便在 access_token 过期后,不用用户再次登录的情况下,获取新的 access_token。

 

grant_type 设置为 refresh_token。

refresh_token 设置为请求 token 时返回的 refresh_token 的值。

请求头加入 Authorization,格式依然是 Basic + 空格 + base64(client-id:client-secret)

请求成功后会返回和请求 token 同样的数据格式。

用 JWT 替换 redisToken

上面 token 的存储用的是 redis 的方案,Spring Security OAuth2 还提供了 jdbc 和 jwt 的支持,jdbc 的暂不考虑,现在来介绍用 JWT 的方式来实现 token 的存储。

用 JWT 的方式就不用把 token 再存储到服务端了,JWT 有自己特殊的加密方式,可以有效的防止数据被篡改,只要不把用户密码等关键信息放到 JWT 里就可以保证安全性。

认证服务端改造

先把有关 redis 的配置去掉。

添加 JwtConfig 配置类

 

JwtAccessTokenConverter是为了做 JWT 数据转换,这样做是因为 JWT 有自身独特的数据格式。如果没有了解过 JWT ,可以搜索一下先了解一下。

更改 OAuthConfig 配置类

 

注入 JWT 相关的 Bean,然后修改 configure(final AuthorizationServerEndpointsConfigurer endpoints) 方法为 JWT 存储模式。

改造用户客户端

修改 application.yml 配置文件

 

注意认证服务端 JwtAccessTokenConverter设置的 SigningKey 要和配置文件中的 key-value 相同,不然会导致无法正常解码 JWT ,导致验证不通过。

ResourceServerConfig 类的配置

 

运行请求 token 接口的请求

 

返回结果如下:

 

我们已经看到返回的 token 是 JWT 格式了,到 JWT 在线解码网站 https://jwt.io/ 或者 http://jwt.calebb.net/将 token 解码看一下

看到了没,user_name、client_id 等信息都在其中。

拿着返回的 token 请求用户客户端接口

 

增强 JWT

如果我想在 JWT 中加入额外的字段(比方说用户的其他信息)怎么办呢,当然可以。spring security oauth2 提供了 TokenEnhancer 增强器。其实不光 JWT ,RedisToken 的方式同样可以。

声明一个增强器

 

通过 oAuth2Authentication 可以拿到用户名等信息,通过这些我们可以在这里查询数据库或者缓存获取更多的信息,而这些信息都可以作为 JWT 扩展信息加入其中。

OAuthConfig 配置类修改

注入增强器

 

修改 configure(final AuthorizationServerEndpointsConfigurer endpoints)方法

 

再次请求 token ,返回内容中多了个刚刚加入的 jwt-ext 字段

 

用户客户端解析 JWT 数据

我们如果在 JWT 中加入了额外信息,这些信息我们可能会用到,而在接收到 JWT 格式的 token 之后,用户客户端要把 JWT 解析出来。

引入 JWT 包

 

加一个 RESTful 接口,在其中解析 JWT

 

同样注意其中签名的设置要与认证服务端相同。

用上一步的 token 请求上面的接口

 

返回内容如下:

 

以上就是 password 模式的完整过程。

源码地址: https://github.com/huzhicheng/spring-cloud-study/tree/master/oauth2 

 


相关阅读

http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

https://mp.weixin.qq.com/s?__biz=MzAxMjA0MDk2OA==&mid=2449469148&idx=1&sn=cf7f6f81134e8f43e6fc6a71e36f940a&scene=21#wechat_redirect



微信授权就是这个原理,Spring Cloud OAuth2 授权码模式

上一篇文章Spring Cloud OAuth2 实现单点登录介绍了使用 password 模式进行身份认证和单点登录。本篇介绍 Spring Cloud OAuth2 的另外一种授权模式-授权码模式。

授权码模式的认证过程是这样的:

1、用户客户端请求认证服务器的认证接口,并附上回调地址;

2、认证服务接口接收到认证请求后调整到自身的登录界面;

3、用户输入用户名和密码,点击确认,跳转到授权、拒绝提示页面(也可省略);

4、用户点击授权或者默认授权后,跳转到微服务客户端的回调地址,并传入参数 code;

5、回调地址一般是一个 RESTful 接口,此接口拿到 code 参数后,再次请求认证服务器的 token 获取接口,用来换取 access_token 等信息;

6、获取到 access_token 后,拿着 token 去请求各个微服务客户端的接口。

注意上面所说的用户客户端可以理解为浏览器、app 端,微服务客户端就是我们系统中的例如订单服务、用户服务等微服务,认证服务端就是用来做认证授权的服务,相对于认证服务端来说,各个业务微服务也可以称作是它的客户端。

认证服务端配置

认证服务端继续用上一篇文章的配置,代码不需要任何改变,只需要在数据库里加一条记录,来支持新加的微服务客户端的认证

我们要创建的客户端的 client-id 为 code-client,client-secret 为 code-secret-8888,但是同样需要加密,可以用如下代码获取:

 

除了以上这两个参数,要将 authorized_grant_types 设置为 authorization_code,refresh_token,web_server_redirect_uri 设置为回调地址,稍后微服务客户端会创建这个接口。

然后将这条记录组织好插入数据库中。

 

 

创建授权模式的微服务

引入 maven 包

 

引入 okhttp 和 thymeleaf 是因为要做一个简单的页面并模拟正常的认证过程。

配置文件 application.yml

 

创建 resourceConfig

 

使用 jwt 作为 token 的存储,注意允许 /login 接口无授权访问,这个地址是认证的回调地址,会返回 code 参数。

创建 application.java启动类

 

 

到这步可以先停一下了。我们把认证服务端和刚刚创建的认证客户端启动起来,就可以手工测试一下了。回调接口不是还没创建呢吗,没关系,我们权当那个地址现在就是为了接收 code 参数的。 1、在浏览器访问 /oauth/authorize 授权接口,接口地址为:

 

注意 response_type 参数设置为 code,redirect_uri 设置为数据库中插入的回调地址。

2、输入上面地址后,会自动跳转到认证服务端的登录页面,输入用户名、密码,这里用户名是 admin,密码是 123456

3、点击确定后,来到授权确认页面,页面上有 Authorize 和 Deny (授权和拒绝)两个按钮。可通过将 autoapprove 字段设置为 0 来取消此页面的展示,默认直接同意授权。 

4、点击同意授权后,跳转到了回调地址,虽然是 404 ,但是我们只是为了拿到 code 参数,注意地址后面的 code 参数。

5、拿到这个 code 参数是为了向认证服务器 /oauth/token 接口请求 access_token ,继续用 REST Client 发送请求,同样的,你也可以用 postman 等工具测试。

注意 grant_type 参数设置为 authorization_code,code 就是上一步回调地址中加上的,redirect_uri 仍然要带上,作为验证条件,如果不带或者与前面设置的不一致,会出现错误。

请求头 Authorization ,仍然是 Basic + 空格 + base64(client_id:client_secret),可以通过 https://www.sojson.com/base64.html 网站在线做 base64 编码。

code-client:code-secret-8888 通过 base64 编码后结果为 Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==

 

发送请求后,返回的 json 内容如下:

 

和上一篇文章 password 模式拿到的 token 内容是一致的,接下来的请求都需要带上 access_token 。

6、把获取到的 access_token 代入到下面的请求中 ${access_token} 的位置,就可以请求微服务中的需要授权访问的接口了。

 

接口内容如下:

 

 

经过以上的手工测试,证明此过程是通的,但是还没有达到自动化。如果你集成过微信登录,那你一定知道我们在回调地址中做了什么,拿到返回的 code 参数去 token 接口换取 access_token 对不对,没错,思路都是一样的,我们的回调接口中同样要拿 code 去换取 access_token。

为此,我做了一个简单的页面,并且在回调接口中请求获取 token 的接口。

创建简单的登录页面

在 resources 目录下创建 templates 目录,用来存放 thymeleaf 的模板,不做样式,只做最简单的演示,创建 index.html 模板,内容如下:

 

回调接口及其他接口

 

其中 index() 方法是为了展示 thymeleaf 模板,login 方法就是回调接口,这里用了 okhttp3 用作接口请求,请求认证服务端的 /oauth/token 接口来换取 access_token,只是把我们手工测试的步骤自动化了。

访问 index.html 页面

我们假设这个页面就是一个网站的首页,未登录的用户会在网站上看到登录按钮,我们访问这个页面:http://localhost:6102/client-authcode/index,看到的页面是这样的

接下来,点击登录按钮,通过上面的模板代码看出,点击后其实就是跳转到了我们手工测试第一步访问的那个地址,之后的操作和上面手工测试的是一致的,输入用户名密码、点击同意授权。

接下来,页面跳转回回调地址

最后,拿到 token 后的客户端,就可以将 token 加入到请求头后,去访问需要授权的接口了。

结合上一篇文章,我们就实现了 password 和 授权码两种模式的 oauth2 认证。

对应的源码地址为:

https://github.com/huzhicheng/spring-cloud-study/tree/master/oauth2/oauth2-client-authorization-code-server

 


相关阅读

http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

https://mp.weixin.qq.com/s?__biz=MzAxMjA0MDk2OA==&mid=2449469148&idx=1&sn=cf7f6f81134e8f43e6fc6a71e36f940a&scene=21#wechat_redirect

参考:

http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

https://mp.weixin.qq.com/s/tXIycDTHw4nruuMP7xirQA

系统架构说明

认证中心:oauth2-auth-server,OAuth2 主要实现端,Token 的生成、刷新、验证都在认证中心完成。

订单服务:oauth2-client-order-server,微服务之一,接收到请求后会到认证中心验证。

用户服务:oauth2-client-user-server,微服务之二,接收到请求后会到认证中心验证。

客户端:例如 APP 端、web 端 等终端

上图描述了使用了 OAuth2 的客户端与微服务间的请求过程。大致的过程就是客户端用用户名和密码到认证服务端换取 token,返回给客户端,客户端拿着 token 去各个微服务请求数据接口,一般这个 token 是放到 header 中的。当微服务接到请求后,先要拿着 token 去认证服务端检查 token 的合法性,如果合法,再根据用户所属的角色及具有的权限动态的返回数据。

创建并配置认证服务端

配置最多的就是认证服务端,验证账号、密码,存储 token,检查 token ,刷新 token 等都是认证服务端的工作。

1、引入需要的 maven 包


<dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-web</artifactId></dependency><dependency>   <groupId>org.springframework.cloud</groupId>   <artifactId>spring-cloud-starter-oauth2</artifactId></dependency><dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-actuator</artifactId></dependency>

spring-cloud-starter-oauth2包含了 spring-cloud-starter-security,所以不用再单独引入了。之所以引入 redis 包,是因为下面会介绍一种用 redis 存储 token 的方式。

2、配置好 application.yml

将项目基本配置设置好,并加入有关 redis 的配置,稍后会用到。


spring:application:  name: auth-serverredis:  database: 2  host: localhost  port: 32768  password: 1qaz@WSX  jedis:    pool:      max-active: 8      max-idle: 8      min-idle: 0  timeout: 100msserver:port: 6001management:endpoint:  health:    enabled: true

3、spring security 基础配置


@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {   @Bean   public PasswordEncoder passwordEncoder() {       return new BCryptPasswordEncoder();  }   @Bean   @Override   public AuthenticationManager authenticationManagerBean() throws Exception {       return super.authenticationManagerBean();  }   /**    * 允许匿名访问所有接口 主要是 oauth 接口    * @param http    * @throws Exception    */   @Override   protected void configure(HttpSecurity http) throws Exception {       http.authorizeRequests()              .antMatchers("/**").permitAll();  }}

使用@EnableWebSecurity注解修饰,并继承自WebSecurityConfigurerAdapter类。

这个类的重点就是声明 PasswordEncoder 和 AuthenticationManager两个 Bean。稍后会用到。其中 BCryptPasswordEncoder是一个密码加密工具类,它可以实现不可逆的加密,AuthenticationManager是为了实现 OAuth2 的 password 模式必须要指定的授权管理 Bean。

4、实现 UserDetailsService

如果你之前用过 Security 的话,那肯定对这个类很熟悉,它是实现用户身份验证的一种方式,也是最简单方便的一种。另外还有结合 AuthenticationProvider的方式,有机会讲 Security 的时候再展开来讲吧。

UserDetailsService的核心就是 loadUserByUsername方法,它要接收一个字符串参数,也就是传过来的用户名,返回一个 UserDetails对象。


@Slf4j@Component(value = "kiteUserDetailsService")public class KiteUserDetailsService implements UserDetailsService {   @Autowired   private PasswordEncoder passwordEncoder;   @Override   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {       log.info("usernameis:" + username);       // 查询数据库操作       if(!username.equals("admin")){           throw new UsernameNotFoundException("the user is not found");      }else{           // 用户角色也应在数据库中获取           String role = "ROLE_ADMIN";           List<SimpleGrantedAuthority> authorities = new ArrayList<>();           authorities.add(new SimpleGrantedAuthority(role));           // 线上环境应该通过用户名查询数据库获取加密后的密码           String password = passwordEncoder.encode("123456");           return new org.springframework.security.core.userdetails.User(username,password, authorities);      }  }}

这里为了做演示,把用户名、密码和所属角色都写在代码里了,正式环境中,这里应该是从数据库或者其他地方根据用户名将加密后的密码及所属角色查出来的。账号 admin ,密码 123456,稍后在换取 token 的时候会用到。并且给这个用户设置 “ROLE_ADMIN” 角色。

5、OAuth2 配置文件

创建一个配置文件继承自 AuthorizationServerConfigurerAdapter.


@Configuration@EnableAuthorizationServerpublic class OAuth2Config extends AuthorizationServerConfigurerAdapter {   @Autowired   public PasswordEncoder passwordEncoder;   @Autowired   public UserDetailsService kiteUserDetailsService;   @Autowired   private AuthenticationManager authenticationManager;   @Autowired   private TokenStore redisTokenStore;   @Override   public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {       /**        * redis token 方式        */       endpoints.authenticationManager(authenticationManager)              .userDetailsService(kiteUserDetailsService)              .tokenStore(redisTokenStore);  }   @Override   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {       clients.inMemory()              .withClient("order-client")              .secret(passwordEncoder.encode("order-secret-8888"))              .authorizedGrantTypes("refresh_token", "authorization_code", "password")              .accessTokenValiditySeconds(3600)              .scopes("all")              .and()              .withClient("user-client")              .secret(passwordEncoder.encode("user-secret-8888"))              .authorizedGrantTypes("refresh_token", "authorization_code", "password")              .accessTokenValiditySeconds(3600)              .scopes("all");  }   @Override   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {       security.allowFormAuthenticationForClients();       security.checkTokenAccess("isAuthenticated()");       security.tokenKeyAccess("isAuthenticated()");  }}

有三个 configure 方法的重写。

AuthorizationServerEndpointsConfigurer参数的重写


endpoints.authenticationManager(authenticationManager)              .userDetailsService(kiteUserDetailsService)              .tokenStore(redisTokenStore);

authenticationManage() 调用此方法才能支持 password 模式。

userDetailsService() 设置用户验证服务。

tokenStore() 指定 token 的存储方式。

redisTokenStore Bean 的定义如下:


@Configurationpublic class RedisTokenStoreConfig {   @Autowired   private RedisConnectionFactory redisConnectionFactory;   @Bean   public TokenStore redisTokenStore (){       return new RedisTokenStore(redisConnectionFactory);  }}

ClientDetailsServiceConfigurer参数的重写,在这里定义各个端的约束条件。包括

ClientId、Client-Secret:这两个参数对应请求端定义的 cleint-id 和 client-secret

authorizedGrantTypes 可以包括如下几种设置中的一种或多种:

  • authorization_code:授权码类型。
  • implicit:隐式授权类型。
  • password:资源所有者(即用户)密码类型。
  • client_credentials:客户端凭据(客户端ID以及Key)类型。
  • refresh_token:通过以上授权获得的刷新令牌来获取新的令牌。

accessTokenValiditySeconds:token 的有效期

scopes:用来限制客户端访问的权限,在换取的 token 的时候会带上 scope 参数,只有在 scopes 定义内的,才可以正常换取 token。

上面代码中是使用 inMemory 方式存储的,将配置保存到内存中,相当于硬编码了。正式环境下的做法是持久化到数据库中,比如 mysql 中。

具体的做法如下:

1. 在数据库中增加表,并插入数据


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));INSERT INTO oauth_client_details  (client_id, client_secret, scope, authorized_grant_types,  web_server_redirect_uri, authorities, access_token_validity,  refresh_token_validity, additional_information, autoapprove)VALUES  ('user-client', '$2a$10$o2l5kA7z.Caekp72h5kU7uqdTDrlamLq.57M1F6ulJln9tRtOJufq', 'all',   'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);INSERT INTO oauth_client_details  (client_id, client_secret, scope, authorized_grant_types,  web_server_redirect_uri, authorities, access_token_validity,  refresh_token_validity, additional_information, autoapprove)VALUES  ('order-client', '$2a$10$GoIOhjqFKVyrabUNcie8d.ADX.qZSxpYbO6YK4L2gsNzlCIxEUDlW', 'all',   'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);

注意: client_secret 字段不能直接是 secret 的原始值,需要经过加密。因为是用的 BCryptPasswordEncoder,所以最终插入的值应该是经过 BCryptPasswordEncoder.encode()之后的值。

2. 然后在配置文件 application.yml 中添加关于数据库的配置


spring:datasource:  url: jdbc:mysql://localhost:3306/spring_cloud?characterEncoding=UTF-8&useSSL=false  username: root  password: password  hikari:    connection-timeout: 30000    idle-timeout: 600000    max-lifetime: 1800000    maximum-pool-size: 9  

Spring Boot 2.0 之后默认使用 hikari 作为数据库连接池。如果使用其他连接池需要引入相关包,然后对应的增加配置。

3. 在 OAuth2 配置类(OAuth2Config)中增加 DataSource 的注入


@Autowiredprivate DataSource dataSource;

4. 将 public void configure(ClientDetailsServiceConfigurer clients)重写方法修改为如下:


@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);jcsb.passwordEncoder(passwordEncoder);}

还有一个重写的方法 public void configure(AuthorizationServerSecurityConfigurer security),这个方法限制客户端访问认证接口的权限。


security.allowFormAuthenticationForClients();security.checkTokenAccess("isAuthenticated()");security.tokenKeyAccess("isAuthenticated()");

第一行代码是允许客户端访问 OAuth2 授权接口,否则请求 token 会返回 401。

第二行和第三行分别是允许已授权用户访问 checkToken 接口和获取 token 接口。

完成之后,启动项目,如果你用的是 IDEA 会在下方的 Mapping 窗口中看到 oauth2 相关的 RESTful 接口。

主要有如下几个:


POST /oauth/authorize 授权码模式认证授权接口GET/POST /oauth/token 获取 token 的接口POST /oauth/check_token 检查 token 合法性接口
创建用户客户端项目

上面创建完成了认证服务端,下面开始创建一个客户端,对应到我们系统中的业务相关的微服务。我们假设这个微服务项目是管理用户相关数据的,所以叫做用户客户端。

1、引用相关的 maven 包


<dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-web</artifactId></dependency><dependency>   <groupId>org.springframework.cloud</groupId>   <artifactId>spring-cloud-starter-oauth2</artifactId></dependency><dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-data-redis</artifactId></dependency>

2、application.yml 配置文件


spring:application:  name: client-userredis:  database: 2  host: localhost  port: 32768  password: 1qaz@WSX  jedis:    pool:      max-active: 8      max-idle: 8      min-idle: 0  timeout: 100msserver:port: 6101servlet:  context-path: /client-usersecurity:oauth2:  client:    client-id: user-client    client-secret: user-secret-8888    user-authorization-uri: http://localhost:6001/oauth/authorize    access-token-uri: http://localhost:6001/oauth/token  resource:    id: user-client    user-info-uri: user-info  authorization:    check-token-access: http://localhost:6001/oauth/check_token

上面是常规配置信息以及 redis 配置,重点是下面的 security 的配置,这里的配置稍有不注意就会出现 401 或者其他问题。

client-id、client-secret 要和认证服务中的配置一致,如果是使用 inMemory 还是 jdbc 方式。

user-authorization-uri 是授权码认证方式需要的,下一篇文章再说。

access-token-uri 是密码模式需要用到的获取 token 的接口。

authorization.check-token-access 也是关键信息,当此服务端接收到来自客户端端的请求后,需要拿着请求中的 token 到认证服务端做 token 验证,就是请求的这个接口

3、资源配置文件

在 OAuth2 的概念里,所有的接口都被称为资源,接口的权限也就是资源的权限,所以 Spring Security OAuth2 中提供了关于资源的注解 @EnableResourceServer,和 @EnableWebSecurity的作用类似。


@Configuration@EnableResourceServer@EnableGlobalMethodSecurity(prePostEnabled = true)public class ResourceServerConfig extends ResourceServerConfigurerAdapter {   @Value("${security.oauth2.client.client-id}")   private String clientId;   @Value("${security.oauth2.client.client-secret}")   private String secret;   @Value("${security.oauth2.authorization.check-token-access}")   private String checkTokenEndpointUrl;   @Autowired   private RedisConnectionFactory redisConnectionFactory;   @Bean   public TokenStore redisTokenStore (){       return new RedisTokenStore(redisConnectionFactory);  }   @Bean   public RemoteTokenServices tokenService() {       RemoteTokenServices tokenService = new RemoteTokenServices();       tokenService.setClientId(clientId);       tokenService.setClientSecret(secret);       tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);       return tokenService;  }   @Override   public void configure(ResourceServerSecurityConfigurer resources) throws Exception {       resources.tokenServices(tokenService());  }}

因为使用的是 redis 作为 token 的存储,所以需要特殊配置一下叫做 tokenService 的 Bean,通过这个 Bean 才能实现 token 的验证。

4、最后,添加一个 RESTful 接口


@Slf4j@RestControllerpublic class UserController {   @GetMapping(value = "get")   //@PreAuthorize("hasAuthority('ROLE_ADMIN')")   @PreAuthorize("hasAnyRole('ROLE_ADMIN')")   public Object get(Authentication authentication){       //Authentication authentication = SecurityContextHolder.getContext().getAuthentication();       authentication.getCredentials();       OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();       String token = details.getTokenValue();       return token;  }}

一个 RESTful 方法,只有当访问用户具有 ROLE_ADMIN 权限时才能访问,否则返回 401 未授权。

通过 Authentication 参数或者 SecurityContextHolder.getContext().getAuthentication() 可以拿到授权信息进行查看。

测试认证功能

1、启动认证服务端,启动端口为 6001

2、启动用户服务客户端,启动端口为6101

3、请求认证服务端获取 token

我是用 REST Client 来做访问请求的,请求格式如下:


POST http://localhost:6001/oauth/token?grant_type=password&username=admin&password=123456&scope=allAccept: */*Cache-Control: no-cacheAuthorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

假设咱们在一个 web 端使用,grant_type 是 password,表明这是使用 OAuth2 的密码模式。

username=admin 和 password=123456 就相当于在 web 端登录界面输入的用户名和密码,我们在认证服务端配置中固定了用户名是 admin 、密码是 123456,而线上环境中则应该通过查询数据库获取。

scope=all 是权限有关的,在认证服务的 OAuthConfig 中指定了 scope 为 all 。

Authorization 要加在请求头中,格式为 Basic 空格 base64(clientId:clientSecret),这个微服务客户端的 client-id 是 user-client,client-secret 是 user-secret-8888,将这两个值通过冒号连接,并使用 base64 编码(user-client:user-secret-8888)之后的值为 dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==,可以通过 https://www.sojson.com/base64.html 在线编码获取。

运行请求后,如果参数都正确的话,获取到的返回内容如下,是一段 json 格式


{ "access_token": "9f958300-5005-46ea-9061-323c9e6c7a4d", "token_type": "bearer", "refresh_token": "0f5871f5-98f1-405e-848e-80f641bab72e", "expires_in": 3599, "scope": "all"}

access_token :  就是之后请求需要带上的 token,也是本次请求的主要目的 token_type:为 bearer,这是 access token 最常用的一种形式 refresh_token:之后可以用这个值来换取新的 token,而不用输入账号密码 expires_in:token 的过期时间(秒)

4、用获取到的 token 请求资源接口

我们在用户客户端中定义了一个接口 http://localhost:6101/client-user/get,现在就拿着上一步获取的 token 来请求这个接口。


GET http://localhost:6101/client-user/getAccept: */*Cache-Control: no-cacheAuthorization: bearer ce334918-e666-455a-8ecd-8bd680415d84

同样需要请求头 Authorization,格式为 bearer + 空格 + token,正常情况下根据接口的逻辑,会把 token 原样返回。

5、token 过期后,用 refresh_token 换取 access_token

一般都会设置 access_token 的过期时间小于 refresh_token 的过期时间,以便在 access_token 过期后,不用用户再次登录的情况下,获取新的 access_token。


### 换取 access_tokenPOST http://localhost:6001/oauth/token?grant_type=refresh_token&refresh_token=706dac10-d48e-4795-8379-efe8307a2282Accept: */*Cache-Control: no-cacheAuthorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

grant_type 设置为 refresh_token。

refresh_token 设置为请求 token 时返回的 refresh_token 的值。

请求头加入 Authorization,格式依然是 Basic + 空格 + base64(client-id:client-secret)

请求成功后会返回和请求 token 同样的数据格式。

用 JWT 替换 redisToken

上面 token 的存储用的是 redis 的方案,Spring Security OAuth2 还提供了 jdbc 和 jwt 的支持,jdbc 的暂不考虑,现在来介绍用 JWT 的方式来实现 token 的存储。

用 JWT 的方式就不用把 token 再存储到服务端了,JWT 有自己特殊的加密方式,可以有效的防止数据被篡改,只要不把用户密码等关键信息放到 JWT 里就可以保证安全性。

认证服务端改造

先把有关 redis 的配置去掉。

添加 JwtConfig 配置类

@Configurationpublic class JwtTokenConfig {   @Bean   public TokenStore jwtTokenStore() {       return new JwtTokenStore(jwtAccessTokenConverter());  }   @Bean   public JwtAccessTokenConverter jwtAccessTokenConverter() {       JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();       accessTokenConverter.setSigningKey("dev");       return accessTokenConverter;  }}

JwtAccessTokenConverter是为了做 JWT 数据转换,这样做是因为 JWT 有自身独特的数据格式。如果没有了解过 JWT ,可以搜索一下先了解一下。

更改 OAuthConfig 配置类

@Autowiredprivate TokenStore jwtTokenStore;@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;@Overridepublic void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {       /**        * 普通 jwt 模式        */        endpoints.tokenStore(jwtTokenStore)              .accessTokenConverter(jwtAccessTokenConverter)              .userDetailsService(kiteUserDetailsService)               /**                * 支持 password 模式                */              .authenticationManager(authenticationManager);}

注入 JWT 相关的 Bean,然后修改 configure(final AuthorizationServerEndpointsConfigurer endpoints) 方法为 JWT 存储模式。

改造用户客户端

修改 application.yml 配置文件

security:oauth2:  client:    client-id: user-client    client-secret: user-secret-8888    user-authorization-uri: http://localhost:6001/oauth/authorize    access-token-uri: http://localhost:6001/oauth/token  resource:    jwt:      key-uri: http://localhost:6001/oauth/token_key      key-value: dev

注意认证服务端 JwtAccessTokenConverter设置的 SigningKey 要和配置文件中的 key-value 相同,不然会导致无法正常解码 JWT ,导致验证不通过。

ResourceServerConfig 类的配置

@Configuration@EnableResourceServer@EnableGlobalMethodSecurity(prePostEnabled = true)public class ResourceServerConfig extends ResourceServerConfigurerAdapter {   @Bean   public TokenStore jwtTokenStore() {       return new JwtTokenStore(jwtAccessTokenConverter());  }   @Bean   public JwtAccessTokenConverter jwtAccessTokenConverter() {       JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();       accessTokenConverter.setSigningKey("dev");       accessTokenConverter.setVerifierKey("dev");       return accessTokenConverter;  }   @Autowired   private TokenStore jwtTokenStore;   @Override   public void configure(ResourceServerSecurityConfigurer resources) throws Exception {       resources.tokenStore(jwtTokenStore);  }}
运行请求 token 接口的请求

POST http://localhost:6001/oauth/token?grant_type=password&username=admin&password=123456&scope=allAccept: */*Cache-Control: no-cacheAuthorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

返回结果如下:


{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJleHAiOjE1NzE3NzU4OTQsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiZjdkMjg4NDUtMmU2ZC00ZmRjLTg1OGYtMWNiY2RlNzI1ZmMyIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQifQ.vk_msYtbrAr93h5sK4wy6EC2_wRD_cD_UBS8O6eRziw", "expires_in": 3599, "scope": "all", "jti": "8cca29af-ea77-4fe6-9fe1-327415dcd21d"}

我们已经看到返回的 token 是 JWT 格式了,到 JWT 在线解码网站 https://jwt.io/ 或者 http://jwt.calebb.net/将 token 解码看一下

看到了没,user_name、client_id 等信息都在其中。

拿着返回的 token 请求用户客户端接口

GET http://localhost:6101/client-user/getAccept: */*Cache-Control: no-cacheAuthorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM

增强 JWT

如果我想在 JWT 中加入额外的字段(比方说用户的其他信息)怎么办呢,当然可以。spring security oauth2 提供了 TokenEnhancer 增强器。其实不光 JWT ,RedisToken 的方式同样可以。

声明一个增强器

public class JWTokenEnhancer implements TokenEnhancer {   @Override   public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {       Map<String, Object> info = new HashMap<>();       info.put("jwt-ext", "JWT 扩展信息");      ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);       return oAuth2AccessToken;  }}

通过 oAuth2Authentication 可以拿到用户名等信息,通过这些我们可以在这里查询数据库或者缓存获取更多的信息,而这些信息都可以作为 JWT 扩展信息加入其中。

OAuthConfig 配置类修改

注入增强器


@Autowiredprivate TokenEnhancer jwtTokenEnhancer;@Beanpublic TokenEnhancer jwtTokenEnhancer(){   return new JWTokenEnhancer();}

修改 configure(final AuthorizationServerEndpointsConfigurer endpoints)方法


@Overridepublic void configure( final AuthorizationServerEndpointsConfigurer endpoints ) throws Exception{/*** jwt 增强模式*/TokenEnhancerChain enhancerChain = new TokenEnhancerChain();List<TokenEnhancer> enhancerList = new ArrayList<>();enhancerList.add( jwtTokenEnhancer );enhancerList.add( jwtAccessTokenConverter );enhancerChain.setTokenEnhancers( enhancerList );endpoints.tokenStore( jwtTokenStore ).userDetailsService( kiteUserDetailsService )/*** 支持 password 模式*/.authenticationManager( authenticationManager ).tokenEnhancer( enhancerChain ).accessTokenConverter( jwtAccessTokenConverter );}
再次请求 token ,返回内容中多了个刚刚加入的 jwt-ext 字段

{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.5j4hNsVpktG2iKxNqR-q1rfcnhlyV3M6HUBx5cd6PiQ", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImE0NTUxZDllLWI3ZWQtNDc1OS1iMmYxLWYwYjliMjFjYTQyYyIsImV4cCI6MTU3MTc3NzU3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJmNTI3ODJlOS0wOGRjLTQ2NGUtYmJhYy03OTMwNzYwYmZiZjciLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.UQMf140CG8U0eWh08nGlctpIye9iJ7p2i6NYHkGAwhY", "expires_in": 3599, "scope": "all", "jwt-ext": "JWT 扩展信息", "jti": "a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c"}

用户客户端解析 JWT 数据

我们如果在 JWT 中加入了额外信息,这些信息我们可能会用到,而在接收到 JWT 格式的 token 之后,用户客户端要把 JWT 解析出来。

引入 JWT 包

<dependency>   <groupId>io.jsonwebtoken</groupId>   <artifactId>jjwt</artifactId>   <version>0.9.1</version></dependency>
加一个 RESTful 接口,在其中解析 JWT

@GetMapping(value = "jwt")@PreAuthorize("hasAnyRole('ROLE_ADMIN')")public Object jwtParser(Authentication authentication){   authentication.getCredentials();   OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();   String jwtToken = details.getTokenValue();   Claims claims = Jwts.parser()              .setSigningKey("dev".getBytes(StandardCharsets.UTF_8))              .parseClaimsJws(jwtToken)              .getBody();   return claims;}

同样注意其中签名的设置要与认证服务端相同。

用上一步的 token 请求上面的接口

### 解析 jwtGET http://localhost:6101/client-user/jwtAccept: */*Cache-Control: no-cacheAuthorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.5j4hNsVpktG2iKxNqR-q1rfcnhlyV3M6HUBx5cd6PiQ

返回内容如下:


{ "user_name": "admin", "jwt-ext": "JWT 扩展信息", "scope": [   "all"], "exp": 1571745178, "authorities": [   "ROLE_ADMIN"], "jti": "a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c", "client_id": "user-client"}

以上就是 password 模式的完整过程,源码放到了 github 上,有需要的可以去看一下。

源码地址: https://github.com/huzhicheng/spring-cloud-study/tree/master/oauth2

mysql数据库连接超过8小时失效的解决方案(springboot)

最近由于业务需要,开发了一个考勤服务,昨天部署上去,当时测试业务OK,今天早上发现考勤无法正常上传服务器。 后台日志显示数据库连接不上。

使用IDE连接数据库正常,服务确显示链接超时。
查看错误日志,发现了如下的日志:

经查发现原来是mysql默认会将8个小时内没有操作过的数据库连接断开。

解决方法:

1)设置Mysql链接超时时间

mysql数据库有一个wait_timeout的配置,默认值为28800(即8小时).

在默认配置不改变的情况下,如果连续8小时内都没有访问数据库的操作,再次访问mysql数据库的时候,mysql数据库会拒绝访问。

查看超时时间:
show variables like  ‘%timeout%’;
闲置连接的超时时间由wait_timeout控制、默认8小时。

解决方案:

第一种途径使用命令行在mysql提示符下

>set  global wait_timeout=1814400
这种方式是一种临时方法,重启服务就会返回默认值了。

第二种途径修改my.ini配置文件

[mysqld]
wait_timeout=31536000
interactive_timeout=31536000 

在mysqld下面添加以上两行,后面的数字是时间

重启服务

2:代码服务中设置数据源超时检测

在application.yaml中设置datasource的时候,加入如下设置:

      #验证连接的有效性
      test-while-idle: true 
      #获取连接时候验证,会影响性能
      test-on-borrow: true
      #在连接归还到连接池时是否测试该连接
      secondary.test-on-return: false
      validation-query: SELECT 1 FROM DUAL
      #空闲连接回收的时间间隔,与test-while-idle一起使用,设置5分钟
      time-between-eviction-runs-millis: 300000
      #连接池空闲连接的有效时间 ,设置30分钟
      min-evictable-idle-time-millis: 600000
      initial-size: 5
      #指定连接池中最大的活跃连接数.
      max-active: 50
      #指定连接池等待连接返回的最大等待时间,毫秒单位.
      max-wait: 60000
      #指定必须保持连接的最小值
      min-idle: 5

它的作用就是会每隔一段时间向mysql进行一次连接可用确认。

测试:

  1. 将mysql的默认时间改为60s,方便测试。(这俩都设置)
set global interactive_timeout=60;
set global wait_timeout=60;
  1. 不设置上面的自动连接确认,隔两分钟请求测试接口。
    第一次请求正常,第二次请求又报上面的错误。
  2. 设置自动连接确认,隔两分钟请求测试接口。
    多次请求均正常。

也可以将上面两个参数设置大一些,比如一年等。需要注意这俩参数的作用级别是“数据库实例”。注意对其他库的影响。

使用Poedit翻译&汉化WordPress主题和插件

WordPress有非常多的主题和插件,但是很多都是老外开发的,没有默认包含中文语言包,导致很多朋友无法直接使用。一直来,倡萌都在分享一些不错的WordPress插件,然后尽可能进行汉化翻译,但是一个人的精力毕竟有限。

所以,今天分享下使用Poedit翻译/汉化WordPress主题和插件的教程,希望可以帮助到一些朋友。

PoEdit 是一款.po文件编辑器,PO即Portable Object,可跨平台对象的意思,PoEdit常用来汉化各种程序,它内置于wxWidgets工具中,可以在所有它支持的平台上运行,如Unix下的GTK+以及Windows。PoEdit提供更方便的办法编辑目录,而不用通过启动VI文本编辑器,手动编辑。百度百科

估计谁看了百科的介绍都有点云里雾里,下面就直接进入正题吧。

下载安装Poedit

下载并安装Poedit:https://poedit.net/(建议务必到官方下载,如果实在没办法官方下载,请自己百度下载吧)安装过程就不想讲述了。

编辑和保存翻译

1、安装好以后,运行poedit,然后就可以看到如下界面,通常我们直接点击“翻译WordPress主题或插件”:

2、然后会弹出一个窗口,将需要翻译的插件文件夹拖放到上面,然后按照下图选择语言,继续:

3、稍等一下,Poedit就为检索到了需要翻译的字段,非常智能化。

根据倡萌的经验,我们需要修改一下“编目属性”里的“复数形式”,如下图,选择“使用自定义复数表达式”,然后填入 ‪nplurals=2; plural=(n > 1);,这样就可以确保单复数字段可以正常识别和翻译

在“编目属性”的“源关键字”选项卡,我们可以看到Poedit已经自动添加了关键字,也就是只要在WordPress主题或插件中,包含了这些关键字的字段,都可以检索和翻译。一般而言,我们不需要去修改这里的设置,除非你很清楚源关键字到底是什么。

4、接下来,我们就可以逐条进行翻译,上面是英文,下面就是输入中文的地方。

注意:翻译一定要非常注意高亮字段,比如下图的源文本中包含了高亮显示的 %s,我们需要原样复制到翻译中,必须确保可以正常高亮显示,否则可能会导致这条翻译无法正常生效。

Poedit 的右侧栏已经包含了自动翻译建议,免费版一般采用的是微软翻译,如果你要更好的翻译质量,那可以购买他们的最高级的授权,是按年收费的。

5、翻译以后,我们第一次保存,会 弹出如下窗口,一般文件名默认即可,文件名后面的部分,简体中文为 zh_CN,繁体中文为 zh_TW

保存以后,一般会同时生成一个.mo格式的文件,这个文件就是显示翻译必须的,而 .po 格式的文件是Poedit软件可编辑的格式,也就是说,我们只需要去编辑.po格式的文件,保存时会自动更新.mo文件。

上传翻译到对应目录

一般而言,你保存翻译文件的时候,Poedit会智能地识别目录,然后将文件保存在插件的指定语言目录中,这样一般就可以生效了。比如上图的 search-meter/languages 目录就是Search Meter 插件的语言目录。

但是,通常我们都需要在后台更新主题和插件,这样会导致,每次更新都会删除掉语言包,为了解决这个问题,WordPress还提供了一个很好的解决办法。

  • 插件:将插件的.mo和.po文件上传到 /wp-content/languages/plugins 目录即可
  • 主题:主题默认保存的语言包,是不包含主题名称的,你会看到 zh_CN.mo 和 zh_CN.po,所以我们要修改下名字为 主题名+zh_CN(比如2020主题的语言包名字为 twentytwenty-zh_CN),然后将语言文件上传到/wp-content/languages/themes 目录即可

注:严格来说,上面说的主题名其实应该是主题注册翻译的时候设置的textdomain,通常规范的主题设置都会将textdomain设置为主题文件夹的名称。

如果你要了解更多,可以看下:《让WordPress主题支持语言本地化

如何对新版本进行翻译

WordPress主题和插件会不断升级更新,如果我们要对新版本的主题或插件更新翻译,如何做呢?

其实很简单,只需要将之前的语言包文件复制到主题或插件自身的语言目录,然后编辑 .po 文件,点击界面的“从代码更新”即可获取到新的字段,翻译后,保存即可。

设置Poedit首选项

如果你觉得每次翻译新的项目,都需要填写一些信息太麻烦了,你可以在首选项设置一些默认的设置,比如翻译者的名称和电子邮件等:

一些翻译建议

翻译最重要的就是质量。如果翻译不准确,可能会给使用者造成困扰,甚至会导致使用者误操作。所以,翻译是一项细活,翻译完了以后,还要去后台插件的界面去仔细看下,哪些句子或词语翻译不够准确,进行必要的修正。

并不是所有的主题和插件都非常规范,有时候,你会发现无法使用Poedit获取翻译字段,或者获取到的字段不完整。如果遇到这些问题,建议联系主题或插件的作者反馈,希望他们进行完善和修复。

如果你发现翻译的语言包在主题或插件的语言目录中不生效,那就尝试复制语言包到 /wp-content/languages/plugins(插件)或/wp-content/languages/themes (主题),或许就可以了。

如果你翻译汉化了主题或插件,学会分享出来,让更多人的人受益。只要人人都奉献一点爱,世界将变成美好的人间!

拓展阅读:

宝塔面板如何升级PHP版本

如果我们的wordpress网站是用宝塔面板一键部署的,那么在升级php版本的时候,我们可以直接在宝塔面板后台直接安装升级就可以了。

具体操作步骤如下:

在这里插入图片描述

首先打开“软件商店”,在“运行环境”下找到需要升级的php版本

然后到“网站管理”找到对应的网站,点击“设置”,切换到对应PHP版本即可。

在这里插入图片描述

集成mongoDB遇到的问题

一:admin用户登录鉴权失败

当mongoDB设置了账号密码时,且设置的db为admin(角色为root),此时登录且操作都是OK的,连接可视化工具进行各种操作也是ok的,但是springboot项目里面却是一直超时

原因:设置的账号密码是admin数据库的,然后连接的是自己的其他数据库(cloud_demo),使得一直超时

解决方案:

1)创建有(cloud_demo)数据库的权限的用户,不适用admin

2)比较简单,直接在springboot项目的mongoDB的uri后面拼接就行了 (在mongodb4中未测试通过)

mongodb://name:password@localhost:27017/cloud_demo?authSource=admin


二:密码鉴权失败

当密码有: @两个符号的时候,需要使用%3A替代:,使用%40替换@符号

Redis设置密码

设置密码有两种方式。

1. 命令行设置密码。

运行cmd切换到redis根目录,先启动服务端

>redis-server.exe

另开一个cmd切换到redis根目录,启动客户端

>redis-cli.exe -h 127.0.0.1 -p 6379

客户端使用config get requirepass命令查看密码

>config get requirepass
1)"requirepass"
2)""    //默认空

客户端使用config set requirepass yourpassword命令设置密码

>config set requirepass 123456
>OK

一旦设置密码,必须先验证通过密码,否则所有操作不可用

>config get requirepass
(error)NOAUTH Authentication required

使用auth password验证密码

>auth 123456
>OK
>config get requirepass
1)"requirepass"
2)"123456"

也可以退出重新登录

redis-cli.exe -h 127.0.0.1 -p 6379 -a 123456

命令行设置的密码在服务重启后失效,所以一般不使用这种方式。

2. 配置文件设置密码

在redis根目录下找到redis.conf配置文件,搜索requirepass,找到注释密码行,添加密码如下:

# requirepass foobared
requirepass tenny     //注意,行前不能有空格


指定conf配置文件 重启服务后,客户端重新登录后查看

>redis-cli -h 127.0.0.1 -p 6379 -a 123456
>config get requirepass
1)"requirepass"
2)"123456"

linux 下搜索字段

方法如下:

1、命令模式下输入“/字符串”,例如“/Section 3”。

2、如果查找下一个,按“n”即可。

要自当前光标位置向上搜索,请使用以下命令:

/pattern Enter

其中,pattern表示要搜索的特定字符序列。

要自当前光标位置向下搜索,请使用以下命令

Redius 安装

Linux 下安装 Redius

下载地址:http://redis.io/download,下载最新稳定版本。

本教程使用的最新文档版本为5.0.9,下载并安装:

 
$ wget http://download.redis.io/releases/redis-5.0.9.tar.gz
$ tar xzf redis-5.0.9.tar.gz
$ cd redis-5.0.9
$ make

make完后 redis 5.0.9目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli,两个程序位于安装目录 src 目录下:

下面启动redis服务.

$ cd src 
$ ./redis-server

注意这种方式启动redis 使用的是默认配置。也可以通过启动参数告诉redis使用指定配置文件使用下面命令启动。

$ cd src 
$ ./redis-server ../redis.conf

redis.conf 是一个默认的配置文件。我们可以根据需要使用自己的配置文件。

启动redis服务进程后,就可以使用测试客户端程序redis-cli和redis服务交互了。 比如:

$ cd src 
$ ./redis-cli
redis> set foo bar
OK
redis> get foo "bar"

Ubuntu 下安装

在 Ubuntu 系统安装 Redis 可以使用以下命令:

$sudo apt-get update 
$sudo apt-get install redis-server

启动 Redis

$ redis-server

查看 redis 是否启动?

$ redis-cli

以上命令将打开以下终端:

redis 127.0.0.1:6379>

127.0.0.1 是本机 IP ,6379 是 redis 服务端口。现在我们输入 PING 命令。

redis 127.0.0.1:6379> ping
PONG

以上说明我们已经成功安装了redis。

查看Redis进程

$ lsof -i:6379

MongoDB 指定配置启动

本文档是在mongodb为4.1下编写的,仅作为参考,详细内容请参考:https://docs.mongodb.com/manual/reference/configuration-options/#configuration-file

一.说明

     配置mongodb有两种方式,一种是通过mongod和mongos两个命令;另外一种方式就是配置文件的方式。因为更容易去管理,所以后者更受大家的青睐。

二. 配置文件格式

    mongodb 配置文件采用的YAML格式;

    例如:

数据库数据存放目录
dbpath=/usr/local/mongodb/data
数据库日志存放目录
logpath=/usr/local/mongodb/logs/mongodb.log
以追加的方式记录日志
logappend = true
端口号 默认为27017
port=27017
以后台方式运行进程
fork=true
开启用户认证
auth=true
关闭http接口,默认关闭http端口访问
nohttpinterface=true
mongodb所绑定的ip地址
bind_ip = 127.0.0.1
启用日志文件,默认启用
journal=true
这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置为false
quiet=true
bind_ip=0.0.0.0

三 使用配置文件

           通过mongod和mongos命令去执行配置文件,这里要使用他们的一个选项–config(这里是两个横线,具体查看 > mongod –help)或者-f(–config的简写)

例如:


mongod --config  /usr/local/mongdb4/mongod.conf

mongos --config/usr/local/mongdb4/mongodb/mongos.conf

  或


mongod -f  /usr/local/mongdb4/mongod.conf
mongos -f /usr/local/mongdb4/mongodb/mongos.conf

四 配置文件的核心选项

 1. systemLog 选项


systemLog:
verbosity: <int> 
quiet: <boolean>
traceAllExceptions: <boolean>
syslogFacility: <string>
path: <string>
logAppend: <boolean>
logRotate: <string>
destination: <string>
timeStampFormat: <string>
component:
accessControl:
verbosity: <int>
command:
verbosity: <int>

 systemLog.traceAllExceptions

               类型:boolean

               作用: 为调试打印详细信息,用于支持相关的故障排除。

     systemLog.syslogFacility

               类型:string

               默认值:user

               作用:将mongodb使用日志记录到系统日志中,如果要使用这个选项,必须开启–sysylog选项

      systemLog.path

               类型:string

               作用:指定日志文件的目录

        syetemLog.logAppend

               类型:boolean

                默认值:False

               作用:当mongod或mongos重启时,如果为true,将日志追加到原来日志文件内容末尾;如果为false,将创建一个新的日志文件

         systemLog.destination

               类型:string

               作用:指定日志文件的路径,如果设置了这个值,必须指定systemLog.path.如果没有设置,日志会标准的输出到后台

         systemLog.timeStampFormat

                类型:string

                默认值:iso8601-local

               作用:为日志添加时间戳。

描述
ctime 显示时间戳格式为:Wed Dec 31 18:17:54.811.
iso8601-utc安装iso-8601-utc格式显示:1970-01-01T00:00:00.000Z
iso8601-local按照iso8601-local格式显示:1969-12-31T19:00:00.000-0500

  processMangement 选项    

processManagement:fork: <boolean>pidFilePath: <string>

  processMangement.fork

             类型:Boolean

             默认值:False

            作用:在前台启动Mongodb进程,如果Session窗口关闭,Mongodb进程也随之停止。不过Mongodb同时还提供了一种后台Daemon方式启动,只需要加上一个”–fork”参数即可,值得注意的是,用到了”–fork”参数就必须启用”–logpath”参数。如下所示:


[root@localhost mongodb]# ./bin/mongod –dbpath=data/db –fork –fork has to be used with –logpath 

[root@localhost mongodb]# ./bin/mongod –dbpath=data/db –fork –logpath=log/mongodb.log  

all output going to: /opt/mongodb/log/mongodb.log 

forked process: 3300 


  daemon方式启动的fork参数也可以配置配置文件中,如下所示:


port=27017 dbpath=data/db logpath=log/mongodb.log logappend=true fork=true 

 net 选项 


net:
port: <int>
bindIp: <string>
maxIncomingConnections: <int>
wireObjectCheck: <boolean>
ipv6: <boolean>
unixDomainSocket:
enabled: <boolean>
pathPrefix: <string>
filePermissions: <int>
http:
enabled: <boolean>
JSONPEnabled: <boolean>
RESTInterfaceEnabled: <boolean>
ssl:
sslOnNormalPorts: <boolean>  # deprecated since 2.6
mode: <string>
PEMKeyFile: <string>
PEMKeyPassword: <string>
clusterFile: <string>
clusterPassword: <string>
CAFile: <string>
CRLFile: <string>
allowConnectionsWithoutCertificates: <boolean>
allowInvalidCertificates: <boolean>
allowInvalidHostnames: <boolean>
disabledProtocols: <string>
FIPSMode: <boolean>
compression:
compressors: <string>

  net.port

                类型:integer

               默认值:27017

                作用:设置mongodb的监听TCP端口

       net.bindIp

                类型:string

                作用:设置mongodb服务器监听ip地址,默认是127.0.0.1;如果监听多个ip地址,使用逗号隔开

        net.maxIncomingConnections

                类型:integer 

               默认值:65536

               作用:最大并发链接数

       net.ipv6

              类型:boolean

              默认值:false

             作用:开启或关闭支持ipv6地址;

  security 选项     


security:
keyFile: <string>
clusterAuthMode: <string>
authorization: <string>
transitionToAuth: <boolean>
javascriptEnabled:  <boolean>
redactClientLogData: <boolean>
sasl:
hostName: <string>
serviceName: <string>
saslauthdSocketPath: <string>
enableEncryption: <boolean>
encryptionCipherMode: <string>
encryptionKeyFile: <string>
kmip:
keyIdentifier: <string>
rotateMasterKey: <boolean>
serverName: <string>
port: <string>
clientCertificateFile: <string>
clientCertificatePassword: <string>
serverCAFile: <string>
ldap:
servers: <string>
bind:
method: <string>
saslMechanisms: <string>
queryUser: <string>
queryPassword: <string>
useOSDefaults: <boolean>
transportSecurity: <string>
timeoutMS: <int>
userToDNMapping: <string>
authz:
queryTemplate: <string>

  ….查看mongodb手册 #security选项

setParameter 选项

     设置mongodb参数,查看参数列表

     采用YAML语言格式

123setParameter:<parameter1>: <value1><parameter2>: <value2>

  storage 选项


storage:
dbPath: <string>
indexBuildRetry: <boolean>
repairPath: <string>
journal:
enabled: <boolean>
commitIntervalMs: <num>
directoryPerDB: <boolean>
syncPeriodSecs: <int>
engine: <string>
mmapv1:
preallocDataFiles: <boolean>
nsSize: <int>
quota:
enforced: <boolean>
maxFilesPerDB: <int>
smallFiles: <boolean>
journal:
debugFlags: <int>
commitIntervalMs: <num>
wiredTiger:
engineConfig:
cacheSizeGB: <number>
journalCompressor: <string>
directoryForIndexes: <boolean>
collectionConfig:
blockCompressor: <string>
indexConfig:
prefixCompression: <boolean>
inMemory:
engineConfig:
inMemorySizeGB: <number>

  storage.dbPath

                类型:string

                默认值:/data/db(linux和macOS系统) ,\data\db(window系统)

               作用:设置数据存储文件目录

     storage.indexBuildRetry

               类型:boolean

               默认值:true

               作用:开启或关闭是否在mongod下次启动重建不完整的索引。

               注:在in-memory存储引擎下不可用

storage.repairPath

               类型:string

               默认值:在dbpath下的A _tmp_repairDatabase_<num> 文件目录

               作用:为进行恢复操作指定目录

                注意:仅仅在MMAPv1存储引擎下可用

  storage.journal.enabled

              类型:boolean

               默认值:true(64-bit系统);false(32-bit系统)

               作用:开启和关闭journal,为了保证数据文件的有效性和可恢复性;在设置了dbpath之后有效

                注:在in-memory存储引擎下不可用

   storage.directoryPerDB

       类型:boolean

        默认值:false

         作用:当为true,mongodb为每个数据库建立一个单独的路径,这个路径是在dbpath下创建的;每次创建需要重启服务器

           注:在in-memory存储引擎下不可用

   storage.engine

           默认值:wiredTiger

           作用:这是数据存储引擎

描述
MMAPV1MMAPCV1 storage engine
wiredTigerWiredTiger Storage Engine.
inMemoryIn-Memory Storage Engine.

   storage.mmapv1 选项


storage:
mmapv1:
preallocDataFiles: <boolean>
nsSize: <int>
quota:
enforced: <boolean>
maxFilesPerDB: <int>
smallFiles: <boolean>
journal:
debugFlags: <int>
commitIntervalMs: <num>

  storage.mmapv1.preallocDataFiles

                 类型:boolean

                 默认值:true

                 作用:开启或关闭数据文件的预分配;

   storage.mmapv1.quota.enforced

                 类型:boolean

                 默认值:false

                  作用:开启或关闭每个数据库中的数据文件个数的限额;默认是每个数据库最多有8个数据文件,通过调整storage.mmapv1.quota.maxFilesPerDB

        storage.mmapv1.quota.maxFilesPerDB

                  类型:integer

                      默认值:8

                      作用:设置每个数据库中数据文件的限制个数;

     storage.wiredTiger 选项


storage:
wiredTiger:
engineConfig:
cacheSizeGB: <number>
journalCompressor: <string>
directoryForIndexes: <boolean>
collectionConfig:
blockCompressor: <string>
indexConfig:
prefixCompression: <boolean>

  storage.wiredTiger.engineConfig.cacheSizeGB

                  类型:float

                  作用:设置缓存大小,从3.4版本开始,内存的50%-1GB 和256MB的最大值

        storage.wriedTiger.engineConfig.journalCompressor

                  默认值:snappy

                  作用:设置journal压缩方式;可选项:none/snappy/zlib

  storage.inmemory 选项

 
storage:inMemory:engineConfig:inMemorySizeGB: <number>

  storage.inmemory.engineConfig.inMemorySizeGB

                     类型:float

                     默认值:物理内存的50%-1GB

                    作用:设置缓冲区大小;

    opeartionProfiling 选项       

 
operationProfiling:slowOpThresholdMs: <int>mode: <string>

  opeartionProfiling.slowOpThresholdMs

                类型:integer

                默认值:100

                 作用:设置区分慢查询的一个阈值,比如100,当查询速度大于100ms,记录到日志中

        operationProfiling.mode

                 类型:string

                 默认值:off

      replication 选项       

 
replication:oplogSizeMB: <int>replSetName: <string>secondaryIndexPrefetch: <string>enableMajorityReadConcern: <boolean>

  replication.oplogSizeMB

               类型:integer

                作用:设置复制日志文件的大小;

 sharding 选项   

 
sharding:clusterRole: <string>archiveMovedChunks: <boolean>

   sharding.clusterRole

              类型:string

               作用:设置分片集群中的角色;

值  描述
configsvr作为配置服务器,默认端口27019
shardsvr作为一个分片,默认端口27018

 auditLog 选项 (MongoDB Enterprise可用)


auditLog:destination: <string>format: <string>path: <string>filter: <string>

  auditLog.destination 

                 类型:string

                 作用:审计日志位置;

描述
syslogJSON文件格式输出在系统日志中,window系统中不可用
consoleJSON格式输出
file输出到文件

    auditLog.format

              类型:string

              作用:设置设计日志输出格式;可用值:JSON/BSON

   auditLog.path

             类型:string

                作用:设计日志输出文件路径

    auditLog.filter

             类型:string

              作用:审计日志过滤器

              格式:{ <field1>: <expression1>, … }