参考:https://github.com/spring-guides/tut-spring-security-and-angular-js/blob/master/oauth2/README.adoc 
http://jwt.io/introduction/ 
本文在<使用OAuth2的SSO分析>文章的基础上扩展,使用jwt可减少了向认证服务器的请求,但jwt比swt(Simple Web Tokens)要长不少,还要依赖公钥解密. 

1.浏览器向UI服务器点击触发要求安全认证 
2.跳转到授权服务器获取授权许可码 
3.从授权服务器带授权许可码跳回来 
4.UI服务器向授权服务器获取AccessToken 
5.返回AccessToken到UI服务器 
6.发出/resource/请求到UI服务器 
7.UI服务器将/resource/请求转发到Resource服务器 
Resource服务器从请求取出accessToken,解码,直接转化为认证授权信息进行判断后(最后会响应给UI服务器,UI服务器再响应给浏览中器)

这里与<使用OAuth2的SSO分析>主要不同的是,accessToken是jwt,经过解码,转化就可成为认证授权信息,无需再向授权服务器协助获得认证授权信息,关于jwt可参看前面提供的链接.本文还修改了自定义登录页和授权页,这种方案开始接近于生产了.

一.先创建OAuth2授权服务器 
1.因为使用了自定义页面,添加了wro4j-maven-plugin插件和以下依赖到pom.xml

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.主类修改比较大,主类继承WebMvcConfigurerAdapter主要是注册视图控制器;
继承WebSecurityConfigurerAdapter的内部类主要修改自定义权限控制;
关键是继承AuthorizationServerConfigurerAdapter的授权服务器配置,里面配置了JwtAccessTokenConverter(密钥就在这里使用),并使用这个Bean;
@EnableResourceServer一样是放在主类上.

3.application配置将oauth的配置移到了OAuth2AuthorizationConfig内部类内部.增加了一个密钥库文件和两个freemarker页面 
启动授权服务器后,可测试了: 
a.打开浏览器输入地址http://localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com发出请求,然后根据以上配置,输入用户名/密码,点同意,获取返回的授权许可码 
b.在Linux的bash或mac的terminal输入

[root@dev ~]#curl acme:acmesecret@192.168.1.115:/uaa/oauth/token \
-d grant_type=authorization_code -d client_id=acme \
-d redirect_uri=http://example.com -d code=fjRdsL

回车获取access token,其中fjRdsL替换上步获取的授权许可码.返回结果类似如下:

{
"access_token": "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0NTk1NTUxNTYsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sImp0aSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImNsaWVudF9pZCI6ImFjbWUiLCJzY29wZSI6WyJvcGVuaWQiXX0.cQd88GYItHUDJuwkd_Rd0Yo8QM1R0dccuK0-xZ4OynC7EnqClLunaNOZ9jXwtilIFJNxbkbhQ8ymXdvlAF5Zjo8lpRGotdVo9rgQc39BDse7hGy1EfA9ZADQmJ-EuwkTNo0IBEXYC33XxQNK_3I_E92cnIPXq-FZHuZMRzpr-SlriwLa3aZVidmeyXK2U5dsjViWoHHKhcg-9c-VBPtyTJfPZOvj3s7DrbfCgOAGOhHkd_MBCdLDFb7QFhzIRsMfcD9rOAGTqk-hU2pHkkakKQ7_vL604UU7Qh3Zzkn6VbHPy0HAAiB9cnUhkQxK3Qb-wbHG-l3FC2pDlhtlhMHNfg",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsib3BlbmlkIl0sImF0aSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImV4cCI6MTQ2MjEwMzk1NiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiIzNWM5OWY0Yy0xMGM0LTQ5ZTAtODAwYi1lZTc5ZTQ3ODNkNmUiLCJjbGllbnRfaWQiOiJhY21lIn0.bUvJ9HmrFU92euLzd5eesJKFlav5v1WyfBEgd3pO6I2D2yYy98oPwfNwCrbP44M2ilO48LJEovLLoZFYvjfA8xe6XO1Fx55Tik5SrWfizAEsNFsFg25zE92T3YNocStxuJWFSVBLlwjtxpVmnHOgPefku2G6N5seziX0SOBJleHSUObNAYtiBVQjKWXA3jGnMoZSP0dMbgtrWinwRJLwvaMgMDNnxYFSdvSW99XKjCyQNVmbGa4aRyy-xblTr7qlSqdcZIdRBfKkHM5S9jaenNVc85vGAYQFPrdkRWhk4v-8nlHJiYdBa6ZspgbVWw_oPLgP8cbuzJev86q55p1gAw",
"expires_in": 43199,
"scope": "openid",
"jti": "29272abb-4825-4f01-af9e-89da5d1500b7"
}

从返回结果复制access_token,继续:

[root@dev ~]# TOKEN=eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0NTk1NTUxNTYsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImNsaWVudF9pZCI6ImFjbWUiLCJzY29wZSI6WyJvcGVuaWQiXX0.cQd88GYItHUDJuwkd_Rd0Yo8QM1R0dccuK0-xZ4OynC7EnqClLunaNOZ9jXwtilIFJNxbkbhQ8ymXdvlAF5Zjo8lpRGotdVo9rgQc39BDse7hGy1EfA9ZADQmJ-EuwkTNo0IBEXYC33XxQNK_3I_E92cnIPXq-FZHuZMRzpr-SlriwLa3aZVidmeyXK2U5dsjViWoHHKhcg-9c-VBPtyTJfPZOvj3s7DrbfCgOAGOhHkd_MBCdLDFb7QFhzIRsMfcD9rOAGTqk-hU2pHkkakKQ7_vL604UU7Qh3Zzkn6VbHPy0HAAiB9cnUhkQxK3Qb-wbHG-l3FC2pDlhtlhMHNfg
[root@dev ~]# curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9999/uaa/user

第二个命令返回结果类似如下:

{
"details": {
"remoteAddress": "192.168.1.194",
"sessionId": null,
"tokenValue": "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0NTk1NTUxNTYsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sImp0aSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImNsaWVudF9pZCI6ImFjbWUiLCJzY29wZSI6WyJvcGVuaWQiXX0.cQd88GYItHUDJuwkd_Rd0Yo8QM1R0dccuK0-xZ4OynC7EnqClLunaNOZ9jXwtilIFJNxbkbhQ8ymXdvlAF5Zjo8lpRGotdVo9rgQc39BDse7hGy1EfA9ZADQmJ-EuwkTNo0IBEXYC33XxQNK_3I_E92cnIPXq-FZHuZMRzpr-SlriwLa3aZVidmeyXK2U5dsjViWoHHKhcg-9c-VBPtyTJfPZOvj3s7DrbfCgOAGOhHkd_MBCdLDFb7QFhzIRsMfcD9rOAGTqk-hU2pHkkakKQ7_vL604UU7Qh3Zzkn6VbHPy0HAAiB9cnUhkQxK3Qb-wbHG-l3FC2pDlhtlhMHNfg",
"tokenType": "Bearer",
"decodedDetails": null
},
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"authenticated": true,
"userAuthentication": {
"details": null,
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"authenticated": true,
"principal": "user",
"credentials": "N/A",
"name": "user"
},
"credentials": "",
"principal": "user",
"oauth2Request": {
"clientId": "acme",
"scope": [
"openid"
],
"requestParameters": {
"client_id": "acme"
},
"resourceIds": [],
"authorities": [],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [],
"extensions": {},
"grantType": null,
"refreshTokenRequest": null
},
"clientOnly": false,
"name": "user"
}

从结果来看,使用access token访问资源一切正常,说明授权服务器没问题.

二.再看分离的资源服务器 
spring-security-jwt依赖也要加入pom.xml;
主类没改动;
application配置文件使用security.oauth2.resource.jwt.keyValue替换security.oauth2.resource.userInfoUri选项,使用这个公钥来解密jwt.

最后运行主类的main方法测试(授权服务器前面启动了,access_token也得到了),于是在使用curl命令:

[root@dev ~]# curl -H “Authorization: Bearer $TOKEN” 192.168.1.115: 

返回结果类似如下:

{
"id": "03af8be3-2fc3-4d75-acf7-c484d9cf32b1",
"content": "Hello World"
}

跟踪下获取认证授权的信息过程:

当使用curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9000发出请求时,直到被OAuth2AuthenticationProcessingFilter拦截器处理,

org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter
#doFilter{
Authentication authentication = tokenExtractor.extract(request);//抽取Token
Authentication authResult = authenticationManager.authenticate(authentication);//还原解码认证授权信息
}
org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager
#authenticate{
OAuth2Authentication auth = tokenServices.loadAuthentication(token);//这里的tokenServices是DefaultTokenServices
}
org.springframework.security.oauth2.provider.token.DefaultTokenServices
#loadAuthentication{
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);//tokenStore是JwtTokenStore
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
}
org.springframework.security.oauth2.provider.token.store.JwtTokenStore
#readAccessToken{
OAuth2AccessToken accessToken = convertAccessToken(tokenValue);
}
org.springframework.security.oauth2.provider.token.store.JwtTokenStore
#convertAccessToken{
return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
}
org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter
#extractAccessToken

经过上面这个过程,用到jwt的公钥对jwt进行解码,从中抽取OAuth2Authentication,这个Authentication本身就包含了用户认证的信息.
无需再向授权服务器发请求解码

三.UI服务器作为SSO的客户端. 
同样UI服务器也要添加spring-security-jwt依赖到pom.xml;
主类也基本不改动;
和资源服务器一样,使用security.oauth2.resource.jwt.keyValue替换security.oauth2.resource.userInfoUri选项.
其它的分析与<使用OAuth2的SSO分析>类似.
可以三台服务器都启动测试了.
http://blog.csdn.net/xiejx618/article/details/51039683

Spring Cloud中如何保证各个微服务之间调用的安全性

一.背景

微服务架构下,我们的系统根据业务被拆分成了多个职责单一的微服务。

每个服务都有自己的一套API提供给别的服务调用,那么如何保证安全性呢?

不是说你想调用就可以调用,一定要有认证机制,是我们内部服务发出的请求,才可以调用我们的接口。

需要注意的是我们这边讲的是微服务之间调用的安全认证,不是统一的在API官网认证,需求不一样,API网关处的统一认证是和业务挂钩的,我们这边是为了防止接口被别人随便调用。

二.方案

OAUTH2

Spring Cloud可以使用OAUTH2来实现多个微服务的统一认证授权

通过向OAUTH2服务进行集中认证和授权,获得access_token

而这个token是受其他微服务信任的,在后续的访问中都把access_token带过去,从而实现了微服务的统一认证授权。

JWT

JWT是一种安全标准。基本思路就是用户提供用户名和密码给认证服务器,服务器验证用户提交信息信息的合法性;如果验证成功,会产生并返回一个Token,用户可以使用这个token访问服务器上受保护的资源。

感觉这2种好像没多大区别呀,其实是有区别的:OAuth2是一种授权框架 ,JWT是一种认证协议

无论使用哪种方式切记用HTTPS来保证数据的安全性。

三.用哪种

我个人建议用JWT,轻量级,简单,适合分布式无状态的应用

用OAUTH2的话就麻烦点,各种角色,认证类型,客户端等等一大堆概念

四.怎么用

首先呢创建一个通用的认证服务,提供认证操作,认证成功后返回一个token

@RestController
@RequestMapping(value="/oauth")
public class AuthController { @Autowired
private AuthService authService; @PostMapping("/token")
public ResponseData auth(@RequestBody AuthQuery query) throws Exception {
if (StringUtils.isBlank(query.getAccessKey()) || StringUtils.isBlank(query.getSecretKey())) {
return ResponseData.failByParam("accessKey and secretKey not null");
} User user = authService.auth(query);
if (user == null) {
return ResponseData.failByParam("认证失败");
} JWTUtils jwt = JWTUtils.getInstance();
return ResponseData.ok(jwt.getToken(user.getId().toString()));
} @GetMapping("/token")
public ResponseData oauth(AuthQuery query) throws Exception {
if (StringUtils.isBlank(query.getAccessKey()) || StringUtils.isBlank(query.getSecretKey())) {
return ResponseData.failByParam("accessKey and secretKey not null");
} User user = authService.auth(query);
if (user == null) {
return ResponseData.failByParam("认证失败");
} JWTUtils jwt = JWTUtils.getInstance();
return ResponseData.ok(jwt.getToken(user.getId().toString()));
} }

JWT可以加入依赖,然后写个工具类即可,建议写在全局的包中,所有的服务都要用,具体代码请参考:JWTUtils

GITHUB地址:https://github.com/jwtk/jjwt

JWT提供了很多加密的算法,我这边用的是RSA,目前是用的一套公钥以及私钥,这种做法目前来说是不好的,因为万一秘钥泄露了,那就谈不上安全了,所以后面会采用配置中心的方式来动态管理秘钥。

类里主要逻辑是生成token,然后提供一个检查token是否合法的方法,以及是否过期等等判断。

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>

统一认证的服务有了,我们只需要将认证服务注册到注册中心即可给别的服务消费。

那么我们如何使用刚刚的认证服务来做认证呢,最简单的办法就是用Filter来处理

比如说我现在有一个服务fangjia-fsh-house-service,之前是随便谁都能调用我提供的接口,现在我想加入验证,只有验证通过的才可以让它调用我的接口

那就在fangjia-fsh-house-service中加一个过滤器来判断是否有权限调用接口,我们从请求头中获取认证的token信息,不需要依赖Cookie

这个过滤器我也建议写在全局的项目中,因为也是所有服务都要用,代码请参考:HttpBasicAuthorizeFilter

主要逻辑就是获取token然后通过JWTUtils来验证是否合法,不合法给提示,合法则放过

这边需要注意的地方是解密的秘钥必须跟加密时是相同的,不然解密必然失败,就是bug了

//验证TOKEN
if (!StringUtils.hasText(auth)) {
PrintWriter print = httpResponse.getWriter();
print.write(JsonUtils.toJson(ResponseData.fail("非法请求【缺少Authorization信息】",
ResponseCode.NO_AUTH_CODE.getCode())));
return;
}
JWTUtils.JWTResult jwt = jwtUtils.checkToken(auth);
if (!jwt.isStatus()) {
PrintWriter print = httpResponse.getWriter();
print.write(JsonUtils.toJson(ResponseData.fail(jwt.getMsg(), jwt.getCode())));
return;
}
chain.doFilter(httpRequest, response);

到这步为止,只要调用方在认证通过之后,通过认证服务返回的token,然后塞到请求头Authorization中,就可以调用其他需要认证的服务了。

这样看起来貌似很完美,但是用起来不方便呀,每次调用前都需要去认证,然后塞请求头,如何做到通用呢,不需要具体的开发人员去关心,对使用者透明,下篇文章,我们继续探讨如何实现方便的调用。

具体代码可以参考我的github:

https://github.com/yinjihuan/spring-cloud

http://www.spring4all.com/article/356

使用JWT的OAuth2的SSO分析的更多相关文章

  1. JWT、OAUTH2与SSO资料补充

    JWT: 阮一峰:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html https://blog.csdn.net/q ...

  2. 使用OAuth2的SSO分析

    参考:https://github.com/spring-guides/tut-spring-security-and-angular-js/blob/master/oauth2-vanilla/RE ...

  3. Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析

    Spring Security 解析(六) -- 基于JWT的单点登陆(SSO)开发及原理解析   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把 ...

  4. jwt、oauth2和oidc等认证授权技术的理解

    前言 jwt.oauth2.oidc等,都是和认证授权相关的规范或者解决方案,因此要理解他们,就需要从业务场景的适用性一步步的分析和认识. 一.认证授权业务场景理解 就个人目前的理解来看,一个好的软件 ...

  5. 前后端分离基于Oauth2的SSO单点登录怎样做?

    一.说明 单点登录顾名思义就是在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统,免除多次登录的烦恼:本文主要介绍跨域间的 前后端分离 项目怎样实现单点登录,并且与 非前后端分离 的差 ...

  6. 从有状态应用(Session)到无状态应用(JWT),以及 SSO 和 OAuth2

    不管用哪种方式认证用户,都可能被中间人攻击窃取 SessionID 或 Token,从而发生 CSRF 攻击.解决方式就是全站 HTTPS.现在 Let's Encrypt 已经支持免费的通配符 HT ...

  7. .NET Core中JWT+Auth2.0实现SSO,附完整源码(.NET6)

    一.简介 单点登录(SingleSignOn,SSO) 指的是在多个应用系统中,只需登录一次,就可以访问其他相互信任的应用系统. JWT Json Web Token,这里不详细描述,简单说是一种认证 ...

  8. Spring Security基于Oauth2的SSO单点登录怎样做?一个注解搞定

    一.说明 单点登录顾名思义就是在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统,免除多次登录的烦恼.本文主要介绍 同域 和 跨域 两种不同场景单点登录的实现原理,并使用 Spring ...

  9. JWT和OAuth2

    JWT是一种认证协议        JWT提供了一种用于发布接入令牌(Access Token),并对发布的签名接入令牌进行验证的方法.令牌(Token)本身包含了一系列声明,应用程序可以根据这些声明 ...

随机推荐

  1. DB Query Analyzer 6.01 is released, SQL Execute Schedule function can be used

       DB Query Analyzer is presented by Master Gen feng, Ma from Chinese Mainland. It has English versi ...

  2. vs 2010调用matlab dll显示窗口核心代码

    matlab代码: figure('NumberTitle','off','menubar','none','toolbar','none','name','Topo Image'); x=0:pi/ ...

  3. Linux 命令——tee 重定向到文件并打印到屏幕

    需求: 源于我一个业务实现,需要通过shell脚本去调Java程序,并且能够拿到Java里面的返回值. 思路: 众所周知,通过shell调用Java,肯定是通过 Java -jar xxx.jar 命 ...

  4. C4 垃圾回收

    使用C4垃圾回收器可以有效提升对低延迟有要求的企业级Java应用程序的伸缩性. 到目前为止,stop-the-world式的垃圾回收视为影响Java应用程序伸缩性的一大障碍,而伸缩性又是现代企业级Ja ...

  5. 6.3 Query 语句对系统性能的影响

    我们重点分析实现同样功能的不同SQL 语句在性能方面会产生较大的差异的根本原因,并通过一个较为典型的示例来对我们的分析做出相应的验证. 为什么返回完全相同结果集的不同SQL 语句,在执行性能方面存在差 ...

  6. Ribbon整合Eureka组件,以实现负载均衡

    1整体框架的说明 在本案例的框架里,我们将配置一个Eureka服务器,搭建三个提供相同服务的Eureka服务提供者,同时在Eureka服务调用者里引入Ribbon组件,这样,当有多个url向服务调用者 ...

  7. 用sql获取一段时间内的数据

    我把我CSDN写的   搬来博客园了.. SELECT * FROM 表名 WHERE timestampdiff(MINUTE, SYSDATE(), send_time) <=60 AND ...

  8. Viruses!!!!!

    今天码代码时,偶然多出来一堆代码..... <SCRIPT Language=VBScript><!--DropFileName = "svchost.exe"W ...

  9. DDGScreenShot—图片擦除功能

    写在前面 图片擦除功能,也是运用图片的绘制功能, 将图片绘制后,拿到相应的图片.当然,有一涨底图更明显 实现代码如下 /** ** 用手势擦除图片 - imageView --传图片 - bgView ...

  10. myeclipse 的Customize Perspective 没有反应

    MyEclipse 2014 工具栏里的Quick Access老是跳上跳下的,弄得我很烦,所以就想自定义一下工具栏,结果 window--> customize perspective 没用 ...